From 506d092b2b1669a665145ccc9da229f44048afe2 Mon Sep 17 00:00:00 2001 From: francy51 Date: Thu, 14 May 2026 21:40:24 -0400 Subject: [PATCH] Wire remaining reactive UI controls --- apps/web/src/ui/app/AppShell.tsx | 165 ++++++++++++++++++++++--------- 1 file changed, 118 insertions(+), 47 deletions(-) diff --git a/apps/web/src/ui/app/AppShell.tsx b/apps/web/src/ui/app/AppShell.tsx index b7e79bc..e438f84 100644 --- a/apps/web/src/ui/app/AppShell.tsx +++ b/apps/web/src/ui/app/AppShell.tsx @@ -250,6 +250,25 @@ export function App() { else addToast({ type: "error", title: "Could not refresh exports", desc: result.error.message }); }, [activeCompanyId, addToast]); + const runPipeline = useCallback(async (pipeline: "research" | "competitive" | "cross-cutting") => { + if (!activeCompanyId) { + addToast({ type: "warning", title: "Select a company first", desc: "Agent pipelines require an active company." }); + return; + } + setRunningPipeline(pipeline); + try { + const result = await rpc.call("agent.runPipeline", { companyId: activeCompanyId, pipeline }); + if (result.ok) { + addToast({ type: "success", title: `${title(pipeline)} pipeline started`, desc: `${result.data.runIds.length} agents queued` }); + await reloadActiveCompany(activeCompanyId); + } else { + addToast({ type: "error", title: "Pipeline failed", desc: result.error.message }); + } + } finally { + setRunningPipeline(null); + } + }, [activeCompanyId, addToast, reloadActiveCompany]); + useEffect(() => { const root = document.documentElement; root.setAttribute("data-density", density); @@ -541,6 +560,8 @@ export function App() { onSelectCompany={selectCompany} onRemoveHolding={removeHolding} pendingTicker={settingActiveTicker ?? removingTicker ?? addingTicker} + onRunResearch={() => runPipeline("research")} + runningResearch={runningPipeline === "research"} addToast={addToast} /> )} @@ -593,26 +614,32 @@ export function App() { onCreateExport={async (type) => { if (!activeCompanyId) return; setCreatingExportType(type); - const result = await rpc.call("export.create", { type, companyId: activeCompanyId, options: { format: type === "excel" ? "xlsx" : type } }); - if (result.ok) { - await refreshExports(activeCompanyId); - addToast({ type: "success", title: "Export created", desc: result.data.exportId }); - } else addToast({ type: "error", title: "Export failed", desc: result.error.message }); - setCreatingExportType(null); + try { + const result = await rpc.call("export.create", { type, companyId: activeCompanyId, options: { format: type === "excel" ? "xlsx" : type } }); + if (result.ok) { + await refreshExports(activeCompanyId); + addToast({ type: "success", title: "Export created", desc: result.data.exportId }); + } else addToast({ type: "error", title: "Export failed", desc: result.error.message }); + } finally { + setCreatingExportType(null); + } }} onDownloadExport={async (record) => { setDownloadingExportId(record.id); - const result = await rpc.call("export.download", { exportId: record.id }); - if (result.ok) { - const blob = new Blob([result.data.data]); - const url = URL.createObjectURL(blob); - const anchor = document.createElement("a"); - anchor.href = url; - anchor.download = `${record.title.replace(/[^a-z0-9]+/gi, "_")}.${record.type === "excel" ? "xlsx" : record.type === "ppt" ? "pptx" : "html"}`; - anchor.click(); - URL.revokeObjectURL(url); - } else addToast({ type: "error", title: "Download failed", desc: result.error.message }); - setDownloadingExportId(null); + try { + const result = await rpc.call("export.download", { exportId: record.id }); + if (result.ok) { + const blob = new Blob([result.data.data]); + const url = URL.createObjectURL(blob); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = `${record.title.replace(/[^a-z0-9]+/gi, "_")}.${record.type === "excel" ? "xlsx" : record.type === "ppt" ? "pptx" : "html"}`; + anchor.click(); + URL.revokeObjectURL(url); + } else addToast({ type: "error", title: "Download failed", desc: result.error.message }); + } finally { + setDownloadingExportId(null); + } }} creatingExportType={creatingExportType} downloadingExportId={downloadingExportId} @@ -668,12 +695,15 @@ export function App() { onCreateExport={async (type) => { if (!activeCompanyId) return; setCreatingExportType(type); - const result = await rpc.call("export.create", { type, companyId: activeCompanyId, options: { format: type === "excel" ? "xlsx" : type } }); - if (result.ok) { - await refreshExports(activeCompanyId); - addToast({ type: "success", title: "Export created", desc: result.data.exportId }); - } else addToast({ type: "error", title: "Export failed", desc: result.error.message }); - setCreatingExportType(null); + try { + const result = await rpc.call("export.create", { type, companyId: activeCompanyId, options: { format: type === "excel" ? "xlsx" : type } }); + if (result.ok) { + await refreshExports(activeCompanyId); + addToast({ type: "success", title: "Export created", desc: result.data.exportId }); + } else addToast({ type: "error", title: "Export failed", desc: result.error.message }); + } finally { + setCreatingExportType(null); + } }} addToast={addToast} /> @@ -682,12 +712,15 @@ export function App() { { if (!activeCompanyId) return; setCreatingExportType("pdf"); - const result = await rpc.call("export.create", { type: "pdf", companyId: activeCompanyId, options: { format: "html" } }); - if (result.ok) { - await refreshExports(activeCompanyId); - addToast({ type: "success", title: "PDF export created", desc: result.data.exportId }); - } else addToast({ type: "error", title: "Export failed", desc: result.error.message }); - setCreatingExportType(null); + try { + const result = await rpc.call("export.create", { type: "pdf", companyId: activeCompanyId, options: { format: "html" } }); + if (result.ok) { + await refreshExports(activeCompanyId); + addToast({ type: "success", title: "PDF export created", desc: result.data.exportId }); + } else addToast({ type: "error", title: "Export failed", desc: result.error.message }); + } finally { + setCreatingExportType(null); + } }} /> )} {activeScreen === "agents" && ( @@ -734,14 +767,7 @@ export function App() { setPausingAgentIds(new Set()); }} onRunPipeline={async (pipeline) => { - if (!activeCompanyId) return; - setRunningPipeline(pipeline); - const r = await rpc.call("agent.runPipeline", { companyId: activeCompanyId, pipeline }); - if (r.ok) { - addToast({ type: "success", title: `${title(pipeline)} pipeline started`, desc: `${r.data.runIds.length} agents queued` }); - await reloadActiveCompany(activeCompanyId); - } else addToast({ type: "error", title: "Pipeline failed", desc: r.error.message }); - setRunningPipeline(null); + await runPipeline(pipeline); }} runningPipeline={runningPipeline} pausingAgentIds={pausingAgentIds} @@ -1066,6 +1092,8 @@ function Home(props: { onSelectCompany: (tickerOrId: string, screen?: Screen) => void; onRemoveHolding: (ticker: string) => void; pendingTicker: string | null; + onRunResearch: () => void | Promise; + runningResearch: boolean; addToast: (t: Omit) => void; }) { const holdings = props.holdings; @@ -1078,10 +1106,10 @@ function Home(props: {
@@ -1457,7 +1485,7 @@ function AlertsSection({ alerts }: { alerts: Alert[] }) { ? "Thesis −" : "Neutral"} - + @@ -1598,7 +1626,7 @@ function FilingSection({ filings }: { filings: Filing[] }) { Key changes: {f.keyChanges}
)} - + ))} @@ -1693,7 +1721,7 @@ function Model({ Revenue Build ($M)
- + @@ -1733,7 +1761,11 @@ function Model({ forecast={i >= 3} total={row.kind === "total"} > - void onUpdateCell(rowIndex, i, event.target.value)} /> + onUpdateCell(rowIndex, i, value)} + /> ))} @@ -1747,6 +1779,45 @@ function Model({ ); } +function ModelCell({ + value, + disabled, + onCommit, +}: { + value: string; + disabled: boolean; + onCommit: (value: string) => void | Promise; +}) { + const [draft, setDraft] = useState(value); + + useEffect(() => { + setDraft(value); + }, [value]); + + const commit = useCallback(() => { + if (draft !== value) void onCommit(draft); + }, [draft, onCommit, value]); + + return ( + setDraft(event.target.value)} + onBlur={commit} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.currentTarget.blur(); + } + if (event.key === "Escape") { + setDraft(value); + event.currentTarget.blur(); + } + }} + /> + ); +} + function Memo({ memo, company, @@ -2164,9 +2235,9 @@ function Memo({ AI suggestion: tighten the fee-increase sentence and quantify renewal-cycle timing.
- - - + + +
)}