/* ============================================ INGESTION DASHBOARD — Upload + live polling ============================================ */ function IngestionScreen({ onNavigate }) { const [drag, setDrag] = useState(false); const [tasks, setTasks] = useState([]); // [{task_id, filename, status, processed_pages, total_pages, ...}] const [activeTaskId, setActiveTaskId] = useState(localStorage.getItem(window.API.TASK_KEY)); const [uploading, setUploading] = useState(false); const [error, setError] = useState(null); const fileInputRef = useRef(null); // Resume polling on mount if there's a task_id in localStorage. useEffect(() => { if (!activeTaskId) return; let stop = false; const tick = async () => { try { const data = await window.API.status(activeTaskId); if (stop) return; setTasks((prev) => { const i = prev.findIndex((t) => t.task_id === activeTaskId); if (i === -1) return [data, ...prev]; const next = [...prev]; next[i] = data; return next; }); if (data.status === "COMPLETED" || data.status === "FAILED") { localStorage.removeItem(window.API.TASK_KEY); setActiveTaskId(null); } else { setTimeout(tick, 3000); } } catch (err) { // 404 or 401 — give up on this task if (err.status === 404 || err.status === 401) { localStorage.removeItem(window.API.TASK_KEY); setActiveTaskId(null); } else if (!stop) { setTimeout(tick, 5000); } } }; tick(); return () => { stop = true; }; }, [activeTaskId]); const onPick = async (file) => { if (!file) return; if (!file.name.toLowerCase().endsWith(".pdf")) { setError("Only PDF files are accepted."); return; } setError(null); setUploading(true); try { const res = await window.API.upload(file); localStorage.setItem(window.API.TASK_KEY, res.task_id); setTasks((prev) => [ { task_id: res.task_id, filename: res.filename, status: "QUEUED", processed_pages: 0, total_pages: null }, ...prev, ]); setActiveTaskId(res.task_id); } catch (err) { setError(err.message || "Upload failed."); } finally { setUploading(false); } }; const onDropFile = (e) => { e.preventDefault(); setDrag(false); onPick(e.dataTransfer.files?.[0]); }; const stats = { queued: tasks.filter((t) => t.status === "QUEUED").length, processing: tasks.filter((t) => t.status === "PROCESSING").length, failed: tasks.filter((t) => t.status === "FAILED").length, completed: tasks.filter((t) => t.status === "COMPLETED").length, }; return ( <>

Ingestion Dashboard

Upload a PDF — progress polled every 3 seconds.

{/* Status strip — counts from this session only (no /api/tasks list yet) */}
} /> } accent="var(--navy-700)" /> } accent="var(--status-danger)" /> } accent="var(--status-ok)" />
{/* Upload zone */} { onPick(e.target.files?.[0]); e.target.value = ""; }} />
{ e.preventDefault(); setDrag(true); }} onDragLeave={() => setDrag(false)} onDrop={onDropFile} onClick={() => !uploading && fileInputRef.current?.click()} style={{ border: `2px dashed ${drag ? "var(--orange-600)" : "var(--orange-500)"}`, background: drag ? "var(--orange-100)" : "var(--orange-50)", borderRadius: 8, padding: "32px 24px", textAlign: "center", marginBottom: 20, transition: "all 0.15s", cursor: uploading ? "wait" : "pointer", opacity: uploading ? 0.7 : 1, }} >

{uploading ? "Uploading…" : "Drop a PDF here to ingest"}

PDF only · max 500 MB · machine-readable (no OCR in v1)

{error && (
{error}
)}
{/* Processing queue table */} {tasks.length > 0 && ( {tasks.map((r) => { const pct = r.total_pages && r.total_pages > 0 ? Math.min(100, Math.round((r.processed_pages / r.total_pages) * 100)) : (r.status === "COMPLETED" ? 100 : 0); const kind = r.status === "COMPLETED" ? "complete" : r.status === "FAILED" ? "failed" : r.status === "PROCESSING" ? "processing" : "pending"; return ( ); })}
Document Status Progress Pages
{r.filename}
{r.task_id.slice(0, 8)}
{r.status === "COMPLETED" ? "Completed" : r.status === "FAILED" ? "Failed" : r.status === "PROCESSING" ? "Processing" : "Queued"}
{r.status === "FAILED" ? (r.error_message || "Failed") : r.status === "COMPLETED" ? "Indexed" : r.status === "PROCESSING" ? "Embedding chunks…" : "Waiting for worker"}
{r.processed_pages || 0}{r.total_pages ? ` / ${r.total_pages}` : ""}
)}
); } function StatCard({ label, value, sub, icon, accent }) { return (
{label} {icon}
{value}
{sub}
); } window.IngestionScreen = IngestionScreen; window.StatCard = StatCard;