diff --git a/frontend/src/shell/CentralStatus.test.tsx b/frontend/src/shell/CentralStatus.test.tsx new file mode 100644 index 00000000..b72f8dfe --- /dev/null +++ b/frontend/src/shell/CentralStatus.test.tsx @@ -0,0 +1,20 @@ +import { render, screen } from "@testing-library/react"; +import { beforeEach } from "vitest"; +import { useSessionStore } from "../store/sessionStore"; +import { CentralStatus } from "./CentralStatus"; + +beforeEach(() => useSessionStore.getState().resetSession()); + +test("renders nothing when there is no user entry and no step messages", () => { + const { container } = render(); + expect(container).toBeEmptyDOMElement(); +}); + +test("echoes the last user entry and the step messages", () => { + const st = useSessionStore.getState(); + st.setLastUserEntry({ kind: "input", text: "how many patients?" }); + st.applyEvent({ type: "info", level: "info", text: "Searching the schema…" }); + render(); + expect(screen.getByText("how many patients?")).toBeInTheDocument(); + expect(screen.getByText("Searching the schema…")).toBeInTheDocument(); +}); diff --git a/frontend/src/shell/CentralStatus.tsx b/frontend/src/shell/CentralStatus.tsx new file mode 100644 index 00000000..ffc7dcc4 --- /dev/null +++ b/frontend/src/shell/CentralStatus.tsx @@ -0,0 +1,35 @@ +import { useSessionStore } from "../store/sessionStore"; + +/** The minimal central view: the user's last input/choice + the gate's curated + * messages for the current step. The verbose model stream lives in the left panel. */ +export function CentralStatus() { + const lastUserEntry = useSessionStore((s) => s.lastUserEntry); + const stepMessages = useSessionStore((s) => s.stepMessages); + if (!lastUserEntry && stepMessages.length === 0) return null; + + return ( +
+ {lastUserEntry && ( +
+ + {lastUserEntry.kind === "input" ? "You asked" : "You chose"} + +

{lastUserEntry.text}

+
+ )} + {stepMessages.map((m, i) => ( +

+ {m.text} +

+ ))} +
+ ); +} diff --git a/frontend/src/shell/ModelActivityPanel.test.tsx b/frontend/src/shell/ModelActivityPanel.test.tsx new file mode 100644 index 00000000..8e0e0b1b --- /dev/null +++ b/frontend/src/shell/ModelActivityPanel.test.tsx @@ -0,0 +1,20 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, vi } from "vitest"; +import { useSessionStore } from "../store/sessionStore"; +import { ModelActivityPanel } from "./ModelActivityPanel"; + +beforeEach(() => useSessionStore.getState().resetSession()); + +test("renders the streamed model transcript", () => { + useSessionStore.getState().applyEvent({ type: "text_delta", text: "Promoting table dim_patient." }); + render(); + expect(screen.getByText(/Promoting table dim_patient/)).toBeInTheDocument(); +}); + +test("close button calls onClose", async () => { + const onClose = vi.fn(); + render(); + await userEvent.click(screen.getByRole("button", { name: /close/i })); + expect(onClose).toHaveBeenCalled(); +}); diff --git a/frontend/src/shell/ModelActivityPanel.tsx b/frontend/src/shell/ModelActivityPanel.tsx new file mode 100644 index 00000000..3052d324 --- /dev/null +++ b/frontend/src/shell/ModelActivityPanel.tsx @@ -0,0 +1,21 @@ +import { X } from "lucide-react"; +import { Transcript } from "./Transcript"; +import { Button } from "../components/ui/button"; + +/** Left drawer showing the active session's streamed model text ("model activity"), + * opened on demand from the work-in-progress icon. */ +export function ModelActivityPanel({ onClose }: { onClose: () => void }) { + return ( + + ); +}