Spaces:
Sleeping
Sleeping
Download frontend/src/main.jsx from vettri06/Document_intelligence: direct link, hf CLI and curl.
- Browser
- Download file 15.6 kB
-
https://huggingface.co/spaces/vettri06/Document_intelligence/resolve/main/frontend/src/main.jsx
- Command line
-
hf download hf://spaces/vettri06/Document_intelligence/frontend/src/main.jsx
-
curl -L -o main.jsx https://huggingface.co/spaces/vettri06/Document_intelligence/resolve/main/frontend/src/main.jsx
15.6 kB
| 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 <div className="shell"> | |
| <header className="topbar"> | |
| <div className="brand"><div className="brand-mark"><ShieldCheck size={20}/></div><div><strong>Document_intelligence</strong><span>Document intelligence studio</span></div></div> | |
| <div className="topbar-status"><span className="live-dot"/> Pipeline online <span className="divider"/> CPU inference</div> | |
| </header> | |
| <div className="workspace"> | |
| <aside className="sidebar"> | |
| <div className="eyebrow">Workspace</div> | |
| <nav> | |
| <NavButton icon={<FileText size={17}/>} label="Document analysis" active={view === "analysis"} onClick={() => setView("analysis")}/> | |
| <NavButton icon={<Layers3 size={17}/>} label="Batch processing" active={view === "batch"} onClick={() => setView("batch")}/> | |
| <NavButton icon={<BarChart3 size={17}/>} label="Model analytics" active={view === "analytics"} onClick={() => setView("analytics")}/> | |
| </nav> | |
| <div className="sidebar-bottom"><div className="mini-label">System status</div><div className="system-line"><span className="live-dot"/> API ready</div><div className="system-line muted"><Activity size={14}/> OCR + classification</div></div> | |
| </aside> | |
| <main className="main"> | |
| {error && <div className="alert error"><AlertTriangle size={17}/><span>{error}</span><button onClick={() => setError("")} aria-label="Dismiss"><X size={16}/></button></div>} | |
| {view === "analysis" && <AnalysisView {...{inputMode, setInputMode, samples, sampleId, setSampleId, activeSample, file, setFile, previewUrl, result, loading, analyze, resetAnalysis, fileInput}}/>} | |
| {view === "batch" && <BatchView {...{batchFiles, setBatchFiles, batchResults, runBatch, loading}}/>} | |
| {view === "analytics" && <AnalyticsView {...{analyticsModel, setAnalyticsModel, analytics}}/>} | |
| </main> | |
| </div> | |
| </div>; | |
| } | |
| function NavButton({ icon, label, active, onClick }) { return <button className={`nav-button ${active ? "active" : ""}`} onClick={onClick}>{icon}<span>{label}</span>{active && <ChevronRight size={15}/>}</button>; } | |
| function AnalysisView({ inputMode, setInputMode, samples, sampleId, setSampleId, activeSample, file, setFile, previewUrl, result, loading, analyze, resetAnalysis, fileInput }) { | |
| return <> | |
| <PageHeading kicker="Document analysis" title="Classify, extract, decide" description="Turn an uploaded document into structured fields, confidence scores, and a clear routing decision." action={<button className="ghost-button" onClick={resetAnalysis}><RefreshCcw size={16}/> Reset</button>}/> | |
| <section className="control-strip"> | |
| <div className="segmented"><button className={inputMode === "upload" ? "selected" : ""} onClick={() => setInputMode("upload")}><Upload size={15}/> Upload document</button><button className={inputMode === "sample" ? "selected" : ""} onClick={() => setInputMode("sample")}><Sparkles size={15}/> Use sample</button></div> | |
| {inputMode === "sample" && <select value={sampleId} onChange={(e) => setSampleId(e.target.value)}><option value="">Select a sample document</option>{samples.map((item) => <option key={item.id} value={item.id}>{item.label}</option>)}</select>} | |
| </section> | |
| <div className="analysis-grid"> | |
| <section className="panel document-panel"> | |
| <div className="panel-heading"><div><span className="panel-kicker">Input document</span><h2>{file?.name || activeSample?.label || "No document selected"}</h2></div>{(file || activeSample) && <span className="ready-badge"><CheckCircle2 size={14}/> Ready</span>}</div> | |
| {inputMode === "upload" && <div className="dropzone" onClick={() => fileInput.current?.click()} onDragOver={(e) => e.preventDefault()} onDrop={(e) => { e.preventDefault(); setFile(e.dataTransfer.files[0]); }}><input ref={fileInput} type="file" accept=".png,.jpg,.jpeg,.pdf" onChange={(e) => setFile(e.target.files[0])}/><Upload size={24}/><strong>{file ? "Replace document" : "Choose a document"}</strong><span>PNG, JPG, JPEG, or PDF · maximum 10 MB</span></div>} | |
| {inputMode === "sample" && <div className="sample-note"><Sparkles size={18}/><div><strong>{activeSample?.label || "Choose a sample above"}</strong><span>{activeSample?.description || "Use a curated document to check the pipeline without uploading a file."}</span></div></div>} | |
| {previewUrl && (file?.type?.startsWith("image/") || activeSample) ? <div className="preview-wrap"><img src={previewUrl} alt="Document preview"/></div> : previewUrl ? <div className="pdf-preview"><FileText size={32}/><span>PDF document selected</span></div> : <div className="empty-preview"><FileChartColumn size={28}/><span>Preview appears here after selecting a document</span></div>} | |
| <button className="primary-button analyze-button" disabled={loading} onClick={analyze}>{loading ? <><LoaderCircle className="spin" size={17}/> Analyzing document...</> : <><Search size={17}/> Analyze document</>}</button> | |
| </section> | |
| <ResultsPanel result={result} loading={loading}/> | |
| </div> | |
| </>; | |
| } | |
| function ResultsPanel({ result, loading }) { | |
| if (loading) return <section className="panel result-panel loading-panel"><LoaderCircle className="spin big" size={28}/><h2>Processing document</h2><p>OCR, layout classification, and field extraction are running.</p><div className="progress-line"><span/></div></section>; | |
| if (!result) return <section className="panel result-panel empty-results"><div className="empty-icon"><Activity size={22}/></div><span className="panel-kicker">Analysis output</span><h2>Ready for processing</h2><p>Select a document and run the pipeline to see classification, extracted fields, and routing confidence.</p></section>; | |
| const cls = result.doc_class.replaceAll("_", " "); | |
| return <section className="panel result-panel"><div className="panel-heading"><div><span className="panel-kicker">Analysis output</span><h2>Document decision</h2></div><span className={`class-badge ${result.needs_review ? "review" : "accepted"}`}>{result.needs_review ? "Review" : "Accepted"}</span></div><div className="classification"><div><span>Predicted class</span><strong>{cls}</strong></div><div className="confidence"><span>Class confidence</span><strong>{(result.class_confidence * 100).toFixed(1)}%</strong><div className="meter"><i style={{width: `${result.class_confidence * 100}%`}}/></div></div></div><div className="stat-row"><Stat icon={<FileText size={16}/>} label="OCR words" value={result.ocr_token_count}/><Stat icon={<Clock3 size={16}/>} label="Latency" value={`${result.processing_time_ms} ms`}/></div><div className="section-title">Structured fields</div><div className="field-list">{Object.entries(result.fields || {}).map(([key, value]) => <div className="field-row" key={key}><span>{key.replaceAll("_", " ")}</span><strong>{value ?? "Not detected"}</strong><em>{result.field_confidences?.[key] ? `${(result.field_confidences[key] * 100).toFixed(0)}%` : "—"}</em></div>)}</div><div className={`route-note ${result.needs_review ? "warn" : "good"}`}><ShieldCheck size={17}/><span>{result.routing_reason || (result.needs_review ? "Manual review recommended" : "Confidence thresholds satisfied")}</span></div><div className="download-row"><button className="secondary-button" onClick={() => downloadJson(result)}><Download size={15}/> JSON</button><button className="secondary-button" onClick={() => downloadCsv(result)}><Download size={15}/> CSV</button></div></section>; | |
| } | |
| function Stat({ icon, label, value }) { return <div className="stat"><span>{icon}{label}</span><strong>{value}</strong></div>; } | |
| function BatchView({ batchFiles, setBatchFiles, batchResults, runBatch, loading }) { const input = useRef(null); return <><PageHeading kicker="Batch processing" title="Process a document queue" description="Run the same classification and extraction pipeline across multiple documents."/><section className="panel batch-panel"><div className="panel-heading"><div><span className="panel-kicker">Batch input</span><h2>{batchFiles.length ? `${batchFiles.length} documents selected` : "Select documents"}</h2></div><button className="secondary-button" onClick={() => input.current?.click()}><Upload size={15}/> Add files</button><input ref={input} hidden type="file" multiple accept=".png,.jpg,.jpeg,.pdf" onChange={(e) => setBatchFiles([...e.target.files])}/></div><div className="batch-list">{batchFiles.length ? batchFiles.map((file) => <div className="batch-item" key={file.name}><FileText size={17}/><span>{file.name}</span><small>{(file.size / 1024).toFixed(0)} KB</small></div>) : <div className="empty-preview compact"><Layers3 size={26}/><span>Your selected documents will appear here.</span></div>}</div><button className="primary-button" disabled={loading || !batchFiles.length} onClick={runBatch}>{loading ? <><LoaderCircle className="spin" size={17}/> Running batch...</> : <><Activity size={17}/> Execute batch analysis</>}</button></section>{batchResults.length > 0 && <section className="panel table-panel"><div className="panel-heading"><div><span className="panel-kicker">Batch output</span><h2>Processing summary</h2></div></div><div className="table-scroll"><table><thead><tr><th>Document</th><th>Class</th><th>Confidence</th><th>Decision</th><th>Words</th><th>Latency</th></tr></thead><tbody>{batchResults.map((item) => <tr key={item.file_name}><td>{item.file_name}</td><td>{item.error ? <span className="error-text">Error</span> : item.doc_class}</td><td>{item.confidence ? `${(item.confidence * 100).toFixed(1)}%` : "—"}</td><td>{item.error ? item.error : item.needs_review ? "Review" : "Accepted"}</td><td>{item.words ?? "—"}</td><td>{item.latency_ms ? `${item.latency_ms} ms` : "—"}</td></tr>)}</tbody></table></div></section>}</>; } | |
| function AnalyticsView({ analyticsModel, setAnalyticsModel, analytics }) { return <><PageHeading kicker="Model analytics" title="Benchmark the decision engine" description="Inspect champion model metrics and confusion matrices from the training artifacts."/><section className="analytics-toolbar"><label>Model<select value={analyticsModel} onChange={(e) => setAnalyticsModel(e.target.value)}>{MODELS.map((model) => <option key={model}>{model}</option>)}</select></label></section><div className="analytics-grid"><section className="panel"><div className="panel-heading"><div><span className="panel-kicker">Confusion matrix</span><h2>{analyticsModel.replaceAll("_", " ")}</h2></div></div>{analytics?.matrix_url ? <img className="matrix" src={analytics.matrix_url} alt="Normalized confusion matrix"/> : <div className="empty-preview compact">Matrix artifact not found.</div>}</section><section className="panel"><div className="panel-heading"><div><span className="panel-kicker">Performance report</span><h2>Classification metrics</h2></div></div><MetricsTable data={analytics?.metrics}/></section></div></>; } | |
| function MetricsTable({ data }) { if (!data) return <div className="empty-preview compact">Metrics are not available.</div>; return <div className="table-scroll"><table><thead><tr><th>Class</th><th>Precision</th><th>Recall</th><th>F1</th><th>Support</th></tr></thead><tbody>{Object.entries(data).map(([key, value]) => typeof value === "object" ? <tr key={key}><td>{key}</td><td>{fmt(value.precision)}</td><td>{fmt(value.recall)}</td><td>{fmt(value["f1-score"])}</td><td>{value.support ?? "—"}</td></tr> : null)}</tbody></table></div>; } | |
| function fmt(value) { return typeof value === "number" ? value.toFixed(3) : "—"; } | |
| function PageHeading({ kicker, title, description, action }) { return <div className="page-heading"><div><span className="eyebrow">{kicker}</span><h1>{title}</h1><p>{description}</p></div>{action}</div>; } | |
| 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(<App/>); | |