diff --git a/docs/screenshots/assistant.png b/docs/screenshots/assistant.png index 63f9259..47a687a 100644 Binary files a/docs/screenshots/assistant.png and b/docs/screenshots/assistant.png differ diff --git a/docs/screenshots/chat.png b/docs/screenshots/chat.png index 1dd3d39..11a2276 100644 Binary files a/docs/screenshots/chat.png and b/docs/screenshots/chat.png differ diff --git a/docs/screenshots/mobile-chat.png b/docs/screenshots/mobile-chat.png index 3b1cf47..e8defb8 100644 Binary files a/docs/screenshots/mobile-chat.png and b/docs/screenshots/mobile-chat.png differ diff --git a/server/scripts/mock-rpc-host.ts b/server/scripts/mock-rpc-host.ts index d8e1599..930c6df 100644 --- a/server/scripts/mock-rpc-host.ts +++ b/server/scripts/mock-rpc-host.ts @@ -142,7 +142,12 @@ function streamCannedTurn() { ]; let index = 0; const tick = () => { - if (index >= steps.length) return; + if (index >= steps.length) { + // Turn finished: drop the timer so get_state reports isStreaming + // false again — the UI keys its queue/stop mode off that flag. + streamTimer = undefined; + return; + } steps[index]!(); index++; streamTimer = setTimeout(tick, 250); @@ -253,7 +258,9 @@ function handleFrame(frame: unknown) { pendingPrompt = { id, command: type }; write({ id, type: "response", command: type, success: true }); write({ type: "turn_start" }); - // Echo the prompt back as a user message, images included. + // Echo the prompt back as a user message, images included. Real omp + // finalizes the entry with message_end; without it the UI keeps the + // message in streaming state forever ("agent is working…" never clears). const images = Array.isArray(obj.images) ? (obj.images as unknown[]) : []; const content = images.length > 0 @@ -262,10 +269,9 @@ function handleFrame(frame: unknown) { ...images.map(image => ({ type: "image", ...(image as Record) })), ] : String(obj.message ?? ""); - write({ - type: "message_start", - message: { role: "user", content, timestamp: Date.now() }, - }); + const echo = { role: "user", content, timestamp: Date.now() }; + write({ type: "message_start", message: echo }); + write({ type: "message_end", message: echo }); streamCannedTurn(); return; } diff --git a/web/src/components/chat/Composer.tsx b/web/src/components/chat/Composer.tsx index 49d1302..81a178d 100644 --- a/web/src/components/chat/Composer.tsx +++ b/web/src/components/chat/Composer.tsx @@ -506,11 +506,14 @@ export function Composer({ store, snapshot, assistant = false }: ComposerProps) )} -
+ {/* min-w-0 (not shrink-0): the model chip is the elastic item — + it truncates so the row still fits narrow screens instead of + pushing the primary button out of the composer. */} +
{/* Queued message count */} {queuedCount > 0 ? ( @@ -527,10 +530,10 @@ export function Composer({ store, snapshot, assistant = false }: ComposerProps) type="button" onClick={() => toggleMenu("model")} title="Switch model" - className="flex h-7 items-center gap-1.5 rounded-md border border-border bg-surface-2 px-2.5 font-mono text-[10.5px] text-fg-1 hover:border-border-strong" + className="flex h-7 min-w-0 items-center gap-1.5 rounded-md border border-border bg-surface-2 px-2.5 font-mono text-[10.5px] text-fg-1 hover:border-border-strong" > - + {modelLabel ?? (currentModel ? `${currentModel.provider}/${currentModel.id}` : "model")} @@ -562,7 +565,7 @@ export function Composer({ store, snapshot, assistant = false }: ComposerProps)
{/* Thinking level */} -
+