Download app/frontend/src/components/Handoff.tsx from ml-intern-explorers/primarydraft: direct link, hf CLI and curl.
- Browser
- Download file 8.14 kB
-
https://huggingface.co/spaces/ml-intern-explorers/primarydraft/resolve/main/app/frontend/src/components/Handoff.tsx
- Command line
-
hf download hf://spaces/ml-intern-explorers/primarydraft/app/frontend/src/components/Handoff.tsx
-
curl -L -o Handoff.tsx https://huggingface.co/spaces/ml-intern-explorers/primarydraft/resolve/main/app/frontend/src/components/Handoff.tsx
8.14 kB
| import { useEffect, useState } from "react"; | |
| import { DownloadSimple, SealCheck, ListChecks, ClockCounterClockwise } from "@phosphor-icons/react"; | |
| import { api, ApiError, type Run } from "../lib/api"; | |
| import { ErrorNote, Note, TableSkeleton } from "./common"; | |
| export function Handoff({ runId }: { runId: string }) { | |
| const [run, setRun] = useState<Run | null>(null); | |
| const [error, setError] = useState<string | null>(null); | |
| const [showAll, setShowAll] = useState(false); | |
| const load = () => { | |
| setError(null); | |
| setRun(null); | |
| api.getRun(runId) | |
| .then(setRun) | |
| .catch((e) => setError(e instanceof ApiError ? e.message : String(e))); | |
| }; | |
| useEffect(load, [runId]); | |
| if (error) return <ErrorNote message={error} onRetry={load} />; | |
| if (!run) { | |
| return <div className="panel"><div className="panel-b"><TableSkeleton rows={8} /></div></div>; | |
| } | |
| const covered = run.coverage.filter((c) => c.covered); | |
| const uncovered = run.coverage.filter((c) => !c.covered); | |
| const pct = run.coverage_pct; | |
| const blockers = run.severity.blocker ?? 0; | |
| return ( | |
| <> | |
| <div className="page-head"> | |
| <h1 className="page">Handoff to the merchant banker</h1> | |
| <p className="lede"> | |
| The draft, the clause coverage and the complete decision log. PrimaryDraft does not | |
| certify and does not file. The merchant banker reviews this, exercises independent | |
| judgement, and gives the due diligence certificate required under Regulation 23. | |
| </p> | |
| </div> | |
| <div className="grid cols-sidebar"> | |
| <div style={{ display: "grid", gap: 14 }}> | |
| <section className="panel"> | |
| <div className="panel-h"> | |
| <ListChecks size={16} /> | |
| <h2>ICDR Schedule VI coverage</h2> | |
| <span className="count"> | |
| {covered.length} of {run.coverage.length} clauses | |
| </span> | |
| <button | |
| className="btn btn-ghost btn-sm" | |
| style={{ marginLeft: "auto" }} | |
| onClick={() => setShowAll((v) => !v)} | |
| > | |
| {showAll ? "Show gaps only" : "Show all clauses"} | |
| </button> | |
| </div> | |
| <div style={{ maxHeight: "44vh", overflowY: "auto" }}> | |
| {(showAll ? run.coverage : uncovered).map((c) => ( | |
| <div className="clause-row" key={c.clause_id}> | |
| <span className="clause-c">{c.clause}</span> | |
| <span> | |
| {c.title} | |
| {!c.covered && ( | |
| <span | |
| className="f-id" | |
| style={{ display: "block", marginTop: 2, maxWidth: "60ch" }} | |
| > | |
| {c.reason} | |
| </span> | |
| )} | |
| </span> | |
| <span className={`tag ${c.covered ? "tag-ok" : "tag-warn"}`}> | |
| {c.covered ? "drafted" : "gap"} | |
| </span> | |
| </div> | |
| ))} | |
| </div> | |
| </section> | |
| <section className="panel"> | |
| <div className="panel-h"> | |
| <ClockCounterClockwise size={16} /> | |
| <h2>Decision log</h2> | |
| <span className="count">{run.decisions.length} entries</span> | |
| </div> | |
| <div style={{ maxHeight: "34vh", overflowY: "auto" }}> | |
| {run.decisions.map((d, i) => ( | |
| <div className="log-row" key={i}> | |
| <span className="log-t"> | |
| {d.at.replace("T", " ").replace("+00:00", "")} | |
| </span> | |
| <span> | |
| <span className="log-a">{d.actor}</span> | |
| <span style={{ color: "var(--text-3)" }}> · {d.action}</span> | |
| {d.rule_id && <span className="f-id"> · {d.rule_id}</span>} | |
| <div style={{ color: "var(--text-2)", marginTop: 2, lineHeight: 1.5 }}> | |
| {d.detail} | |
| </div> | |
| </span> | |
| </div> | |
| ))} | |
| </div> | |
| </section> | |
| </div> | |
| <aside style={{ display: "grid", gap: 14 }}> | |
| <section className="panel"> | |
| <div className="panel-b" style={{ textAlign: "center", padding: "22px 16px" }}> | |
| <Ring pct={pct} /> | |
| <div style={{ fontSize: 12.5, color: "var(--text-2)", marginTop: 8 }}> | |
| of {run.coverage.length} Schedule VI clauses drafted | |
| </div> | |
| <div className="f-id" style={{ marginTop: 6, lineHeight: 1.5 }}> | |
| {uncovered.length} clauses are deliberately out of scope and reported as gaps, | |
| not hidden. | |
| </div> | |
| </div> | |
| </section> | |
| <section className="panel"> | |
| <div className="panel-h"><h2>Exports</h2></div> | |
| <div className="panel-b" style={{ display: "grid", gap: 9 }}> | |
| <a className="btn btn-primary" href={api.docxUrl(run.id)} download | |
| style={{ justifyContent: "center" }}> | |
| <DownloadSimple size={15} /> Offer document (DOCX) | |
| </a> | |
| <a className="btn btn-ghost" href={api.manifestUrl(run.id)} download | |
| style={{ justifyContent: "center" }}> | |
| <DownloadSimple size={15} /> Audit manifest (JSON) | |
| </a> | |
| <div className="f-id" style={{ lineHeight: 1.55, marginTop: 2 }}> | |
| The manifest records every figure, its source page, the status it earned, and | |
| every human decision. A reviewer can rebuild the draft from it. | |
| </div> | |
| </div> | |
| </section> | |
| <section className="panel"> | |
| <div className="panel-h"> | |
| <SealCheck size={16} /> | |
| <h2>Status</h2> | |
| </div> | |
| <div className="panel-b" style={{ display: "grid", gap: 10 }}> | |
| <Stat k="Facts in ledger" v={String(run.fact_count)} /> | |
| <Stat k="Sourced figures used" v={String(run.meta.used_fact_ids?.length ?? 0)} /> | |
| <Stat k="Unsourced figures" v={String(run.meta.unbound_numbers?.length ?? 0)} /> | |
| <Stat k="Checks passed" v={`${run.rules_passed} of ${run.rule_count}`} /> | |
| <Stat k="Open blockers" v={String(blockers)} /> | |
| </div> | |
| </section> | |
| {blockers > 0 ? ( | |
| <Note tone="warn"> | |
| {blockers} blocker{blockers === 1 ? "" : "s"} remain open. A draft in this state | |
| should not go to the exchange until each is reconciled. | |
| </Note> | |
| ) : ( | |
| <Note> | |
| No blockers remain open. The draft is ready for the merchant banker's review and | |
| certification. | |
| </Note> | |
| )} | |
| <Note tone="quiet"> | |
| Not certified. Not filed. Not an offer document. PrimaryDraft is decision support: | |
| it moves the work, not the signature. | |
| </Note> | |
| </aside> | |
| </div> | |
| </> | |
| ); | |
| } | |
| function Stat({ k, v }: { k: string; v: string }) { | |
| return ( | |
| <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12.5 }}> | |
| <span style={{ color: "var(--text-2)" }}>{k}</span> | |
| <span className="num" style={{ fontWeight: 600 }}>{v}</span> | |
| </div> | |
| ); | |
| } | |
| function Ring({ pct }: { pct: number }) { | |
| const r = 52; | |
| const c = 2 * Math.PI * r; | |
| return ( | |
| <svg width="132" height="132" viewBox="0 0 132 132" role="img" | |
| aria-label={`${pct} percent of clauses drafted`}> | |
| <circle cx="66" cy="66" r={r} fill="none" stroke="var(--line)" strokeWidth="10" /> | |
| <circle | |
| cx="66" cy="66" r={r} fill="none" stroke="var(--accent)" strokeWidth="10" | |
| strokeLinecap="round" strokeDasharray={c} | |
| strokeDashoffset={c - (c * pct) / 100} | |
| transform="rotate(-90 66 66)" | |
| /> | |
| <text x="66" y="72" textAnchor="middle" | |
| style={{ font: "600 26px var(--mono)", fill: "var(--text)" }}> | |
| {pct}% | |
| </text> | |
| </svg> | |
| ); | |
| } | |