Spaces:
Sleeping
Sleeping
File size: 12,385 Bytes
2695640 375d687 2695640 a38a802 2695640 375d687 2695640 375d687 2695640 375d687 2695640 375d687 2695640 375d687 2695640 375d687 2695640 40c5ba0 2695640 40c5ba0 2695640 a38a802 2695640 a38a802 2695640 375d687 2695640 375d687 2695640 375d687 939ceb8 a38a802 939ceb8 a38a802 2695640 375d687 2695640 375d687 2695640 375d687 2695640 40c5ba0 2695640 375d687 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 | 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 (
<div className="landing">
{/* ---- Hero ---- */}
<section className="hero-stage">
<div>
<span className="hero-eyebrow">
<IconSpark size={14} /> Audit-ready finance answers
</span>
<h1 className="hero-h1">
Answers you can <span className="clip">prove.</span>
</h1>
<p className="hero-sub">
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.
</p>
<div className="hero-cta">
<Button variant="primary" size="lg" loading={seeding} onClick={onTryDemo}>
{seeding ? "Loading demo…" : "Try the live demo"}
</Button>
<Button variant="ghost" size="lg" onClick={scrollToUpload}>
Upload a document
</Button>
</div>
<div className="hero-trust-strip">
<span>Grounded only</span>
<span>Cites every line</span>
<span>Abstains when unsure</span>
</div>
</div>
<HeroPreview />
</section>
{/* ---- Works-with strip ---- */}
<div className="logo-strip">
<span className="ls-label">Reads your</span>
<span className="ls-item"><IconDoc size={16} /> Invoices</span>
<span className="ls-item"><IconDoc size={16} /> Contracts</span>
<span className="ls-item"><IconDoc size={16} /> Statements</span>
<span className="ls-item"><IconDoc size={16} /> Payment notes</span>
<span className="ls-item"><IconDoc size={16} /> PDF & TXT</span>
</div>
{/* ---- How it works ---- */}
<section className="reveal" style={{ marginTop: 24 }}>
<p className="eyebrow-mono">01 / <b>How it works</b></p>
<div className="flow">
{STEPS.map(({ Icon, t, d }, i) => (
<Card key={t}>
<div className="flow-num">{i + 1}</div>
<Icon size={20} />
<h3>{t}</h3>
<p>{d}</p>
</Card>
))}
</div>
</section>
{/* ---- Stats band ---- */}
<section className="reveal" style={{ marginTop: 40 }}>
<div className="stats-band">
<div className="stat">
<div className="stat-num"><CountUp end={100} suffix="%" /></div>
<div className="stat-label">of answers carry a citation</div>
</div>
<div className="stat">
<div className="stat-num">0</div>
<div className="stat-label">unsupported / hallucinated answers</div>
</div>
<div className="stat">
<div className="stat-num"><CountUp end={25} />/25</div>
<div className="stat-label">automated tests passing in CI</div>
</div>
</div>
</section>
{/* ---- Why it's trustworthy (bento) ---- */}
<section className="reveal" style={{ marginTop: 48 }}>
<p className="eyebrow-mono">02 / <b>Why it's trustworthy</b></p>
<div className="bento">
<div className="bento-tile bento-cite">
<IconQuote size={20} />
<h3>Citation-first retrieval</h3>
<p>Every answer shows the exact source excerpt, page, and relevance score — so you verify in one glance.</p>
<div className="bento-rankviz">
<div className="bento-rankbar" style={{ width: "100%" }} />
<div className="bento-rankbar" style={{ width: "72%" }} />
<div className="bento-rankbar" style={{ width: "48%" }} />
</div>
</div>
<div className="bento-tile bento-abst">
<IconShield size={20} />
<h3>Abstains when unsure</h3>
<p>Two retrieval gates. If the evidence is weak, it replies “Not enough information” instead of guessing.</p>
</div>
<div className="bento-tile bento-src">
<IconSpark size={20} />
<h3>Source-only answers</h3>
<p>Extractive by default — it can't invent a number that isn't in your file.</p>
</div>
<div className="bento-tile bento-eval">
<IconEval size={20} />
<h3>Evaluation suite included</h3>
<p>A golden dataset grades grounding on every run: 100% citation coverage, 0 unsupported answers.</p>
</div>
</div>
</section>
{/* ---- Evaluation teaser ---- */}
<section className="reveal" style={{ marginTop: 48 }}>
<div className="eval-teaser">
<div>
<h2>We grade ourselves.</h2>
<p>
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.
</p>
<Button variant="ghost" onClick={onOpenEval}>
Open the evaluation
</Button>
</div>
<div className="metric-grid" style={{ margin: 0, minWidth: 240 }}>
<div className="metric-card">
<div className="metric-value good">100%</div>
<div className="metric-label">Citation coverage</div>
</div>
<div className="metric-card">
<div className="metric-value good">0</div>
<div className="metric-label">Unsupported answers</div>
</div>
</div>
</div>
</section>
{/* ---- Proof band (dark) ---- */}
<section className="proof-band">
<div className="pb-head">
<p className="eyebrow-mono" style={{ color: "rgba(234,241,251,0.6)" }}>
<b style={{ color: "#fff" }}>Proof, not promises</b>
</p>
<h2>Trust you can check.</h2>
<p className="pb-sub">
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.
</p>
</div>
<div className="pb-proof">
<div className="pb-proof-item">
<IconCheck size={20} />
<div>
<b>100%</b>
<span className="pb-proof-label">of answers carry a citation</span>
</div>
</div>
<div className="pb-proof-item">
<IconCheck size={20} />
<div>
<b>0</b>
<span className="pb-proof-label">unsupported / hallucinated answers</span>
</div>
</div>
<div className="pb-proof-item">
<IconCheck size={20} />
<div>
<b>25/25</b>
<span className="pb-proof-label">automated tests passing</span>
</div>
</div>
</div>
</section>
{/* ---- Upload + documents (functional) ---- */}
<section id="upload-zone" className="reveal" style={{ marginTop: 56 }}>
<p className="eyebrow-mono">03 / <b>Try it with your own files</b></p>
<div className="section-title">
<h2>Add a document</h2>
<span className="muted">PDF or TXT · 10 MB max</span>
</div>
<Upload onFile={onFile} uploading={uploading} />
{message && <div style={{ marginTop: 16 }}><SuccessNote>{message}</SuccessNote></div>}
<div style={{ marginTop: 12 }}><ErrorState>{error}</ErrorState></div>
<div style={{ marginTop: 28 }}>
<div className="section-title">
<h2>Your documents</h2>
{documents.length > 0 && <span className="muted">{documents.length} total</span>}
</div>
{loading ? (
<Card>
<LoadingState lines={3} label="Loading documents…" />
</Card>
) : documents.length === 0 ? (
<EmptyState title="No documents yet">
Click <strong>Try the live demo</strong> to load sample finance documents,
or drop your own file above.
</EmptyState>
) : (
<div className="doc-grid">
{documents.map((d) => (
<DocumentCard key={d.id} doc={d} onDelete={onDelete} />
))}
</div>
)}
</div>
</section>
{/* ---- Final CTA ---- */}
<section className="reveal final-cta">
<h2>
Stop trusting. <span className="clip">Start verifying.</span>
</h2>
<Button variant="primary" size="lg" loading={seeding} onClick={onTryDemo}>
{seeding ? "Loading demo…" : "Try the live demo"}
</Button>
</section>
{/* ---- Footer ---- */}
<footer className="site-footer">
<span className="foot-brand">
<span
style={{
width: 22, height: 22, borderRadius: 7,
background: "var(--accent-grad)", display: "inline-grid", placeItems: "center",
}}
>
<IconSpark size={13} />
</span>
FinanceFlow AI
</span>
<span>Evidence-backed finance document intelligence.</span>
<a href="https://github.com/aissugur/financeflow-ai" target="_blank" rel="noreferrer">
GitHub ↗
</a>
</footer>
</div>
);
}
|