-
-
Agent-Test (max. {MAX_SLOTS} URLs)
-
- Wählt einen Spezialisten-Agent und feuert ihn gegen 1-5 URLs gleichzeitig.
- Pro URL Speedometer + Findings + Empfehlungen mit Quellen-Herkunft (MC / Regex / LLM-Stufe).
- Keine Aussagen "rechtssicher" oder "garantiert" — alle solchen Wörter werden vor Ausgabe gelöscht.
-
-
-
- Agent
- setAgentId(e.target.value)}
- className="border rounded px-2 py-1 text-sm">
- {agents.map(a => (
-
- {a.agent_id} v{a.agent_version} ({a.mc_count} MCs)
-
- ))}
-
-
- {selectedAgent && (
-
- Doc-Type: {selectedAgent.doc_type}
-
- )}
-
-
- {urls.map((u, i) => (
-
- URL{i+1}
- {
- const next = [...urls]; next[i] = e.target.value
- setUrls(next)
- }}
- placeholder="https://example.com/impressum"
- className="flex-1 border rounded px-2 py-1 text-sm font-mono"/>
-
- ))}
-
-
-
- {running ? 'Laufend...' : 'Test starten'}
-
- {runId && (
-
- Run-ID: {runId}
-
- )}
-
- {error && (
-
- {error}
-
- )}
-
+
- {running && events.length > 0 && (
-
-
- {events.slice(-30).map((ev, i) => (
-
- [{ev.type}] {' '}
- {ev.slot && {ev.slot} }{' '}
- {ev.severity && (
-
- {ev.severity}
-
- )}{' '}
- {ev.title || ev.error || ev.label || ev.model || ev.url || ''}
-
- ))}
-
-
- )}
+
+
+ {running && events.length > 0 &&
}
{slotOutputs.length > 0 && (
{slotOutputs.map(({ slot, output }) => (
-
+
))}
)}
@@ -281,174 +170,147 @@ export function AgentTestTab() {
)
}
-function SlotCard({ slot, output, runId }: {
- slot: string
- output: SlotOutput
+function InputCard({
+ agents, agentId, setAgentId, selectedAgent, urls, setUrls,
+ running, runId, startTest, error,
+}: {
+ agents: AgentInfo[]
+ agentId: string
+ setAgentId: (s: string) => void
+ selectedAgent?: AgentInfo
+ urls: string[]
+ setUrls: (urls: string[]) => void
+ running: boolean
runId: string
+ startTest: () => void
+ error: string
}) {
- const [showAll, setShowAll] = useState(false)
- const visibleFindings = showAll ? output.findings : output.findings.slice(0, 8)
- const wasSkipped = output.mc_total > 0 &&
- output.mc_ok === 0 && output.mc_na === 0 &&
- output.mc_high === 0 && output.mc_medium === 0 && output.mc_low === 0
- const allGreen = !wasSkipped && output.findings.length === 0
return (
-
-
-
Slot: {slot}
-
- {output.duration_ms} ms · confidence {(output.confidence * 100).toFixed(0)}%
-
- {wasSkipped && (
-
- Dokument konnte nicht geladen werden (leer/zu kurz)
-
+
+
Agent-Test (max. {MAX_SLOTS} URLs)
+
+
+ Agent
+ setAgentId(e.target.value)}
+ className="border rounded px-2 py-1 text-sm"
+ >
+ {agents.map(a => (
+
+ {a.agent_id} v{a.agent_version} ({a.mc_count} MCs)
+
+ ))}
+
+
+ {selectedAgent && (
+
+ Doc-Type: {selectedAgent.doc_type}
+
)}
- {allGreen && (
-
- Keine Findings — alle anwendbaren MCs OK
-
- )}
-
- Artefakte ↗
-
-
- {output.escalation_log.length > 0 && (
-
- Eskalationen:{' '}
- {output.escalation_log.map((e, i) => (
-
- {e.stage}/{e.model} {e.success ? '✓' : '✗'} ({e.duration_ms} ms)
+
+ {urls.map((u, i) => (
+
+
+ URL{i + 1}
- ))}
-
- )}
- {output.findings.length > 0 && (
-
-
- Findings ({output.findings.length})
+ {
+ const next = [...urls]; next[i] = e.target.value
+ setUrls(next)
+ }}
+ placeholder="https://example.com/impressum"
+ className="flex-1 border rounded px-2 py-1 text-sm font-mono"
+ />
- {visibleFindings.map(f => (
-
- ))}
- {output.findings.length > 8 && (
- setShowAll(x => !x)}
- className="text-xs text-blue-600 hover:underline">
- {showAll ? 'Weniger anzeigen' : `Alle ${output.findings.length} anzeigen`}
-
- )}
-
- )}
- {output.recommendations.length > 0 && (
-
-
- Empfehlungen ({output.recommendations.length}, gerollupt)
-
- {output.recommendations.map(r => (
-
-
{r.title}
-
{r.body}
-
- {r.related_finding_ids.length} Finding(s) · ~{r.estimated_effort_hours}h
-
-
- ))}
-
- )}
-
- )
-}
-
-function Speedometer({ total, ok, na, high, medium, low }: {
- total: number
- ok: number
- na: number
- high: number
- medium: number
- low: number
-}) {
- const safeTotal = Math.max(total, 1)
- return (
-
-
{total} MCs geprüft
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-function Bar({ pct, color }: { pct: number; color: string }) {
- return
-}
-
-function Legend({ color, label }: { color: string; label: string }) {
- return (
-
-
- {label}
-
- )
-}
-
-function FindingRow({ f }: { f: Finding }) {
- const color = severityHex(f.severity)
- const sourceTags = (f.sources || [])
- .map(s => s.source_type)
- .filter((v, i, arr) => arr.indexOf(v) === i)
- return (
-
-
- {f.severity}
- {f.check_id}
- {sourceTags.map(t => (
- {t}
))}
-
{f.title}
- {f.norm &&
{f.norm}
}
- {f.evidence && (
-
„{f.evidence}"
- )}
- {f.action && (
-
- → {f.action}
+
+
+ {running ? 'Laufend...' : 'Test starten'}
+
+ {runId && (
+
+ Run-ID: {runId}
+
+ )}
+
+ {error && (
+
+ {error}
)}
)
}
+function MethodikInfo() {
+ return (
+
+
+ Methodik — wie geprüft wird
+
+
+
+ Machine-Checks (MCs) — deterministische
+ Pattern-Tests gegen Gesetzestext (z.B. § 5 TMG). Schnell,
+ reproduzierbar.
+
+
+ Knowledge-Base — kuratierte Patterns aus
+ anonymisierten Mandanten-FAQs.
+
+
+ LLM-Eskalation — nur bei unklaren MCs:
+ erst lokales qwen2.5:7b, bei Bedarf größeres OVH-Modell.
+ Claude (Cloud) erst nach Anonymisierung.
+
+
+ Cross-Doc-Vergleich — Konsistenz zwischen
+ DSE, Cookie-Policy, Impressum (späterer Agent).
+
+
+
+ Disclaimer: keine Aussagen wie "rechtssicher" oder "konform" —
+ nur Findings + Empfehlungen + Herleitung. Verbotene Begriffe
+ werden vom Linter aus Agent-Outputs entfernt.
+
+
+ )
+}
+
+function EventLog({ events }: { events: StreamEvent[] }) {
+ return (
+
+
+ {events.slice(-30).map((ev, i) => (
+
+ [{ev.type}] {' '}
+ {ev.slot && {ev.slot} }{' '}
+ {ev.severity && (
+ {ev.severity}
+ )}{' '}
+ {ev.title || ev.error || ev.label || ev.model || ev.url || ''}
+ {ev.word_count !== undefined && (
+
+ {' '}({ev.word_count} Wörter)
+
+ )}
+
+ ))}
+
+
+ )
+}
+
function severityColor(sev: string) {
return sev === 'HIGH' ? 'text-red-600 font-semibold' :
sev === 'MEDIUM' ? 'text-amber-600 font-semibold' :
sev === 'LOW' ? 'text-blue-600' : 'text-gray-600'
}
-
-function severityHex(sev: string) {
- return sev === 'HIGH' ? '#dc2626' :
- sev === 'MEDIUM' ? '#f59e0b' :
- sev === 'LOW' ? '#3b82f6' : '#94a3b8'
-}
diff --git a/admin-compliance/app/sdk/agent/_components/_agentTypes.ts b/admin-compliance/app/sdk/agent/_components/_agentTypes.ts
new file mode 100644
index 00000000..aa353a66
--- /dev/null
+++ b/admin-compliance/app/sdk/agent/_components/_agentTypes.ts
@@ -0,0 +1,153 @@
+// Shared types for the agent-test UI.
+//
+// SourceType-Mapping zur Methodik-Anzeige:
+// mc / regex → "Machine-Check (deterministisch)"
+// kb_faq → "Knowledge-Base (kuratiert)"
+// llm_local → "Lokales LLM (qwen2.5:7b)"
+// llm_local_big → "Externes LLM (OVH 120b)"
+// llm_cloud → "Cloud-LLM (Claude, anonymisiert)"
+// cross → "Cross-Doc-Vergleich"
+
+export type Severity = 'HIGH' | 'MEDIUM' | 'LOW' | 'INFO'
+
+export type SourceType =
+ | 'mc'
+ | 'regex'
+ | 'kb_faq'
+ | 'llm_local'
+ | 'llm_local_big'
+ | 'llm_cloud'
+ | 'cross'
+
+export interface EvidenceSource {
+ source_type: SourceType
+ source_id: string
+ detail?: string
+ confidence?: number
+}
+
+export interface Finding {
+ check_id: string
+ agent: string
+ agent_version: string
+ field_id?: string
+ severity: Severity
+ severity_reason?: string
+ title: string
+ norm?: string
+ evidence?: string
+ action?: string
+ confidence?: number
+ sources?: EvidenceSource[]
+}
+
+export interface Recommendation {
+ recommendation_id: string
+ title: string
+ body: string
+ severity: Severity
+ related_finding_ids: string[]
+ estimated_effort_hours: number
+}
+
+export interface McCoverage {
+ mc_id: string
+ status: 'ok' | 'na' | 'high' | 'medium' | 'low' | 'skipped'
+ reason?: string
+}
+
+export interface EscalationLog {
+ stage: SourceType
+ model: string
+ duration_ms: number
+ tokens_in?: number
+ tokens_out?: number
+ success: boolean
+ error?: string
+}
+
+export interface SlotOutput {
+ agent: string
+ agent_version: string
+ findings: Finding[]
+ recommendations: Recommendation[]
+ mc_coverage: McCoverage[]
+ escalation_log: EscalationLog[]
+ mc_total: number
+ mc_ok: number
+ mc_na: number
+ mc_high: number
+ mc_medium: number
+ mc_low: number
+ duration_ms: number
+ confidence: number
+ notes?: string
+}
+
+export interface AgentInfo {
+ agent_id: string
+ agent_version: string
+ doc_type: string
+ mc_count: number
+}
+
+export interface RunResult {
+ run_id: string
+ agent_id: string
+ finished: boolean
+ results: Record
+ vault_url: string
+}
+
+export interface StreamEvent {
+ type: string
+ slot?: string
+ [key: string]: any
+}
+
+// ── Methodik-Labels für die Source-Type-Badge ───────────────────────
+
+export const METHODIK_LABEL: Record = {
+ mc: 'Machine-Check (deterministisch)',
+ regex: 'Pattern-Match (deterministisch)',
+ kb_faq: 'Knowledge-Base (kuratiert)',
+ llm_local: 'Lokales LLM (qwen2.5:7b)',
+ llm_local_big: 'Externes LLM (OVH 120b)',
+ llm_cloud: 'Cloud-LLM (anonymisiert)',
+ cross: 'Cross-Doc-Vergleich',
+}
+
+export const METHODIK_SHORT: Record = {
+ mc: 'MC',
+ regex: 'Regex',
+ kb_faq: 'KB',
+ llm_local: 'LLM',
+ llm_local_big: 'LLM⁺',
+ llm_cloud: 'Claude',
+ cross: 'Cross',
+}
+
+// Background/foreground colors für die Methodik-Badge.
+export const METHODIK_COLOR: Record = {
+ mc: { bg: '#e0e7ff', fg: '#3730a3' },
+ regex: { bg: '#e0e7ff', fg: '#3730a3' },
+ kb_faq: { bg: '#fef3c7', fg: '#92400e' },
+ llm_local: { bg: '#dcfce7', fg: '#166534' },
+ llm_local_big: { bg: '#bbf7d0', fg: '#14532d' },
+ llm_cloud: { bg: '#fce7f3', fg: '#9d174d' },
+ cross: { bg: '#fed7aa', fg: '#9a3412' },
+}
+
+export const SEVERITY_COLOR: Record = {
+ HIGH: '#dc2626',
+ MEDIUM: '#f59e0b',
+ LOW: '#3b82f6',
+ INFO: '#64748b',
+}
+
+export const SEVERITY_BG: Record = {
+ HIGH: '#fef2f2',
+ MEDIUM: '#fffbeb',
+ LOW: '#eff6ff',
+ INFO: '#f8fafc',
+}