Files
breakpilot-compliance/admin-compliance/components/sdk/advisor/EvidenceUnitCard.tsx
T
Benjamin Admin f9b7ba2424 feat(advisor): v3 Clarity Gate — Case model + clarify/answer contract, [n] citations
Builds the FE against the SDK<->FE Clarity-Gate contract (board 2026-07-01 /
advisor-clarity-gate-contract). The advisor is now a CASE, not a chat:
- Request {question, context?}; response {mode: clarify|answer, clarity, general_answer,
  answer, evidence, citations, visual_evidence, footnotes}.
- clarify mode: short L1 general answer (marked "allgemeine Definition, ohne Rechtsquelle")
  + domain context chips; picking a chip re-runs the case scoped (-> answer).
- answer mode: markdown answer with clickable [n] citation markers coupled to evidence
  cards (highlight + scroll), evidence grouped by document family, visual_evidence
  (visual_type), footnotes, honest summary counts (no trust score).
- FE never parses the answer for structure — only the deliberate [n] markers, mapped via
  citations[]. New: contract.ts, useAdvisorCase, useCitationHighlight, ClarifyView,
  EvidenceUnitCard, VisualEvidencePane, CaseView. Removed the v2 stream/chat components.

NOT deployed: FE shape-switch (JSON modes) must deploy TOGETHER with the SDK endpoint
delivering the contract (board deploy-coupling). Proxy/route.ts unchanged (SDK-owned).
tsc clean, 16 vitest (incl. clarify+answer fixtures), check-loc 0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-01 11:31:28 +02:00

81 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState } from 'react'
import { ChevronDown, ChevronRight, ExternalLink } from 'lucide-react'
import type { EvidenceUnit } from '@/lib/sdk/advisor/contract'
import { resolveRegulation } from '@/lib/sdk/advisor/regulation-display'
/** One evidence unit (contract shape). Compact inside a document group: chapter/section only. */
export function EvidenceUnitCard({
unit,
compact,
highlighted,
}: {
unit: EvidenceUnit
compact?: boolean
highlighted?: boolean
}) {
const [open, setOpen] = useState(false)
const d = resolveRegulation({ code: unit.document, short: unit.document })
const crumbs = [unit.section, unit.paragraph].filter((x): x is string => Boolean(x))
const canOpen = !!unit.url && /^https?:\/\//i.test(unit.url)
const header = compact ? (d.chapter ? `Kapitel ${d.chapter}` : crumbs[0] || d.familyLabel) : d.familyLabel
const sub = compact && !d.chapter && crumbs.length ? crumbs.slice(1) : crumbs
return (
<div
id={`ev-${unit.evidence_id}`}
className={`${
compact
? 'rounded-md border border-gray-100 bg-gray-50 p-2'
: 'rounded-lg border border-gray-200 bg-white p-2.5'
} ${highlighted ? 'ring-2 ring-indigo-400' : ''}`}
>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<div className="truncate text-xs font-semibold text-gray-900">{header}</div>
{sub.length > 0 && (
<div className="mt-0.5 flex flex-wrap items-center gap-x-1 text-[11px] text-gray-500">
{sub.map((c, i) => (
<span key={i} className="flex items-center gap-1">
{i > 0 && <span className="text-gray-300"></span>}
{c}
</span>
))}
</div>
)}
</div>
{canOpen && (
<a
href={unit.url}
target="_blank"
rel="noopener noreferrer"
className="flex flex-shrink-0 items-center gap-0.5 rounded px-1.5 py-0.5 text-[11px] font-medium text-indigo-600 hover:bg-indigo-50"
>
<ExternalLink className="h-3 w-3" />
öffnen
</a>
)}
</div>
{unit.snippet && (
<div className="mt-1.5">
<button
type="button"
onClick={() => setOpen((v) => !v)}
className="flex items-center gap-0.5 text-[11px] text-gray-400 hover:text-gray-600"
>
{open ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}
Textauszug
</button>
{open && (
<p className="mt-1 border-l-2 border-gray-200 pl-2 text-[11px] italic text-gray-500">
{unit.snippet}
</p>
)}
</div>
)}
</div>
)
}