// EvidenceReport — a clean, printable one-page summary of an answer and the // evidence behind it. Rendered off-screen until the user triggers a print; we // flip a class on so a print-only stylesheet shows just the report. // No new deps — window.print() does the work. import { useEffect } from "react"; import { highlightMatch } from "../lib/highlight"; function fmtConfidence(c) { if (typeof c !== "number") return "—"; return `${Math.round(c * 100)}%`; } // One printable evidence row. Mirrors EvidencePanel's metadata line + excerpt, // but always expanded (a report shows everything) and ink-on-paper styled. function ReportEvidence({ citation, rank }) { const meta = [ citation.page != null ? `page ${citation.page}` : null, `chunk #${citation.chunk_index}`, citation.score != null ? `relevance ${citation.score}` : null, ] .filter(Boolean) .join(" · "); return (
{rank} {citation.document_name} {meta}
{highlightMatch(citation.excerpt, citation.match_text)}
); } export default function EvidenceReport({ result, question, onClose }) { // Trigger the browser print dialog once mounted, then notify the parent so it // can drop the report from the tree. afterprint covers both "Print" and // "Cancel" so we never get stuck in the print-only view. useEffect(() => { if (!result) return; const root = document.documentElement; root.classList.add("printing-report"); let finished = false; let fallback; // Runs exactly once: drop the print-only class and notify the parent so it // can unmount the report. Must ALWAYS fire (even if printing fails), or the // parent is left with a stuck report and a later re-export can't reopen. const done = () => { if (finished) return; finished = true; clearTimeout(fallback); window.removeEventListener("afterprint", done); root.classList.remove("printing-report"); onClose?.(); }; window.addEventListener("afterprint", done); // Let the report paint before opening the dialog. const t = setTimeout(() => { try { window.print(); } catch { done(); // print blocked/threw — don't leave the report stuck return; } // Safety net for environments where `afterprint` never fires (sandboxed // iframes / print-blocking browsers): clean up anyway. fallback = setTimeout(done, 3000); }, 60); return () => { clearTimeout(t); clearTimeout(fallback); window.removeEventListener("afterprint", done); root.classList.remove("printing-report"); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); if (!result) return null; const citations = result.citations || []; const meta = result.metadata || {}; const supported = meta.evidence_status === "supported" && !result.abstained; return (
FinanceFlow AI · Evidence Report
{new Date().toLocaleString()}
{supported ? "Grounded" : "Not enough info"}

Question

{question}

Answer

{fmtConfidence(meta.confidence)} confidence

{result.answer}

Mode: {result.mode || "—"} Sources cited: {citations.length} {meta.question_type && Type: {meta.question_type}} {typeof meta.top_score === "number" && ( Top match: {meta.top_score} )}

Evidence{citations.length > 0 ? ` (${citations.length})` : ""}

{citations.length > 0 ? ( citations.map((c, i) => ( )) ) : (

No supporting passages — the assistant abstained rather than guess.

)}
); }