import { useState, useEffect, useRef, useCallback } from "react"; const API = "/api"; /* ═══════════════════════════════════════════════════════════════════════════ */ /* ICONS */ /* ═══════════════════════════════════════════════════════════════════════════ */ const Shield = ({ className = "" }) => ( ); const Search = () => ( ); const Check = () => ( ); const XIcon = () => ( ); const ChevDown = () => ( ); const Zap = () => ( ); const Upload = () => ( ); const FileText = () => ( ); const Trash = () => ( ); const EXAMPLES = [ "When was Python released and who created it?", "What caused World War I?", "Tell me about artificial intelligence history.", "How does the human body work?", "What is climate change and what causes it?", "Tell me about the Renaissance period.", "How did the internet develop?", "Tell me about quantum physics.", ]; /* ═══════════════════════════════════════════════════════════════════════════ */ /* SMALL COMPONENTS */ /* ═══════════════════════════════════════════════════════════════════════════ */ function Metric({ label, value, color = "text-indigo-400" }) { return (

{value}

{label}

); } function ClaimCard({ claim }) { const [open, setOpen] = useState(false); const ok = claim.is_supported; return (
setOpen(!open)}>
{ok ? : }

{claim.text}

Similarity: {(claim.similarity_score * 100).toFixed(1)}% | Entailment: {claim.entailment_label}
{open && claim.best_evidence && (

Best Evidence

{claim.best_evidence}
)}
); } function EvidenceCard({ ev, idx }) { return (
#{idx + 1} — {ev.source} Score: {(ev.similarity_score * 100).toFixed(1)}%

{ev.content}

); } function ResponseRenderer({ text }) { // Detect comparison responses and render as table if (text.startsWith("Comparison between")) { return ; } // Detect list responses (filter results like "Students with % greater than...") if (/^\d+\s+students\s+have|^Students with .* found\):/i.test(text) || /^\s*\d+\.\s+/m.test(text)) { return ; } // Detect detail responses ("Details for X:" or "For X:") if (/^(Details for|For )\S/i.test(text)) { return ; } // Default: plain text with line breaks return

{text}

; } function ComparisonTable({ text }) { const lines = text.split("\n").filter(l => l.trim()); // First line: "Comparison between X and Y:" const header = lines[0]; const nameMatch = header.match(/Comparison between (.+?) and (.+?):/); const name1 = nameMatch?.[1] || "Student 1"; const name2 = nameMatch?.[2] || "Student 2"; // Parse rows: " Subject: val1 vs val2 marker" const rows = []; let summary = []; for (let i = 1; i < lines.length; i++) { const vsMatch = lines[i].match(/^\s*(.+?):\s*([\d.]+)\s+vs\s+([\d.]+)\s*(.*)/); if (vsMatch) { const [, subject, v1, v2, marker] = vsMatch; rows.push({ subject: subject.trim(), v1: parseFloat(v1), v2: parseFloat(v2), marker: marker.trim() }); } else if (!lines[i].match(/^Comparison between/)) { summary.push(lines[i].trim()); } } return (
{rows.map((r, i) => { const diff = r.v1 - r.v2; const cls1 = diff > 0 ? "text-emerald-400 font-semibold" : diff < 0 ? "text-red-400" : "text-slate-300"; const cls2 = diff < 0 ? "text-emerald-400 font-semibold" : diff > 0 ? "text-red-400" : "text-slate-300"; const badge = diff > 0 ? +{diff.toFixed(1)} : diff < 0 ? {diff.toFixed(1)} : Equal; return ( ); })}
Subject {name1} {name2} Result
{r.subject} {r.v1} {r.v2} {badge}
{summary.length > 0 && (
{summary.map((s, i) =>

{s}

)}
)}
); } function ListResponse({ text }) { const lines = text.split("\n").filter(l => l.trim()); const header = lines[0]; const items = lines.slice(1).filter(l => /^\s*\d+\./.test(l)); const rest = lines.slice(1).filter(l => !/^\s*\d+\./.test(l)); return (

{header}

{items.length > 0 && (
{items.map((item, i) => { const m = item.match(/^\s*(\d+)\.\s+(.+?)\s*[—–-]\s*([\d.]+)/); if (!m) return null; return ( ); })}
# Name Value
{m[1]} {m[2].trim()} {m[3]}
)} {rest.map((r, i) =>

{r}

)}
); } function DetailTable({ text }) { const lines = text.split("\n").filter(l => l.trim()); const header = lines[0]; const items = lines.slice(1).filter(l => l.includes(":")); return (

{header}

{items.length > 0 && (
{items.map((item, i) => { const m = item.match(/^\s*-?\s*(.+?):\s*(.+)/); if (!m) return null; return ( ); })}
{m[1].trim()} {m[2].trim()}
)}
); } /* ═══════════════════════════════════════════════════════════════════════════ */ /* TAB: UPLOAD DOCUMENTS */ /* ═══════════════════════════════════════════════════════════════════════════ */ function UploadTab({ onStatusChange, onSwitchToQuery }) { const [uploading, setUploading] = useState(false); const [uploadedFiles, setUploadedFiles] = useState([]); const [dragOver, setDragOver] = useState(false); const [error, setError] = useState(null); const fileInput = useRef(null); // Load already-uploaded files on mount useEffect(() => { fetch(`${API}/status`).then(r => r.json()).then(d => { setUploadedFiles(d.uploaded_files.map(f => ({ name: f, status: "done" }))); }).catch(() => {}); }, []); const uploadFile = async (file) => { setError(null); const ext = file.name.split(".").pop().toLowerCase(); if (!["txt", "pdf", "docx", "xlsx", "xls", "csv"].includes(ext)) { setError(`Unsupported file: .${ext}. Use .txt, .pdf, .docx, .xlsx, .xls, or .csv`); return; } setUploading(true); setUploadedFiles(prev => [...prev, { name: file.name, status: "uploading" }]); const form = new FormData(); form.append("file", file); try { const res = await fetch(`${API}/upload`, { method: "POST", body: form }); if (!res.ok) { const errData = await res.json(); throw new Error(errData.detail || "Upload failed"); } const data = await res.json(); setUploadedFiles(prev => prev.map(f => f.name === file.name ? { ...f, status: "done", chunks: data.chunks_added } : f) ); onStatusChange?.(); // Auto-switch to query tab after successful upload setTimeout(() => onSwitchToQuery?.(), 1000); } catch (e) { setError(e.message); setUploadedFiles(prev => prev.filter(f => f.name !== file.name || f.status !== "uploading")); } setUploading(false); }; const handleFiles = (files) => { Array.from(files).forEach(uploadFile); }; const handleDrop = (e) => { e.preventDefault(); setDragOver(false); handleFiles(e.dataTransfer.files); }; const clearAll = async () => { if (!confirm("Delete all uploaded files?")) return; try { await fetch(`${API}/clear-uploads`, { method: "POST" }); setUploadedFiles([]); onStatusChange?.(); } catch (e) { console.error(e); } }; const deleteFile = async (filename) => { if (!confirm(`Delete "${filename}"?`)) return; try { const res = await fetch(`${API}/delete-file`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ filename }), }); if (res.ok) { setUploadedFiles(prev => prev.filter(f => f.name !== filename)); onStatusChange?.(); } } catch (e) { console.error(e); } }; const doneFiles = uploadedFiles.filter(f => f.status === "done"); return (
{/* Drop zone */}
{ e.preventDefault(); setDragOver(true); }} onDragLeave={() => setDragOver(false)} onDrop={handleDrop} onClick={() => fileInput.current?.click()} > handleFiles(e.target.files)} />

{dragOver ? "Drop files here" : "Upload your documents"}

Drag & drop or click to browse — TXT, PDF, DOCX, Excel, CSV

{uploading && (
Processing...
)}
{error && (
{error}
)} {/* Uploaded files list */} {doneFiles.length > 0 && (

Uploaded Documents ({doneFiles.length})

{doneFiles.map((f, i) => (

{f.name}

{f.chunks ? `${f.chunks} chunks extracted` : "Processed"}

))}
)} {/* Hint */}

How it works:

  1. Upload any document (TXT, PDF, DOCX, Excel, or CSV)
  2. The system extracts text and splits it into searchable chunks
  3. Switch to the Query tab and ask questions about your document
  4. Every claim in the answer is verified against your uploaded content
); } /* ═══════════════════════════════════════════════════════════════════════════ */ /* TAB: QUERY */ /* ═══════════════════════════════════════════════════════════════════════════ */ function QueryTab({ chunkCount }) { const [query, setQuery] = useState(""); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const [showEvidence, setShowEvidence] = useState(false); const run = async (q) => { const text = q || query; if (!text.trim()) return; setQuery(text); setLoading(true); setResult(null); try { const res = await fetch(`${API}/query`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: text }), }); setResult(await res.json()); } catch (e) { console.error(e); } setLoading(false); }; return (
{/* No docs warning */} {chunkCount === 0 && (
No documents loaded. Go to Upload tab to add documents first.
)} {/* Search bar */}
setQuery(e.target.value)} onKeyDown={(e) => e.key === "Enter" && run()} />
{/* Example chips */}
{EXAMPLES.slice(0, 5).map((ex) => ( ))}
{/* Loading */} {loading && (

Running VDHF pipeline...

)} {/* Results */} {result && !loading && (
{/* Status banner */}
{result.is_verified ? "✅" : result.supported_claims === 0 ? "❌" : "⚠️"}

{result.is_verified ? "Verified Response" : result.supported_claims === 0 ? "Hallucinated Response" : "Partially Verified"}

{result.supported_claims}/{result.total_claims} claims supported

{(result.support_ratio * 100).toFixed(0)}%

support ratio

{/* Metrics */}
{/* Response */}

Response

{result.elapsed_seconds}s
{/* Progress bar */}
Verification Progress {result.supported_claims}/{result.total_claims}
{/* Prompt Refinement Suggestion */} {!result.is_verified && result.total_claims > 0 && (

Prompt Refinement Suggested

The response could not be fully verified. Try refining your query to get better results:

{result.claims.filter(c => !c.is_supported).slice(0, 3).map((c, i) => (
Unverified: {c.text.length > 120 ? c.text.slice(0, 118) + "..." : c.text}
))}
)} {/* Claims */} {result.claims.length > 0 && (

Claims Breakdown

{result.claims.map((c, i) => )}
)} {/* Evidence */} {result.evidence.length > 0 && (
{showEvidence && (
{result.evidence.map((ev, i) => )}
)}
)}
)}
); } /* ═══════════════════════════════════════════════════════════════════════════ */ /* TAB: VERIFY CLAIMS */ /* ═══════════════════════════════════════════════════════════════════════════ */ function VerifyTab() { const [text, setText] = useState(""); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const verify = async () => { const lines = text.split("\n").map((l) => l.trim()).filter(Boolean); if (!lines.length) return; setLoading(true); setResult(null); try { const res = await fetch(`${API}/verify`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ claims: lines }), }); setResult(await res.json()); } catch (e) { console.error(e); } setLoading(false); }; return (