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
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>
137 lines
4.5 KiB
TypeScript
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>
|
|
)
|
|
}
|