|
|
|
@@ -2,11 +2,12 @@ import { useEffect, useRef, useState } from "react";
|
|
|
|
|
import { ArrowLeft, ArrowRight, ArrowUpRight, BookOpen, Check, ChevronDown, ChevronRight, Cpu, Database, FileText, FolderClock, Layers, LayoutDashboard, LockKeyhole, Menu, MessageSquare, Play, Search, Settings2, ShieldCheck, SlidersHorizontal, Terminal, Users, X } from "lucide-react";
|
|
|
|
|
import { Content, type ContentProps, type ConnectionValues } from "../application-context/Content";
|
|
|
|
|
import { Button } from "../application-context/ui";
|
|
|
|
|
import { OriginalCorePreview } from "./OriginalCorePreview";
|
|
|
|
|
import { inventory, type Item, type Scenario, type Surface, type Variant } from "../administration-review/model";
|
|
|
|
|
import { models, modules, readLocation, sessions, variants, workspaces, type Job, type Page, type Route, type Session } from "./model";
|
|
|
|
|
|
|
|
|
|
// Three complete-app proposals on the established namespaced routes. V3 is a
|
|
|
|
|
// disposable simulation, with no API, persistence, authorization or server writes.
|
|
|
|
|
// Three context/admin proposals around the unchanged Core layout. V4 is a
|
|
|
|
|
// disposable simulation with remembered demo preferences, but no production API writes.
|
|
|
|
|
const icons = { core: MessageSquare, sessions: FolderClock, database: Database, memory: BookOpen, evidence: FileText, workspace: Layers, pi: Terminal };
|
|
|
|
|
const titles: Record<Page, string> = { core: "Analytical workspace", sessions: "Sessions", database: "Database", memory: "Memory", evidence: "Evidence", workspace: "Workspace", pi: "Pi runtime" };
|
|
|
|
|
import { SessionRail } from "./SessionRail";
|
|
|
|
@@ -25,6 +26,7 @@ export function App() {
|
|
|
|
|
const [width, setWidth] = useState("full");
|
|
|
|
|
const [portalOpen, setPortalOpen] = useState(false);
|
|
|
|
|
const [navOpen, setNavOpen] = useState(false);
|
|
|
|
|
const [coreActivityOpen, setCoreActivityOpen] = useState(false);
|
|
|
|
|
const [shelfOpen, setShelfOpen] = useState(initialPreferences.invalid);
|
|
|
|
|
const [notice, setNotice] = useState(initialPreferences.invalid ? "A remembered choice is no longer available. Choose a valid workspace and model; no alternative has been selected silently." : "");
|
|
|
|
|
const [dirty, setDirty] = useState(false);
|
|
|
|
@@ -41,7 +43,6 @@ export function App() {
|
|
|
|
|
const [active, setActive] = useState<Session | null>(null);
|
|
|
|
|
const [resumeTarget, setResumeTarget] = useState<Session | null>(null);
|
|
|
|
|
const [question, setQuestion] = useState("");
|
|
|
|
|
const [artifact, setArtifact] = useState("Question");
|
|
|
|
|
const [scenario, setScenario] = useState<Scenario>("ready");
|
|
|
|
|
const model = models.find(m => m.id === modelId);
|
|
|
|
|
const currentWorkspace = workspaces.find(w => w.id === workspace);
|
|
|
|
@@ -119,7 +120,7 @@ export function App() {
|
|
|
|
|
}
|
|
|
|
|
function resume(s: Session) {
|
|
|
|
|
if (busy || !ready || s.status === "archived" || availability === "needs-preprocessing" || !canLeave()) return;
|
|
|
|
|
saveChoice({ workspace: s.workspace }); setActive(s); setResumeTarget(null); resetDetail(); setQuestion(""); setArtifact("Question");
|
|
|
|
|
saveChoice({ workspace: s.workspace }); setActive(s); setResumeTarget(null); resetDetail(); setQuestion("");
|
|
|
|
|
writeRoute({ ...route, workspace: s.workspace, page: "core" });
|
|
|
|
|
setOperation({ id: Date.now(), label: `Resume: ${s.title}`, workspace: s.workspace, model: modelId, state: "running" });
|
|
|
|
|
setNotice("Proposed resume: the original workspace and revision are retained; this run uses the globally selected model. Backend changes are required.");
|
|
|
|
@@ -150,10 +151,12 @@ export function App() {
|
|
|
|
|
const title = question.trim(); setActive({ id: `demo-${Date.now()}`, title, workspace, revision: "a71c9f2", status: "paused", lastModel: modelId, phase: 1 });
|
|
|
|
|
setQuestion(""); setDirty(false); startJob(`Analysis: ${title}`, "llm");
|
|
|
|
|
}
|
|
|
|
|
const core = <>{pageHeader(active ? active.title : "From a question to a trusted answer", active ? "Persisted artifacts and reviewer decisions guide this session." : "Ask about your data. Review the evidence, approve the interpretation, then build the query.")}
|
|
|
|
|
{availability === "needs-preprocessing" && <div className="gc-warning"><strong>Prepare this workspace before starting the core</strong><p>Workspace and model are selected. Administration remains available so you can complete preprocessing.</p><Button onClick={() => navigate("workspace")}>Open workspace readiness</Button></div>}
|
|
|
|
|
{active ? <div className="gc-analysis"><div className="gc-session-context"><LockKeyhole size={15}/><strong>{currentWorkspace?.name}</strong><span>Session revision {active.revision}</span><span>{active.status === "archived" ? "Read-only archive" : activeJob ? `Run model: ${models.find(m => m.id === activeJob.model)?.name}` : `Next AI operation: ${model?.name}`}</span></div><div className="gc-workflow"><ol>{["Question", "Schema linking", "SQL draft", "Validation"].map((t,i) => <li key={t} className={i === 1 ? "current" : ""}><span>{String(i+1).padStart(2,"0")}</span>{t}</li>)}</ol><small>Workflow excerpt · 8 phases in production</small></div><div className="gc-artifact-layout"><section className="gc-artifact"><nav aria-label="Session artifacts">{["Question","Schema","SQL"].map(t => <button key={t} aria-current={artifact === t ? "page" : undefined} onClick={() => setArtifact(t)}>{t}</button>)}</nav><div><p className="rv-eyebrow">Persisted artifact · example</p><h2>{artifact === "Question" ? "Domanda analitica" : artifact === "Schema" ? "Collegamento allo schema" : "Query proposta"}</h2>{artifact === "SQL" ? <pre><code>{`SELECT COUNT(DISTINCT patient_id)\nFROM ${currentWorkspace?.schema}.visits\nWHERE visited_at >= DATE '2025-01-01'\n AND visited_at < DATE '2026-01-01';`}</code></pre> : <><p lang="it">{artifact === "Question" ? active.title : "La relazione tra pazienti e visite definisce la popolazione osservata. Il conteggio deve mantenere distinti i pazienti."}</p><div className="gc-evidence-line"><FileText size={16}/><span>Evidence: popolazione di riferimento</span></div><p className="rv-small">Synthetic illustration, not generated from a database.</p></>}</div></section><section className="gc-review"><p className="rv-eyebrow">Human review</p><h2>{active.status === "archived" ? "Retained decisions" : activeJob ? "AI operation in progress" : "Confirm the interpretation"}</h2><p>{active.status === "archived" ? "This archive can be inspected, but its workflow cannot currently be resumed." : activeJob ? "Workspace and model are locked until this operation finishes. No other operation can start in the meantime." : "Check the proposed population and time range before proceeding."}</p>{active.status !== "archived" && <Button primary disabled={busy} onClick={() => startJob(`Analysis: ${active.title}`, "llm")}>{activeJob ? "Waiting for result" : `Approve & continue with ${model?.name}`}</Button>}<p className="rv-small">{active.status === "archived" ? "Archived and finalized sessions remain read-only." : "Model output proposes. The reviewer decides."}</p></section></div></div> : <div className="gc-core-start"><section className="gc-question"><div className="gc-question-heading"><span className="gc-symbol"><MessageSquare size={23}/></span><div><h2>Start an analytical question</h2><p>{currentWorkspace?.database} · Evidence and Memory in this workspace</p></div></div><form onSubmit={e => { e.preventDefault(); submitQuestion(); }}><label htmlFor="analysis-question" className="rv-eyebrow">Your question</label><textarea id="analysis-question" rows={4} value={question} placeholder="Quanti pazienti hanno almeno una visita di follow-up nel 2025?" onChange={e => { setQuestion(e.target.value); setDirty(!!e.target.value); }}/><div className="gc-composer-footer"><span><Cpu size={15}/>{model?.name}<small>Global model</small></span><button className="rv-button rv-primary" type="submit" disabled={!question.trim() || availability === "needs-preprocessing" || busy}>Start analysis<ArrowRight size={16}/></button></div></form></section><section className="gc-method"><p className="rv-eyebrow">A reviewable path</p><div><span><strong>01</strong>Ask a question</span><ChevronRight size={15}/><span><strong>02</strong>Review context</span><ChevronRight size={15}/><span><strong>03</strong>Validate the query</span></div><p>Every analytical result is backed by persisted artifacts and explicit review decisions.</p></section></div>}
|
|
|
|
|
</>;
|
|
|
|
|
const core = <OriginalCorePreview active={active} busy={busy} running={!!activeJob} question={question}
|
|
|
|
|
needsPreprocessing={availability === "needs-preprocessing"}
|
|
|
|
|
onQuestion={text => { setQuestion(text); setDirty(!!text); }} onSubmit={submitQuestion}
|
|
|
|
|
onContinue={() => { if (active) startJob(`Analysis: ${active.title}`, "llm"); }}
|
|
|
|
|
onStop={() => { if (operation) setOperation({ ...operation, state: "cancelled" }); }}
|
|
|
|
|
onWorkspace={() => navigate("workspace")} onActivity={setCoreActivityOpen} />;
|
|
|
|
|
const rows = page === "memory" ? cards[workspace] ?? inventory.memory : admin ? inventory[page] : [];
|
|
|
|
|
const selected = selection[scope] ?? rows[0]?.id;
|
|
|
|
|
const item = rows.find(r => r.id === selected);
|
|
|
|
@@ -176,7 +179,7 @@ export function App() {
|
|
|
|
|
{page === "workspace" && <p className="gc-footnote">Preprocessing uses the installation embedding model. Selecting an interaction LLM does not change index compatibility.</p>}
|
|
|
|
|
{<div className="rv-operations"><div className="gc-operation-intro"><ShieldCheck size={19}/><span>{page === "pi" ? "Installation diagnostics" : `${currentWorkspace?.name} · Operational context`}</span><strong>{scenario === "ready" ? "Ready for review" : scenario}</strong></div><div className="rv-operation-body">{sections}<div className="rv-operation-detail">{hasList && section === 0 && <details className="rv-choose-record"><summary>Choose record<span>{item?.title ?? "New record"}</span></summary>{list}</details>}{detail}</div></div></div>}
|
|
|
|
|
</>;
|
|
|
|
|
return <><div className={`rv-host rv-host-${width} gc-host cs-host ${portalOpen ? "gc-portal-open" : ""}`}><header className="rv-topbar"><button aria-label="Toggle portal navigation" onClick={() => setPortalOpen(!portalOpen)}><Menu size={20}/></button><strong>Omics <span>Portal</span></strong><span className="rv-topbar-divider"/><span className="rv-topbar-location">Clinical data & research</span><span className="rv-topbar-user">Administrator <span className="rv-avatar">AD</span></span></header><div className="gc-host-body"><aside className="gc-portal" aria-label="Illustrative portal navigation"><p className="rv-eyebrow">Portal</p>{[[LayoutDashboard,"Overview"],[Users,"Patients"],[Database,"Clinical data"],[Settings2,"ThothII"]].map(([Icon,label]) => { const I = Icon as typeof Menu; return <div key={String(label)} className={label === "ThothII" ? "current" : ""}><I size={18}/><span>{String(label)}</span></div>; })}<small>Host shell preview</small></aside><div className="gc-mount"><div className="gc-brandline"><span className="gc-brand">Thoth<span>II</span></span><span>Clinical analytics workbench</span><small>Context shelf review · V4</small></div><div className={`gc-app gc-variant-${variant}`}><div className="gc-global-top">{contextShelf}</div>{operation && <section className="gc-operation-strip" aria-label="Current operation"><div><p className="rv-eyebrow">{busy ? "Operation running" : `Operation ${operation.state}`}</p><strong>{operation.label}</strong><small>{workspaces.find(w => w.id === operation.workspace)?.name ?? "Installation"} · {models.find(m => m.id === operation.model)?.name ?? operation.model}</small><p id="operation-lock">{busy ? "Workspace and model are locked. Finish this operation before starting another." : "Workspace and model are available again."}</p></div>{busy && <div className="rv-actions"><Button onClick={() => setOperation({ ...operation, state: "complete" })}>Demo: finish</Button><Button onClick={() => setOperation({ ...operation, state: "cancelled" })}>Demo: stop</Button><Button onClick={() => setOperation({ ...operation, state: "failed" })}>Demo: fail</Button></div>}</section>}<div className="gc-body"><aside className="gc-rail"><button className="gc-mobile-nav" onClick={() => setNavOpen(!navOpen)} aria-expanded={navOpen}><Menu size={17}/>Sessions & navigation<ChevronDown size={16}/></button><div className={`gc-navigation ${navOpen ? "open" : ""}`}><nav className="cs-core-nav" aria-label="Core navigation">{nav("core")}</nav><SessionRail workspace={workspace} ready={ready} busy={busy} activeId={active?.id} canReadAll={canReadAll} onOpen={s => { if (busy || !ready || !canLeave()) return; setNavOpen(false); if (s.status === "paused") setResumeTarget(s); else { resetDetail(); setQuestion(""); setActive(s); writeRoute({ ...route, page: "core" }); } requestAnimationFrame(() => mainRef.current?.focus()); }}/>{canReadAll && <details className="cs-admin-nav" open={variant === "A" ? true : undefined}><summary><Settings2 size={16}/>Administration<ChevronDown size={14}/></summary><nav aria-label="Administration navigation">{(["database","memory","evidence","workspace","pi"] as Page[]).map(nav)}</nav></details>}</div></aside><main ref={mainRef} tabIndex={-1} className={`gc-main rv-variant-${variant}`} aria-label={ready ? titles[page] : "Context required"}>{notice && <div className="rv-notice" role="status"><span>{notice}</span><button aria-label="Dismiss notice" onClick={() => setNotice("")}><X size={16}/></button></div>}{dirty && <div className="rv-dirty" role="status">Unsaved draft. Save or discard before changing workspace or page.</div>}{!ready ? gate : <>{resumeReview}{page === "core" && active && <div className="gc-new-question"><Button disabled={busy} onClick={() => { if (busy || !canLeave()) return; setActive(null); setQuestion(""); setDirty(false); setNotice("Ready for a new question in the selected workspace."); }}>New question<ArrowRight size={15}/></Button></div>}{page === "core" || page === "sessions" ? core : administration}</>}<footer className="gc-page-footer"><span>Prototype · Synthetic content only</span><span>{ready ? "Context applies across ThothII" : "Core and administration are locked"}</span></footer></main></div></div></div></div></div>
|
|
|
|
|
return <><div className={`rv-host rv-host-${width} gc-host cs-host ${portalOpen ? "gc-portal-open" : ""}`}><header className="rv-topbar"><button aria-label="Toggle portal navigation" onClick={() => setPortalOpen(!portalOpen)}><Menu size={20}/></button><strong>Omics <span>Portal</span></strong><span className="rv-topbar-divider"/><span className="rv-topbar-location">Clinical data & research</span><span className="rv-topbar-user">Administrator <span className="rv-avatar">AD</span></span></header><div className="gc-host-body"><aside className="gc-portal" aria-label="Illustrative portal navigation"><p className="rv-eyebrow">Portal</p>{[[LayoutDashboard,"Overview"],[Users,"Patients"],[Database,"Clinical data"],[Settings2,"ThothII"]].map(([Icon,label]) => { const I = Icon as typeof Menu; return <div key={String(label)} className={label === "ThothII" ? "current" : ""}><I size={18}/><span>{String(label)}</span></div>; })}<small>Host shell preview</small></aside><div className="gc-mount"><div className="gc-brandline"><span className="gc-brand">Thoth<span>II</span></span><span>Clinical analytics workbench</span><small>Context shelf review · V4</small></div><div className={`gc-app gc-variant-${variant}`}><div className="gc-global-top">{contextShelf}</div>{operation && <section className="gc-operation-strip" aria-label="Current operation"><div><p className="rv-eyebrow">{busy ? "Operation running" : `Operation ${operation.state}`}</p><strong>{operation.label}</strong><small>{workspaces.find(w => w.id === operation.workspace)?.name ?? "Installation"} · {models.find(m => m.id === operation.model)?.name ?? operation.model}</small><p id="operation-lock">{busy ? "Workspace and model are locked. Finish this operation before starting another." : "Workspace and model are available again."}</p></div>{busy && <div className="rv-actions"><Button onClick={() => setOperation({ ...operation, state: "complete" })}>Demo: finish</Button><Button onClick={() => setOperation({ ...operation, state: "cancelled" })}>Demo: stop</Button><Button onClick={() => setOperation({ ...operation, state: "failed" })}>Demo: fail</Button></div>}</section>}<div className={`gc-body ${coreActivityOpen && !admin ? "cs-activity-open" : ""}`}><aside className="gc-rail"><button className="gc-mobile-nav" onClick={() => setNavOpen(!navOpen)} aria-expanded={navOpen}><Menu size={17}/>Sessions & navigation<ChevronDown size={16}/></button><div className={`gc-navigation ${navOpen ? "open" : ""}`}><nav className="cs-core-nav" aria-label="Core navigation">{nav("core")}</nav><SessionRail workspace={workspace} ready={ready} busy={busy} activeId={active?.id} canReadAll={canReadAll} onOpen={s => { if (busy || !ready || !canLeave()) return; setNavOpen(false); if (s.status === "paused") setResumeTarget(s); else { resetDetail(); setQuestion(""); setActive(s); writeRoute({ ...route, page: "core" }); } requestAnimationFrame(() => mainRef.current?.focus()); }}/>{canReadAll && <details className="cs-admin-nav" open={variant === "A" ? true : undefined}><summary><Settings2 size={16}/>Administration<ChevronDown size={14}/></summary><nav aria-label="Administration navigation">{(["database","memory","evidence","workspace","pi"] as Page[]).map(nav)}</nav></details>}</div></aside><main ref={mainRef} tabIndex={-1} className={`gc-main rv-variant-${variant}`} aria-label={ready ? titles[page] : "Context required"}>{notice && <div className="rv-notice" role="status"><span>{notice}</span><button aria-label="Dismiss notice" onClick={() => setNotice("")}><X size={16}/></button></div>}{dirty && <div className="rv-dirty" role="status">Unsaved draft. Save or discard before changing workspace or page.</div>}{!ready ? gate : <>{resumeReview}{page === "core" && active && <div className="gc-new-question"><Button disabled={busy} onClick={() => { if (busy || !canLeave()) return; setActive(null); setQuestion(""); setDirty(false); setNotice("Ready for a new question in the selected workspace."); }}>New question<ArrowRight size={15}/></Button></div>}{page === "core" || page === "sessions" ? core : administration}</>}<footer className="gc-page-footer"><span>Prototype · Synthetic content only</span><span>{ready ? "Context applies across ThothII" : "Core and administration are locked"}</span></footer></main></div></div></div></div></div>
|
|
|
|
|
{import.meta.env.DEV && <aside className="rv-lab gc-lab" aria-label="Prototype review controls"><div className="rv-lab-main"><span className="rv-lab-tag">CONTEXT SHELF · V4</span><button aria-label="Previous variant" onClick={() => cycle(-1)}><ArrowLeft size={17}/></button><select aria-label="Layout variant" value={variant} onChange={e => { if (canLeave()) { writeRoute({ ...route, variant: e.target.value as Variant }, true); resetDetail(); setQuestion(""); setShelfOpen(!ready); } }}>{Object.entries(variants).map(([k,v]) => <option key={k} value={k}>{k} · {v}</option>)}</select><button aria-label="Next variant" onClick={() => cycle(1)}><ArrowRight size={17}/></button><span className="rv-lab-help">No server writes</span></div><details><summary>Review scenarios & state</summary><div className="rv-lab-options"><label>Host width<select aria-label="Host width" value={width} onChange={e => setWidth(e.target.value)}><option value="full">Available width</option><option value="compact">960 px host</option><option value="phone">390 px host</option></select></label><label>Availability<select aria-label="Availability scenario" disabled={busy} value={availability} onChange={e => { if (canLeave()) { setAvailability(e.target.value as Availability); resetDetail(); } }}><option value="available">Available</option><option value="unavailable">Provider unavailable</option><option value="no-workspaces">No workspaces</option><option value="no-models">No models</option><option value="needs-preprocessing">Needs preprocessing</option></select></label><Button disabled={busy} onClick={() => { if (busy || !canLeave()) return; setOperation(null); writeRoute({ ...route, workspace: "" }); setModelId(""); resetDetail(); setActive(null); setQuestion(""); setResumeTarget(null); setShelfOpen(true); }}>Clear current context</Button><Button disabled={busy || !ready} onClick={() => { if (!busy && canLeave()) { setResumeTarget(sessions[2]); setNavOpen(false); } }}>Demo: Genetics session link</Button><Button disabled={busy} onClick={() => { if (busy || !canLeave()) return; if (!forget()) { setNotice("Browser storage could not be cleared."); return; } location.reload(); }}>Forget saved choices</Button><Button disabled={busy} onClick={() => { if (busy || !canLeave()) return; setCanReadAll(!canReadAll); writeRoute({ ...route, page: "core" }); resetDetail(); }}>Demo access: {canReadAll ? "Administrator" : "Analyst"}</Button><p>Workspace source: {sources.workspace} · Model source: {sources.model}</p><p>Page: {page} · Workspace: {workspace || "none"} · Model: {model?.name ?? "none"} · Gate: {ready ? "open" : "closed"} · Draft: {dirty ? "unsaved" : "clean"} · Operation: {operation?.state ?? "idle"}</p><p>Only explicit workspace/model choices persist on this browser. Reload resets simulated operations and drafts. Defaults are illustrative. V1/V2/V3 are preserved on ports 5174/5175/5176.</p></div></details></aside>}
|
|
|
|
|
</>;
|
|
|
|
|
}
|
|
|
|
|