import { useEffect, useState } from "react"; import { api } from "../../api"; import { IconAsk, IconCheck, IconDoc, IconEval, IconQuote, IconShield, IconSpark, IconUpload, } from "../../lib/icons"; import { useScrollReveal } from "../../lib/useScrollReveal"; import Button from "../ui/Button"; import Card from "../ui/Card"; import { EmptyState, ErrorState, LoadingState, SuccessNote } from "../ui/States"; import DocumentCard from "../DocumentCard"; import Upload from "../Upload"; import HeroPreview from "./HeroPreview"; const STEPS = [ { Icon: IconUpload, t: "Upload", d: "Drop a PDF or TXT. We extract the text and split it into overlapping, page-tagged chunks." }, { Icon: IconAsk, t: "Retrieve", d: "Your question ranks the most relevant passages with a transparent TF-IDF retriever." }, { Icon: IconQuote, t: "Cite", d: "The answer is built only from those passages — and every claim shows its source." }, ]; // Lightweight count-up for the stats band (rAF, ease-out cubic). function CountUp({ end, suffix = "", duration = 1200 }) { const [v, setV] = useState(0); useEffect(() => { if (!end) return; let raf, start; const tick = (t) => { if (!start) start = t; const p = Math.min((t - start) / duration, 1); setV(Math.round((1 - Math.pow(1 - p, 3)) * end)); if (p < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [end, duration]); return ( <> {v} {suffix} > ); } export default function Overview({ documents, loading, reload, onTryDemo, seeding, onOpenEval }) { const [uploading, setUploading] = useState(false); const [message, setMessage] = useState(""); const [error, setError] = useState(""); useScrollReveal([loading, documents.length]); async function onFile(file) { setError(""); setMessage(""); setUploading(true); try { const doc = await api.uploadDocument(file); await reload(); if (doc.status === "processed") { setMessage(`Uploaded "${doc.filename}" — ${doc.num_chunks} chunks ready.`); } else { setError(`"${doc.filename}" couldn't be processed: ${doc.error || "unknown error"}`); } } catch (err) { setError(err.message); } finally { setUploading(false); } } async function onDelete(doc) { if (!confirm(`Delete "${doc.filename}" and its history?`)) return; setError(""); setMessage(""); try { await api.deleteDocument(doc.id); await reload(); setMessage(`Deleted "${doc.filename}".`); } catch (err) { setError(err.message); } } function scrollToUpload() { document .getElementById("upload-zone") ?.scrollIntoView({ behavior: "smooth", block: "center" }); } return (
FinanceFlow audits invoices, contracts, and payment notes for totals, due dates, payment terms, late fees, and unsupported claims — with citations you can verify. When the evidence isn't there, it says so instead of guessing.
01 / How it works
{d}
02 / Why it's trustworthy
Every answer shows the exact source excerpt, page, and relevance score — so you verify in one glance.
Two retrieval gates. If the evidence is weak, it replies “Not enough information” instead of guessing.
Extractive by default — it can't invent a number that isn't in your file.
A golden dataset grades grounding on every run: 100% citation coverage, 0 unsupported answers.
Most AI demos ask you to trust them. FinanceFlow ships a golden-dataset evaluation that scores whether it stayed grounded — and counts a correct refusal as a success.
Proof, not promises
FinanceFlow doesn’t ask you to trust it — it shows its work. Every answer links back to the exact source line, it abstains when the document doesn’t say, and a bundled evaluation grades that grounding on every run.
03 / Try it with your own files