f9b7ba2424
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>
76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
'use client'
|
|
|
|
import type { AdvisorResponse } from '@/lib/sdk/advisor/contract'
|
|
import type { AdvisorCase } from './useAdvisorCase'
|
|
import { ClarifyView } from './ClarifyView'
|
|
import { EvidenceSummary } from './EvidenceSummary'
|
|
import { EvidencePane } from './EvidencePane'
|
|
import { VisualEvidencePane } from './VisualEvidencePane'
|
|
import { FootnotesPane } from './FootnotesPane'
|
|
import { Markdown } from './Markdown'
|
|
import { useCitationHighlight } from './useCitationHighlight'
|
|
|
|
export function LoadingDots() {
|
|
return (
|
|
<div className="flex space-x-1 px-1 py-2" aria-label="Antwort wird erstellt">
|
|
<span className="h-2 w-2 animate-bounce rounded-full bg-gray-400" />
|
|
<span className="h-2 w-2 animate-bounce rounded-full bg-gray-400" style={{ animationDelay: '0.1s' }} />
|
|
<span className="h-2 w-2 animate-bounce rounded-full bg-gray-400" style={{ animationDelay: '0.2s' }} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function ErrorBox({ msg }: { msg?: string }) {
|
|
return (
|
|
<div className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">
|
|
{msg || 'Verbindung fehlgeschlagen'}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/** Answer mode body (stacked): summary + answer (with [n] coupling) + evidence/visual/footnotes. */
|
|
export function AnswerBody({ response }: { response: AdvisorResponse }) {
|
|
const { highlightedId, cite } = useCitationHighlight(response.citations)
|
|
return (
|
|
<div className="space-y-3">
|
|
<EvidenceSummary response={response} />
|
|
<div className="rounded-lg border border-gray-200 bg-white px-3 py-2">
|
|
<Markdown content={response.answer || ''} citations={cite} />
|
|
</div>
|
|
<EvidencePane evidence={response.evidence} highlightedId={highlightedId} />
|
|
<VisualEvidencePane items={response.visual_evidence} />
|
|
<FootnotesPane footnotes={response.footnotes} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/** One case rendered stacked (narrow mode). Clarify -> L1 + chips; answer -> full evidence body. */
|
|
export function CaseView({
|
|
c,
|
|
onSelectContext,
|
|
busy,
|
|
showQuestion,
|
|
}: {
|
|
c: AdvisorCase
|
|
onSelectContext: (ctx: string) => void
|
|
busy: boolean
|
|
showQuestion?: boolean
|
|
}) {
|
|
const r = c.response
|
|
return (
|
|
<div className="space-y-2 border-b border-gray-100 pb-4 last:border-0">
|
|
{showQuestion && (
|
|
<div className="text-xs text-gray-500">
|
|
<span className="font-medium text-gray-400">Frage:</span> {c.question}
|
|
</div>
|
|
)}
|
|
{c.status === 'loading' && <LoadingDots />}
|
|
{c.status === 'error' && <ErrorBox msg={c.error} />}
|
|
{r && r.mode === 'clarify' && (
|
|
<ClarifyView response={r} onSelectContext={onSelectContext} busy={busy} />
|
|
)}
|
|
{r && r.mode === 'answer' && <AnswerBody response={r} />}
|
|
</div>
|
|
)
|
|
}
|