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,64 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ChatWorkspace } from "@/components/chat/ChatWorkspace";
|
||||
import { apiFetch } from "@/lib/api/http";
|
||||
import { AgentSchema } from "@/lib/api/schemas";
|
||||
import {
|
||||
fetchHistory,
|
||||
fetchSessions,
|
||||
type HistoryMessage,
|
||||
} from "@/lib/api/sessions";
|
||||
import { decodeSessionKeyParam, formatSessionKey } from "@/lib/url/session-key";
|
||||
import type { UiMessage } from "@/lib/gateway/transcript";
|
||||
|
||||
const SettingsSchema = z.object({ agent: AgentSchema }).passthrough();
|
||||
|
||||
function toUiMessage(entry: HistoryMessage): UiMessage | null {
|
||||
if (entry.role === "system") {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id: entry.id,
|
||||
role: entry.role,
|
||||
text: entry.content.text,
|
||||
steps: entry.steps.map((step) => ({
|
||||
seq: step.seq,
|
||||
tool: step.tool_name ?? step.kind,
|
||||
input: step.input,
|
||||
output: step.output,
|
||||
status: step.status,
|
||||
})),
|
||||
status: "complete",
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ChatPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ clawId: string; sessionKey: string }>;
|
||||
}) {
|
||||
const { clawId, sessionKey: rawKey } = await params;
|
||||
const key = formatSessionKey(decodeSessionKeyParam(rawKey));
|
||||
|
||||
const [settings, history, sessions] = await Promise.all([
|
||||
apiFetch(
|
||||
SettingsSchema,
|
||||
`/api/claws/settings/full?clawId=${encodeURIComponent(clawId)}`,
|
||||
),
|
||||
fetchHistory(key),
|
||||
fetchSessions(clawId),
|
||||
]);
|
||||
|
||||
const initialMessages = history
|
||||
.map(toUiMessage)
|
||||
.filter((m): m is UiMessage => m !== null);
|
||||
|
||||
return (
|
||||
<ChatWorkspace
|
||||
agent={settings.agent}
|
||||
sessionKey={key}
|
||||
initialMessages={initialMessages}
|
||||
sessions={sessions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { createSession, fetchSessions } from "@/lib/api/sessions";
|
||||
import { encodeSessionKeyParam } from "@/lib/url/session-key";
|
||||
|
||||
// Clicking a claw in the rail lands here: resume the most recent session,
|
||||
// or open a fresh one (sessions are resumable, §6).
|
||||
export default async function ClawHome({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ clawId: string }>;
|
||||
}) {
|
||||
const { clawId } = await params;
|
||||
const sessions = await fetchSessions(clawId);
|
||||
const target = sessions[0] ?? (await createSession(clawId));
|
||||
redirect(
|
||||
`/claws/${clawId}/chat/${encodeSessionKeyParam(target.sessionKey)}`,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { CreateClawForm } from "@/components/wizard/CreateClawForm";
|
||||
|
||||
export default function NewClawPage() {
|
||||
return (
|
||||
<section className="flex min-h-dvh flex-col items-center justify-center gap-6 px-4 motion-safe:animate-[fade-up_var(--duration-normal)_var(--ease-app)]">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
Create your Claw
|
||||
</h1>
|
||||
<p className="pt-1 text-sm text-muted-foreground">
|
||||
Claws help get your work done.
|
||||
</p>
|
||||
</div>
|
||||
<CreateClawForm />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user