Files
breakpilot-compliance/admin-compliance/app/sdk/agent/_components/AgentSlotCard.tsx
T
Benjamin Admin 3ef8c9b247
CI / detect-changes (push) Successful in 7s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Failing after 4s
CI / validate-canonical-controls (push) Successful in 11s
CI / loc-budget (push) Successful in 14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m24s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
feat(agents): Frontend Methodik-First Layout
User-Vorgabe: pro Slot transparent zeigen WAS wir tun:
  1. Was wurde geprueft (MC-Coverage, collapsible)
  2. Speedometer mit Severity-Verteilung
  3. LLM-Eskalation-Log (wenn benutzt)
  4. Findings sortiert HIGH->LOW, je Card:
     - Methodik-Badge (MC / Regex / KB / LLM / Cross)
     - Gesetzliche Basis (Norm-Block, violett)
     - Befund (Zitat-Block, amber)
     - Empfehlung -> 'Pflicht-Massnahme' bei HIGH,
       'Best-Practice' bei MEDIUM/LOW, 'LLM-Vorschlag'
       bei LLM-Quelle
  5. Maszahmen-Plan (gerollupte Recommendations mit
     related_finding_ids + Aufwand)

Refactor: ein File AgentTestTab.tsx (519 LOC) -> 7 Files:
  _agentTypes.ts (Types + Methodik-Konstanten)
  AgentSpeedometer.tsx
  AgentMcCoverage.tsx
  AgentFindingCard.tsx
  AgentRecommendationCard.tsx
  AgentSlotCard.tsx
  AgentTestTab.tsx (Top-Level, schlank)

Plus Methodik-Info-Erklaerung am Tab-Anfang + Disclaimer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 07:53:24 +02:00

137 lines
4.5 KiB
TypeScript

'use client'
/**
* SlotCard — ein Slot im Agent-Test mit Sections:
* 1. Header (Slot-Name, duration, Vault-Link)
* 2. Was wurde geprüft (MC-Coverage, collapsible)
* 3. Speedometer
* 4. Eskalationslog (wenn vorhanden)
* 5. Findings (sortiert HIGH → LOW)
* 6. Recommendations (gerollupt)
*/
import React, { useState } from 'react'
import type { SlotOutput, Severity } from './_agentTypes'
import { AgentFindingCard } from './AgentFindingCard'
import { AgentMcCoverage } from './AgentMcCoverage'
import { AgentRecommendationCard } from './AgentRecommendationCard'
import { AgentSpeedometer } from './AgentSpeedometer'
const SEV_ORDER: Record<Severity, number> = {
HIGH: 0, MEDIUM: 1, LOW: 2, INFO: 3,
}
export function AgentSlotCard({
slot, output, runId,
}: {
slot: string
output: SlotOutput
runId: string
}) {
const [showAll, setShowAll] = useState(false)
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
const sortedFindings = [...output.findings].sort(
(a, b) => SEV_ORDER[a.severity] - SEV_ORDER[b.severity],
)
const visible = showAll ? sortedFindings : sortedFindings.slice(0, 12)
return (
<div className="rounded-lg border bg-white p-4 space-y-3 shadow-sm">
<div className="flex items-baseline gap-3 flex-wrap">
<h3 className="font-semibold text-gray-900">Slot: {slot}</h3>
<span className="text-xs text-gray-500">
{output.duration_ms} ms · Konfidenz {(output.confidence * 100).toFixed(0)}%
</span>
{wasSkipped && (
<span className="text-xs bg-amber-100 text-amber-800 px-2 py-0.5 rounded">
Dokument konnte nicht geladen werden
</span>
)}
{allGreen && (
<span className="text-xs bg-emerald-100 text-emerald-800 px-2 py-0.5 rounded">
Alle anwendbaren MCs erfüllt
</span>
)}
<a
className="text-xs text-blue-600 hover:underline ml-auto"
href={`/api/sdk/v1/specialist-agent/run/${runId}/artifacts`}
target="_blank"
rel="noreferrer"
>
Artefakte
</a>
</div>
{output.notes && (
<div className="text-xs text-amber-700 bg-amber-50 px-2 py-1 rounded">
Hinweis: {output.notes}
</div>
)}
<AgentMcCoverage coverage={output.mc_coverage} />
<AgentSpeedometer
total={output.mc_total}
ok={output.mc_ok}
na={output.mc_na}
high={output.mc_high}
medium={output.mc_medium}
low={output.mc_low}
/>
{output.escalation_log.length > 0 && (
<div className="text-xs text-gray-600 border-l-2 border-violet-400 pl-2 space-y-0.5">
<div className="font-semibold text-violet-700">
LLM-Eskalation eingesetzt:
</div>
{output.escalation_log.map((e, i) => (
<div key={i}>
{e.stage} <code className="text-violet-700">{e.model}</code>{' '}
· {e.duration_ms} ms{' '}
{e.tokens_in ? `· ${e.tokens_in}${e.tokens_out} tok` : ''}{' '}
{e.success ? '✓' : `${e.error || ''}`}
</div>
))}
</div>
)}
{sortedFindings.length > 0 && (
<div className="space-y-2">
<div className="text-xs font-semibold uppercase text-gray-700">
Findings ({sortedFindings.length}) nach Schwere sortiert
</div>
<div className="space-y-2">
{visible.map(f => (
<AgentFindingCard key={f.check_id} f={f} />
))}
</div>
{sortedFindings.length > 12 && (
<button
onClick={() => setShowAll(x => !x)}
className="text-xs text-blue-600 hover:underline"
>
{showAll ? 'Weniger anzeigen' : `Alle ${sortedFindings.length} anzeigen`}
</button>
)}
</div>
)}
{output.recommendations.length > 0 && (
<div className="space-y-2">
<div className="text-xs font-semibold uppercase text-gray-700">
Maßnahmen-Plan ({output.recommendations.length} konsolidiert)
</div>
<div className="space-y-2">
{output.recommendations.map(r => (
<AgentRecommendationCard key={r.recommendation_id} r={r} />
))}
</div>
</div>
)}
</div>
)
}