From 2e79dd122e5664353e02e0d33715ae8c1041a379 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sun, 7 Jun 2026 17:14:40 +0900 Subject: feat(chat): restyle thinking — visible bubble, collapse, title swap, persisted open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thinking renders inside a visible rounded-card bubble (like tool calls), capped to the same max-w-5xl column as assistant text. Uses a DaisyUI checkbox collapse (no arrow/plus icon) with smooth animation. Title reads "Thinking" + loading-dots while the model is actively generating, then flips to "Thoughts" with no dots once done. Open/closed state persists across the generating→completed→sealed transition via stable ordinal keys (per-conversation isolation via {#key} in App). Added optional streaming flag to RenderedChunk (pure selector, only on the accumulating chunk). --- src/features/chat/ui/ChatView.svelte | 52 ++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) (limited to 'src/features/chat/ui') diff --git a/src/features/chat/ui/ChatView.svelte b/src/features/chat/ui/ChatView.svelte index 76d122d..3a078fb 100644 --- a/src/features/chat/ui/ChatView.svelte +++ b/src/features/chat/ui/ChatView.svelte @@ -4,6 +4,27 @@ let { chunks }: { chunks: readonly RenderedChunk[] } = $props(); const groups = $derived(groupRenderedChunks(chunks)); + + // Stable per-row keys. Thinking blocks get an ordinal key (`think`) that + // survives the provisional→committed (seq null → seq N) transition, so the + // collapse's open/close state is NOT lost when a turn seals. (App isolates + // these keys per conversation via {#key}.) + const rows = $derived.by(() => { + let thinking = 0; + return groups.map((group, i) => { + let key: string; + if (group.kind === "tool-batch") { + key = `b${group.stepId}`; + } else if (group.chunk.chunk.type === "thinking") { + key = `think${thinking++}`; + } else if (group.chunk.seq != null) { + key = `c${group.chunk.seq}`; + } else { + key = `p${i}`; + } + return { group, key }; + }); + }); {#snippet chunkRow(rendered: RenderedChunk)} @@ -16,6 +37,26 @@ {/if} + {:else if rendered.chunk.type === "thinking"} + +
+
+
+ +
+ {rendered.streaming ? "Thinking" : "Thoughts"} + {#if rendered.streaming} + + {/if} +
+
+

{rendered.chunk.text}

+
+
+
+
{:else if rendered.chunk.type === "tool-call" || rendered.chunk.type === "tool-result"} +
{#if rendered.chunk.type === "text"}

{rendered.chunk.text}

- {:else if rendered.chunk.type === "thinking"} -
- Thinking -

{rendered.chunk.text}

-
{:else if rendered.chunk.type === "error"}