import { useSessionStream } from "../stream/useSessionStream"; import { useSessionStore } from "../store/sessionStore"; import { WidgetHost } from "./WidgetHost"; import { Transcript } from "./Transcript"; import { NavSessions } from "./NavSessions"; import { SteerInput, ComposerFooter } from "./SteerInput"; import { WorkflowBar } from "./WorkflowBar"; import { Button } from "../components/ui/button"; import { Toaster } from "../components/ui/sonner"; import { closeSession, listSessions, resumeSession } from "../api/sessions"; import type { SessionSummary } from "../api/types"; import { useQuery } from "@tanstack/react-query"; import { useRef, useState } from "react"; /** * The page is designed to live INSIDE the Omics Portal chrome (its left sidebar * + topbar) once embedded, so this shell intentionally has no left rail and no * top header of its own. The session rail sits on the RIGHT, mirroring the * portal's left sidebar, and the workflow phases ride a slim strip above the chat. */ export function AppShell() { const [activeSessionId, setActiveSessionId] = useState(null); const { data: sessions = [] } = useQuery({ queryKey: ["sessions"], queryFn: listSessions, refetchInterval: 10_000, }); const composerRef = useRef(null); // The harness "holds the ball" whenever a session is live and no widget is // waiting on the human; once a widget appears, input is back in the user's court. const pendingWidget = useSessionStore((s) => s.pendingWidget); const resetSession = useSessionStore((s) => s.resetSession); const working = Boolean(activeSessionId) && !pendingWidget; useSessionStream(activeSessionId); function startNewSession() { resetSession(); setActiveSessionId(null); requestAnimationFrame(() => composerRef.current?.focus()); } async function stopSession() { if (!activeSessionId) return; try { await closeSession(activeSessionId); } finally { resetSession(); setActiveSessionId(null); } } return (
{/* Conversation column */}
{activeSessionId && (
)}
{activeSessionId ? ( <> ) : ( )}
{/* Sticky composer: the box stays pinned while the conversation scrolls above. */}
{/* Right session rail — symmetric to the portal's left sidebar */}
); } /** Brand-tinted spinner shown while the assistant/harness is working. */ function WorkingSpinner({ className }: { className?: string }) { return ( ); } function EmptyState() { return (
ThothII

Build datamarts from your data warehouse through a guided, human-in-the-loop workflow. Type your question in the box below to begin a session.

); }