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>
This commit is contained in:
@@ -2,21 +2,21 @@
|
||||
|
||||
import { useState } from 'react'
|
||||
import { ChevronDown, ChevronRight, Library } from 'lucide-react'
|
||||
import type { KnowledgeUnit } from '@/lib/sdk/advisor/evidence'
|
||||
import type { EvidenceUnit } from '@/lib/sdk/advisor/contract'
|
||||
import { resolveRegulation } from '@/lib/sdk/advisor/regulation-display'
|
||||
import { KnowledgeUnitCard } from './KnowledgeUnitCard'
|
||||
import { EvidenceUnitCard } from './EvidenceUnitCard'
|
||||
import { PaneHeader } from './PaneHeader'
|
||||
|
||||
interface EvidenceGroupData {
|
||||
interface Group {
|
||||
key: string
|
||||
label: string
|
||||
units: KnowledgeUnit[]
|
||||
units: EvidenceUnit[]
|
||||
}
|
||||
|
||||
function groupByFamily(sources: KnowledgeUnit[]): EvidenceGroupData[] {
|
||||
const map = new Map<string, EvidenceGroupData>()
|
||||
for (const u of sources) {
|
||||
const d = resolveRegulation(u.regulation)
|
||||
function groupByFamily(units: EvidenceUnit[]): Group[] {
|
||||
const map = new Map<string, Group>()
|
||||
for (const u of units) {
|
||||
const d = resolveRegulation({ code: u.document, short: u.document })
|
||||
const g = map.get(d.familyKey) ?? { key: d.familyKey, label: d.familyLabel, units: [] }
|
||||
g.units.push(u)
|
||||
map.set(d.familyKey, g)
|
||||
@@ -24,7 +24,7 @@ function groupByFamily(sources: KnowledgeUnit[]): EvidenceGroupData[] {
|
||||
return [...map.values()].sort((a, b) => b.units.length - a.units.length)
|
||||
}
|
||||
|
||||
function EvidenceGroup({ group }: { group: EvidenceGroupData }) {
|
||||
function EvidenceGroup({ group, highlightedId }: { group: Group; highlightedId?: string }) {
|
||||
const [open, setOpen] = useState(group.units.length <= 3)
|
||||
return (
|
||||
<div className="rounded-lg border border-gray-200 bg-white">
|
||||
@@ -42,7 +42,7 @@ function EvidenceGroup({ group }: { group: EvidenceGroupData }) {
|
||||
{open && (
|
||||
<div className="space-y-1 border-t border-gray-100 px-2 py-2">
|
||||
{group.units.map((u) => (
|
||||
<KnowledgeUnitCard key={u.id} unit={u} compact />
|
||||
<EvidenceUnitCard key={u.evidence_id} unit={u} compact highlighted={u.evidence_id === highlightedId} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -50,22 +50,24 @@ function EvidenceGroup({ group }: { group: EvidenceGroupData }) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Evidence pane — retrieved units grouped by document/regulation family, count + expandable. */
|
||||
export function EvidencePane({ sources }: { sources: KnowledgeUnit[] }) {
|
||||
const groups = groupByFamily(sources)
|
||||
/** Evidence pane — units grouped by document/regulation family, count + expandable. */
|
||||
export function EvidencePane({
|
||||
evidence,
|
||||
highlightedId,
|
||||
}: {
|
||||
evidence: EvidenceUnit[]
|
||||
highlightedId?: string
|
||||
}) {
|
||||
const groups = groupByFamily(evidence)
|
||||
return (
|
||||
<section>
|
||||
<PaneHeader
|
||||
icon={<Library className="h-3.5 w-3.5 text-gray-500" />}
|
||||
title="Evidence"
|
||||
count={sources.length}
|
||||
/>
|
||||
<PaneHeader icon={<Library className="h-3.5 w-3.5 text-gray-500" />} title="Evidence" count={evidence.length} />
|
||||
{groups.length === 0 ? (
|
||||
<p className="px-1 text-[11px] text-gray-400">Keine strukturierte Evidence zu dieser Antwort.</p>
|
||||
) : (
|
||||
<div className="space-y-1.5">
|
||||
{groups.map((g) => (
|
||||
<EvidenceGroup key={g.key} group={g} />
|
||||
<EvidenceGroup key={g.key} group={g} highlightedId={highlightedId} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user