import React, { useEffect, useMemo, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import { Activity, BarChart3, CheckCircle2, ChevronRight, Clock3, Download, FileChartColumn, FileText, Layers3, LoaderCircle, RefreshCcw, Search, ShieldCheck, Sparkles, Upload, X, AlertTriangle } from "lucide-react";
import "./styles.css";
const API = "";
const MODELS = ["xgboost", "logistic_regression", "random_forest", "linear_svm", "sentence_embedding_lr"];
async function jsonRequest(url, options) {
const response = await fetch(`${API}${url}`, options);
const body = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(body.detail || body.error || `Request failed (${response.status})`);
return body;
}
function App() {
const [view, setView] = useState("analysis");
const [inputMode, setInputMode] = useState("upload");
const [samples, setSamples] = useState([]);
const [sampleId, setSampleId] = useState("");
const [file, setFile] = useState(null);
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [batchFiles, setBatchFiles] = useState([]);
const [batchResults, setBatchResults] = useState([]);
const [analyticsModel, setAnalyticsModel] = useState("xgboost");
const [analytics, setAnalytics] = useState(null);
const fileInput = useRef(null);
useEffect(() => { jsonRequest("/api/samples").then(setSamples).catch((e) => setError(e.message)); }, []);
useEffect(() => { if (view === "analytics") jsonRequest(`/api/analytics?model=${analyticsModel}`).then(setAnalytics).catch((e) => setError(e.message)); }, [view, analyticsModel]);
const activeSample = samples.find((item) => item.id === sampleId);
const previewUrl = file ? URL.createObjectURL(file) : sampleId ? `/api/samples/${sampleId}/preview` : "";
async function analyze() {
setError(""); setResult(null); setLoading(true);
try {
if (inputMode === "upload") {
if (!file) throw new Error("Choose a PNG, JPG, JPEG, or PDF document first.");
const data = new FormData(); data.append("file", file);
setResult(await jsonRequest("/api/process", { method: "POST", body: data }));
} else {
if (!sampleId) throw new Error("Select a sample document first.");
setResult(await jsonRequest(`/api/process-sample/${sampleId}`, { method: "POST" }));
}
} catch (e) { setError(e.message); } finally { setLoading(false); }
}
async function runBatch() {
if (!batchFiles.length) return setError("Choose at least one document for batch processing.");
setError(""); setLoading(true);
try { const data = new FormData(); batchFiles.forEach((item) => data.append("files", item)); setBatchResults(await jsonRequest("/api/process-batch", { method: "POST", body: data })); }
catch (e) { setError(e.message); } finally { setLoading(false); }
}
function resetAnalysis() { setFile(null); setSampleId(""); setResult(null); setError(""); }
return
Workspace
} label="Document analysis" active={view === "analysis"} onClick={() => setView("analysis")}/>
} label="Batch processing" active={view === "batch"} onClick={() => setView("batch")}/>
} label="Model analytics" active={view === "analytics"} onClick={() => setView("analytics")}/>
{error && {error} setError("")} aria-label="Dismiss"> }
{view === "analysis" && }
{view === "batch" && }
{view === "analytics" && }
;
}
function NavButton({ icon, label, active, onClick }) { return {icon}{label} {active && } ; }
function AnalysisView({ inputMode, setInputMode, samples, sampleId, setSampleId, activeSample, file, setFile, previewUrl, result, loading, analyze, resetAnalysis, fileInput }) {
return <>
Reset}/>
setInputMode("upload")}> Upload document setInputMode("sample")}> Use sample
{inputMode === "sample" && setSampleId(e.target.value)}>Select a sample document {samples.map((item) => {item.label} )} }
Input document
{file?.name || activeSample?.label || "No document selected"} {(file || activeSample) &&
Ready }
{inputMode === "upload" && fileInput.current?.click()} onDragOver={(e) => e.preventDefault()} onDrop={(e) => { e.preventDefault(); setFile(e.dataTransfer.files[0]); }}> setFile(e.target.files[0])}/>{file ? "Replace document" : "Choose a document"} PNG, JPG, JPEG, or PDF · maximum 10 MB
}
{inputMode === "sample" && {activeSample?.label || "Choose a sample above"} {activeSample?.description || "Use a curated document to check the pipeline without uploading a file."}
}
{previewUrl && (file?.type?.startsWith("image/") || activeSample) ? : previewUrl ? PDF document selected
: Preview appears here after selecting a document
}
{loading ? <> Analyzing document...> : <> Analyze document>}
>;
}
function ResultsPanel({ result, loading }) {
if (loading) return Processing document OCR, layout classification, and field extraction are running.
;
if (!result) return Analysis output Ready for processing Select a document and run the pipeline to see classification, extracted fields, and routing confidence.
;
const cls = result.doc_class.replaceAll("_", " ");
return Analysis output
Document decision {result.needs_review ? "Review" : "Accepted"} Predicted class {cls}
Class confidence {(result.class_confidence * 100).toFixed(1)}%
} label="OCR words" value={result.ocr_token_count}/>} label="Latency" value={`${result.processing_time_ms} ms`}/>
Structured fields
{Object.entries(result.fields || {}).map(([key, value]) =>
{key.replaceAll("_", " ")} {value ?? "Not detected"} {result.field_confidences?.[key] ? `${(result.field_confidences[key] * 100).toFixed(0)}%` : "—"}
)}
{result.routing_reason || (result.needs_review ? "Manual review recommended" : "Confidence thresholds satisfied")}
downloadJson(result)}> JSON downloadCsv(result)}> CSV
;
}
function Stat({ icon, label, value }) { return {icon}{label} {value}
; }
function BatchView({ batchFiles, setBatchFiles, batchResults, runBatch, loading }) { const input = useRef(null); return <>{batchFiles.length ? batchFiles.map((file) =>
{file.name} {(file.size / 1024).toFixed(0)} KB
) :
Your selected documents will appear here.
}
{loading ? <> Running batch...> : <> Execute batch analysis>} {batchResults.length > 0 && Batch output
Processing summary Document Class Confidence Decision Words Latency {batchResults.map((item) => {item.file_name} {item.error ? Error : item.doc_class} {item.confidence ? `${(item.confidence * 100).toFixed(1)}%` : "—"} {item.error ? item.error : item.needs_review ? "Review" : "Accepted"} {item.words ?? "—"} {item.latency_ms ? `${item.latency_ms} ms` : "—"} )}
}>; }
function AnalyticsView({ analyticsModel, setAnalyticsModel, analytics }) { return <>Model setAnalyticsModel(e.target.value)}>{MODELS.map((model) => {model} )} Confusion matrix
{analyticsModel.replaceAll("_", " ")} {analytics?.matrix_url ? : Matrix artifact not found.
}Performance report
Classification metrics >; }
function MetricsTable({ data }) { if (!data) return Metrics are not available.
; return Class Precision Recall F1 Support {Object.entries(data).map(([key, value]) => typeof value === "object" ? {key} {fmt(value.precision)} {fmt(value.recall)} {fmt(value["f1-score"])} {value.support ?? "—"} : null)}
; }
function fmt(value) { return typeof value === "number" ? value.toFixed(3) : "—"; }
function PageHeading({ kicker, title, description, action }) { return {kicker} {title} {description}
{action}
; }
function downloadJson(result) { downloadBlob(JSON.stringify(result, null, 2), `${result.doc_id}_results.json`, "application/json"); }
function downloadCsv(result) { const rows = [["Field Name", "Extracted Value", "Confidence"], ...Object.entries(result.fields || {}).map(([key, value]) => [key, value ?? "", result.field_confidences?.[key] ?? ""])]; downloadBlob(rows.map((row) => row.map((cell) => `"${String(cell).replaceAll('"', '""')}"`).join(",")).join("\n"), `${result.doc_id}_fields.csv`, "text/csv"); }
function downloadBlob(content, name, type) { const url = URL.createObjectURL(new Blob([content], {type})); const anchor = document.createElement("a"); anchor.href = url; anchor.download = name; anchor.click(); URL.revokeObjectURL(url); }
createRoot(document.getElementById("root")).render( );