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>
53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
'use client'
|
|
|
|
import { Info } from 'lucide-react'
|
|
import type { AdvisorResponse } from '@/lib/sdk/advisor/contract'
|
|
import { Markdown } from './Markdown'
|
|
|
|
/**
|
|
* Clarify mode: a short general (L1) definition — explicitly marked as general, no legal source —
|
|
* plus domain context chips. Picking a chip re-runs the case scoped to that domain (-> L2).
|
|
*/
|
|
export function ClarifyView({
|
|
response,
|
|
onSelectContext,
|
|
busy,
|
|
}: {
|
|
response: AdvisorResponse
|
|
onSelectContext: (id: string) => void
|
|
busy: boolean
|
|
}) {
|
|
const chips = response.clarity.suggested_contexts ?? []
|
|
return (
|
|
<div className="space-y-3">
|
|
<div className="rounded-lg border border-amber-200 bg-amber-50 px-3 py-2">
|
|
<div className="mb-1 flex items-center gap-1 text-[11px] font-semibold text-amber-700">
|
|
<Info className="h-3.5 w-3.5" />
|
|
Allgemeine Definition (ohne Rechtsquelle)
|
|
</div>
|
|
<Markdown content={response.general_answer || ''} />
|
|
</div>
|
|
{chips.length > 0 && (
|
|
<div>
|
|
<div className="mb-1.5 text-xs font-medium text-gray-700">
|
|
Meintest du einen bestimmten Kontext?
|
|
</div>
|
|
<div className="flex flex-wrap gap-1.5">
|
|
{chips.map((c) => (
|
|
<button
|
|
key={c.id}
|
|
type="button"
|
|
disabled={busy}
|
|
onClick={() => onSelectContext(c.id)}
|
|
className="rounded-full border border-indigo-200 bg-white px-3 py-1 text-xs font-medium text-indigo-700 transition-colors hover:bg-indigo-50 disabled:opacity-50"
|
|
>
|
|
{c.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|