import { Citation, PathEvidence, VisualGrounding } from "@/lib/api"; import { coverageTier, displaySource } from "@/lib/format"; const TIER_META = { strong: { label: "Strong evidence", cls: "evidence-strong", bar: "bg-ok", dot: "bg-ok", pill: "pill-ok" }, weak: { label: "Weak evidence", cls: "evidence-weak", bar: "bg-warn", dot: "bg-warn", pill: "pill-warn" }, none: { label: "Insufficient evidence", cls: "evidence-none", bar: "bg-bad", dot: "bg-bad", pill: "pill-bad" }, } as const; function CoverageHeader({ coverage }: { coverage: number }) { const pct = Math.round(Math.max(0, Math.min(1, coverage)) * 100); const tier = coverageTier(coverage); const m = TIER_META[tier]; return (
Evidence coverage {m.label}
{pct}%

Share of the question's key terms supported by retrieved evidence.

); } function Breakdown({ vector, graph, citations, }: { vector: number; graph: number; citations: number; }) { const items = [ { label: "Vector hits", value: vector, tint: "text-brand" }, { label: "Graph paths", value: graph, tint: "text-accent" }, { label: "Reranked", value: citations, tint: "text-brand2" }, { label: "Citations", value: citations, tint: "text-ok" }, ]; return (
{items.map((i) => (
{i.value}
{i.label}
))}
); } // "Why selected" copy by source type โ€” explains the evidence to the user. function whySelected(c: Citation): string { if (c.source_type === "audio") return "Top-ranked transcript segment matching your question."; if (c.source_type === "web") return "High-relevance passage from a retrieved web source."; if (c.method === "pathrag") return "Path-traversal evidence from the relational knowledge graph."; return "High-relevance passage from hybrid retrieval, kept after reranking."; } function cleanLocator(c: Citation): string { if (c.speaker || c.start_s != null) { const t = (s?: number | null) => { if (s == null) return ""; const m = Math.floor(s / 60); const sec = Math.round(s % 60); return `${m}:${String(sec).padStart(2, "0")}`; }; const range = c.start_s != null ? `${t(c.start_s)}โ€“${t(c.end_s)}` : ""; return [c.speaker, range].filter(Boolean).join(" ยท "); } if (c.page != null) return `Page ${c.page}`; return ""; } function ScoreDot({ score }: { score: number }) { const cls = score >= 0.7 ? "bg-ok" : score >= 0.4 ? "bg-warn" : "bg-bad"; const label = score >= 0.7 ? "High relevance" : score >= 0.4 ? "Medium relevance" : "Low relevance"; return ( ); } function MethodPill({ method }: { method: string }) { const cls = method === "pathrag" ? "border-accent/40 text-accent" : method === "hybrid" ? "border-brand/40 text-brand" : "border-edge text-fg3"; return {method}; } function CitationCard({ c, onOpenSource, hasVisualGrounding }: { c: Citation; onOpenSource?: (citationId: string) => void; hasVisualGrounding?: boolean; }) { const loc = cleanLocator(c); const hasScore = c.score != null && c.score > 0; const hasMethod = c.method != null && c.method !== "unknown"; return (
  • {c.marker}
    {displaySource(c.source)}
    {hasScore && } {hasMethod && } {c.source_type}
    {loc &&

    {loc}

    } {hasScore && (
    = 0.7 ? "bg-ok" : c.score! >= 0.4 ? "bg-warn" : "bg-bad"}`} style={{ width: `${Math.round(c.score! * 100)}%` }} />
    {Math.round(c.score! * 100)}% relevance
    )}

    {whySelected(c)}

    {onOpenSource && ( )}
    {c.source && c.source !== displaySource(c.source) && (
    Debug ยท raw source

    {c.source}

    )}
  • ); } function Section({ title, count, children, open = true, }: { title: string; count?: number; children: React.ReactNode; open?: boolean; }) { return (
    {title} {typeof count === "number" && ( {count} )}
    {children}
    ); } export function EvidencePaths({ paths, seeds, coverage = 0, citations = [], visualGrounding, onOpenSource, onOpenSourceTab, }: { paths: PathEvidence[]; seeds: string[]; coverage?: number; citations?: Citation[]; visualGrounding?: VisualGrounding | null; onOpenSource?: (citationId: string) => void; onOpenSourceTab?: () => void; }) { const hasAnything = paths?.length || citations?.length || seeds?.length; if (!hasAnything) return (
    ๐Ÿ”

    Ask a question to see the evidence trail.

    Vector hits, PathRAG graph paths, and the exact citations an answer was grounded on appear here โ€” with a coverage score and per-source reasoning.

    ); const vgAvailable = visualGrounding?.visual_grounding_available; const vgStage = visualGrounding?.grounding_stage ?? "unavailable"; return (
    {/* Visual grounding status bar */} {visualGrounding !== undefined && visualGrounding !== null && (
    Visual grounding {vgStage}
    {vgAvailable && onOpenSourceTab && ( )} {!vgAvailable && ( Reindex to enable )}
    )}
    0}> {citations.length ? (
      {citations.map((c) => ( ))}
    ) : (

    No citations attached to the answer.

    )}
    {seeds?.length > 0 && (
    seeds: {seeds.map((s) => ( {s} ))}
    )} {paths?.length ? (
    {paths.map((p, i) => { const hasPpr = p.ppr_score != null && p.ppr_score > 0; const relColor = p.reliability >= 0.7 ? "bg-ok" : p.reliability >= 0.4 ? "bg-accent" : "bg-warn"; return (
    {/* Chain: Node โ†’ relation โ†’ Node โ†’ ... */}
    {p.nodes.map((n, j) => ( {n} {j < p.relations.length && ( โ€” {p.relations[j]} โ†’ )} ))}
    {/* Reliability bar + PPR authority badge */}
    rel {p.reliability.toFixed(2)} {hasPpr && ( ppr {p.ppr_score!.toFixed(2)} )}
    ); })}
    ) : (

    No graph paths โ€” this was answered by vector retrieval alone.

    )}

    Passages are ranked by hybrid (dense + sparse) retrieval, expanded along reliable graph relations, reranked, then only the cited sources are kept.

    ); }