feat(frontend): sessions list/create + steering + resume + selectors

- NavSessions: left nav listing sessions via listSessions (TanStack Query),
  clicking resumes via resumeSession and sets active; active item highlighted
- NewSessionDialog: ShadCn dialog with question textarea, workspace native
  select (from listWorkspaces), graceful model field (dropdown when models
  list is non-empty, free-text input when empty per §9 degradation),
  optional thinking/provider fields; try/catch surfaces errors inline
- SteerInput: text input + button POSTs to postSteer, clears on send,
  supports Enter key; only shown when a session is active
- WorkflowBar: renders 8 phases (F1..F8) with data-active highlighting
  driven by useSessionStore.currentPhase (new store field + setPhase action)
- AppShell: wires all four components; right artifact sidebar now collapsible
  via a toggle button (useState sidebarOpen); replaces old "Nuova domanda"
  button with NewSessionDialog trigger
- f1-loop.test: updated to drive through the new dialog flow (open → fill
  question → submit → wait for SSE → respond)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 13:06:01 +02:00
co-authored by Claude Sonnet 4.6
parent 3ecfed5d3d
commit 544882f95b
9 changed files with 547 additions and 22 deletions
+35 -19
View File
@@ -1,46 +1,62 @@
import { useState } from "react";
import { createSession } from "../api/sessions";
import { useSessionStream } from "../stream/useSessionStream";
import { WidgetHost } from "./WidgetHost";
import { NavSessions } from "./NavSessions";
import { NewSessionDialog } from "./NewSessionDialog";
import { SteerInput } from "./SteerInput";
import { WorkflowBar } from "./WorkflowBar";
export function AppShell() {
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
const [sidebarOpen, setSidebarOpen] = useState(true);
useSessionStream(activeSessionId);
async function handleNuovaDomanda() {
const { id } = await createSession({ workspace: "default", question: "" });
function handleSessionSelect(id: string) {
setActiveSessionId(id);
}
function handleCreated(id: string) {
setActiveSessionId(id);
}
return (
<div className="flex h-screen">
{/* Left nav / session list */}
{/* Left nav: new session button + sessions list */}
<aside className="w-48 border-r p-2 flex flex-col gap-2">
<button
className="border rounded px-2 py-1 text-sm hover:bg-accent"
onClick={handleNuovaDomanda}
>
Nuova domanda
</button>
<NewSessionDialog onCreated={handleCreated} />
<NavSessions
activeSessionId={activeSessionId}
onSessionSelect={handleSessionSelect}
/>
</aside>
<div className="flex flex-col flex-1">
<div className="flex flex-col flex-1 min-w-0">
{/* Top workflow bar */}
<header className="border-b px-4 py-2 text-sm text-muted-foreground">
Workflow
<header className="border-b px-4 py-2 flex items-center gap-4">
<WorkflowBar />
<button
className="ml-auto text-xs text-muted-foreground hover:text-foreground border rounded px-2 py-0.5"
onClick={() => setSidebarOpen((v) => !v)}
aria-label={sidebarOpen ? "Chiudi sidebar" : "Apri sidebar"}
>
{sidebarOpen ? "←" : "→"}
</button>
</header>
{/* Center: chat + input */}
<main className="flex-1 overflow-auto p-4">
{/* Center: chat + steer input */}
<main className="flex-1 overflow-auto p-4 flex flex-col gap-2">
<WidgetHost sessionId={activeSessionId} />
{activeSessionId && <SteerInput sessionId={activeSessionId} />}
</main>
</div>
{/* Right artifact sidebar */}
<aside className="w-64 border-l p-2">
<p className="text-xs text-muted-foreground">Artefatti</p>
</aside>
{/* Right artifact sidebar — collapsible */}
{sidebarOpen && (
<aside className="w-64 border-l p-2 flex flex-col">
<p className="text-xs text-muted-foreground">Artefatti</p>
</aside>
)}
</div>
);
}