P1 exit: chat UI, sessions column, agent create — 11 E2E journeys green
- Stream layer: incremental SSE parser tested against bytes captured from
the REAL gateway (chunking-independent, UTF-8-safe, heartbeat-tolerant);
Zod gateway event schemas; pure transcript reducer (optimistic send,
delta streaming, step traces, resume dedupe, error states)
- Same-origin /api proxy route: httpOnly cookie -> bearer, unbuffered SSE
passthrough; NuqsAdapter in root layout
- Chat workspace: route /claws/{id}/chat/{key} (RSC history + settings),
WelcomeState with suggested prompts, MessageList (right user bubbles,
left agent messages, blink caret), collapsible StepTrace, Composer
(Enter sends, Shift+Enter newline)
- SessionsColumn in 208px SlidePanel (?sessions=1): search, relative
times, active coral border, new session; /claws/{id} resumes latest or
opens fresh; minimal create-claw form; rail + button and roster links
- P1 exit E2E: scripted reply streams, tool step trace survives reload,
separate transcripts across sessions with column switching, create claw
and chat immediately
83 Rust + 56 frontend unit/component tests + 11 Playwright journeys.
Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
32008c9ef0
commit
9f9f507c15
@@ -0,0 +1,89 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useQueryStates } from "nuqs";
|
||||
|
||||
import type { Agent } from "@/lib/api/schemas";
|
||||
import type { Session } from "@/lib/api/sessions";
|
||||
import { useChat } from "@/lib/gateway/use-chat";
|
||||
import type { UiMessage } from "@/lib/gateway/transcript";
|
||||
import { encodeSessionKeyParam } from "@/lib/url/session-key";
|
||||
import { panelParsers } from "@/lib/url/panel-params";
|
||||
import { SlidePanel } from "@/components/ui/SlidePanel";
|
||||
import { SessionsColumn } from "@/components/sessions/SessionsColumn";
|
||||
import { ChatHeader } from "./ChatHeader";
|
||||
import { Composer } from "./Composer";
|
||||
import { MessageList } from "./MessageList";
|
||||
import { WelcomeState } from "./WelcomeState";
|
||||
|
||||
interface ChatWorkspaceProps {
|
||||
agent: Agent;
|
||||
sessionKey: string;
|
||||
initialMessages: UiMessage[];
|
||||
sessions: Session[];
|
||||
}
|
||||
|
||||
/** The chat column orchestrator (§5/§6). Search-param state is client-only
|
||||
* and shallow; navigating sessions is a real path navigation. */
|
||||
export function ChatWorkspace({
|
||||
agent,
|
||||
sessionKey,
|
||||
initialMessages,
|
||||
sessions,
|
||||
}: ChatWorkspaceProps) {
|
||||
const router = useRouter();
|
||||
const { state, send } = useChat(agent.id, sessionKey, initialMessages);
|
||||
const [draft, setDraft] = useState("");
|
||||
const [{ sessions: showSessions }] = useQueryStates(panelParsers, {
|
||||
shallow: true,
|
||||
});
|
||||
|
||||
function openSession(session: Session) {
|
||||
router.push(
|
||||
`/claws/${agent.id}/chat/${encodeSessionKeyParam(session.sessionKey)}?sessions=1`,
|
||||
);
|
||||
}
|
||||
|
||||
async function newSession() {
|
||||
const res = await fetch("/api/sessions", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ clawId: agent.id, title: "" }),
|
||||
});
|
||||
if (res.ok) {
|
||||
const session = (await res.json()) as Session;
|
||||
router.push(
|
||||
`/claws/${agent.id}/chat/${encodeSessionKeyParam(session.sessionKey)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-dvh">
|
||||
<SlidePanel open={showSessions} width={208} label="Sessions">
|
||||
<SessionsColumn
|
||||
sessions={sessions}
|
||||
activeSessionKey={sessionKey}
|
||||
onOpen={openSession}
|
||||
onNewSession={newSession}
|
||||
/>
|
||||
</SlidePanel>
|
||||
<section className="flex min-w-0 flex-1 flex-col">
|
||||
<ChatHeader agent={agent} onNewSession={newSession} />
|
||||
{state.messages.length === 0 ? (
|
||||
<WelcomeState agent={agent} onPick={setDraft} />
|
||||
) : (
|
||||
<MessageList messages={state.messages} agent={agent} />
|
||||
)}
|
||||
<Composer
|
||||
agentName={agent.name}
|
||||
disabled={state.streaming}
|
||||
onSend={send}
|
||||
draft={draft}
|
||||
onDraftChange={setDraft}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user