import { useCallback, useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { api } from './api' import { useStore } from './store' import type { SamplePack } from './types' import { ExternalIcon, LINKS, Lockup, Spinner } from './Brand' const PIPELINE = [ ['01', 'parse', 'Docling: text and page positions'], ['02', 'mask', 'Presidio: names out before any model call'], ['03', 'classify', 'which document is which'], ['04', 'extract', 'typed record + the exact phrase read'], ['05', 'merge', 'a written rule picks the winner'], ['06', 'trace', 'each value back to its PDF line'], ['07', 'review', 'you verify, override or flag'], ] as const export function UploadPage() { const navigate = useNavigate() const setSession = useStore((s) => s.setSession) const inputRef = useRef(null) const [loading, setLoading] = useState(false) const [step, setStep] = useState(0) const [error, setError] = useState(null) const [files, setFiles] = useState([]) const [dragOver, setDragOver] = useState(false) const [samplePack, setSamplePack] = useState(null) const [loadingSamples, setLoadingSamples] = useState(false) useEffect(() => { let isMounted = true api.getSamples() .then((pack) => { if (isMounted) setSamplePack(pack) }) .catch(() => { if (isMounted) setSamplePack(null) }) return () => { isMounted = false } }, []) // Advance the visual pipeline while the (synchronous) request runs. useEffect(() => { if (!loading) return setStep(0) const t = setInterval(() => setStep((s) => Math.min(s + 1, PIPELINE.length - 2)), 6000) return () => clearInterval(t) }, [loading]) const addPdfFiles = useCallback((incoming: File[]) => { const pdfs = incoming.filter((f) => f.name.toLowerCase().endsWith('.pdf')) setFiles((prev) => { const names = new Set(prev.map((f) => f.name)) return [...prev, ...pdfs.filter((f) => !names.has(f.name))] }) }, []) const handleFiles = useCallback((incoming: FileList | null) => { if (!incoming) return addPdfFiles(Array.from(incoming)) }, [addPdfFiles]) const removeFile = (name: string) => setFiles((prev) => prev.filter((f) => f.name !== name)) const handleLoadSamples = async () => { if (!samplePack?.files.length) return setLoadingSamples(true) setError(null) try { const demoFiles = await Promise.all(samplePack.files.map((sample) => api.downloadSampleFile(sample))) addPdfFiles(demoFiles) } catch (err: unknown) { setError(err instanceof Error ? err.message : 'Could not load the demo sample files.') } finally { setLoadingSamples(false) } } const handleSubmit = async () => { if (!files.length) return setLoading(true) setError(null) try { const resp = await api.processDocuments(files) const sessionData = await api.getSession(resp.session_id) setSession(sessionData) navigate(`/session/${resp.session_id}`) } catch (err: unknown) { setError(err instanceof Error ? err.message : 'An unknown error occurred.') setLoading(false) } } return (
{/* ── Top bar ─────────────────────────────────────────────── */}
{/* ── Body ────────────────────────────────────────────────── */}
{/* Left: what this is */}

Reference system · Document AI

A policy pack in. One checked record out.

Upload the Schedule, Certificate, Statement of Fact and policy booklet. PolicyTrace pulls the facts a claims handler needs into one typed record, settles disagreements between documents with a written rule, and links every value to the line in the PDF it came from.

    {PIPELINE.map(([n, name, desc], i) => { const state = !loading ? 'idle' : i < step ? 'done' : i === step ? 'active' : 'idle' return (
  1. {n} {name} {desc} {state === 'done' && ok} {state === 'active' && }
  2. ) })}
{/* Right: upload card */}
{ if (e.key === 'Enter' || e.key === ' ') inputRef.current?.click() }} onDragOver={(e) => { e.preventDefault(); setDragOver(true) }} onDragLeave={() => setDragOver(false)} onDrop={(e) => { e.preventDefault(); setDragOver(false); handleFiles(e.dataTransfer.files) }} onClick={() => inputRef.current?.click()} className={`rounded-xl border border-dashed px-6 py-10 text-center cursor-pointer transition-colors ${ dragOver ? 'border-signal bg-signal-tint' : 'border-ink-500 hover:border-ink-400 bg-ink-800' }`} >

Drop PDFs here or browse

Schedule · Certificate · Statement of Fact · Booklet

handleFiles(e.target.files)} />
{samplePack?.files.length ? (

No documents to hand?

Load the synthetic demo pack{samplePack.expected_policy_number ? <> ({samplePack.expected_policy_number}) : null}. No real customer data.

) : null} {files.length > 0 && (
    {files.map((f) => (
  • PDF {f.name}
  • ))}
)} {error && (
{error}
)}

Public demo. Use synthetic or redacted documents only.

) }