Skip to content

Commit 7f8e2ea

Browse files
committed
feat: enhance reasoning handling by implementing autodetection of <think> blocks and updating rendering logic
1 parent cedb6e0 commit 7f8e2ea

File tree

1 file changed

+41
-6
lines changed

1 file changed

+41
-6
lines changed

src/lib/components/chat/ChatMessage.svelte

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@
7474
let urlNotTrailing = $derived(page.url.pathname.replace(/\/$/, ""));
7575
let downloadLink = $derived(urlNotTrailing + `/message/${message.id}/prompt`);
7676
77+
// Zero-config reasoning autodetection: detect <think> blocks in content
78+
const THINK_BLOCK_REGEX = /(<think>[\s\S]*?(?:<\/think>|$))/g;
79+
let hasServerReasoning = $derived(
80+
reasoningUpdates &&
81+
reasoningUpdates.length > 0 &&
82+
!!message.reasoning &&
83+
message.reasoning.trim().length > 0
84+
);
85+
let hasClientThink = $derived(!hasServerReasoning && /<think>/.test(message.content));
86+
7787
$effect(() => {
7888
if (isCopied) {
7989
setTimeout(() => {
@@ -119,7 +129,7 @@
119129
</div>
120130
{/if}
121131

122-
{#if reasoningUpdates && reasoningUpdates.length > 0 && message.reasoning && message.reasoning.trim().length > 0}
132+
{#if hasServerReasoning}
123133
{@const summaries = reasoningUpdates
124134
.filter((u) => u.subtype === MessageReasoningUpdateType.Status)
125135
.map((u) => u.status)}
@@ -136,11 +146,36 @@
136146
<IconLoading classNames="loading inline ml-2 first:ml-0" />
137147
{/if}
138148

139-
<div
140-
class="prose max-w-none dark:prose-invert max-sm:prose-sm prose-headings:font-semibold prose-h1:text-lg prose-h2:text-base prose-h3:text-base prose-pre:bg-gray-800 dark:prose-pre:bg-gray-900"
141-
>
142-
<MarkdownRenderer content={message.content} />
143-
</div>
149+
{#if hasClientThink}
150+
{#each message.content.split(THINK_BLOCK_REGEX) as part, i}
151+
{#if part && part.startsWith("<think>")}
152+
{@const isClosed = part.endsWith("</think>")}
153+
{@const thinkContent = part.slice(7, isClosed ? -8 : undefined)}
154+
{@const summary =
155+
isClosed
156+
? (thinkContent.trim().split(/\n+/)[0] || "Reasoning")
157+
: "Thinking..."}
158+
159+
<OpenReasoningResults
160+
summary={summary}
161+
content={thinkContent}
162+
loading={isLast && loading && !isClosed}
163+
/>
164+
{:else if part && part.trim().length > 0}
165+
<div
166+
class="prose max-w-none dark:prose-invert max-sm:prose-sm prose-headings:font-semibold prose-h1:text-lg prose-h2:text-base prose-h3:text-base prose-pre:bg-gray-800 dark:prose-pre:bg-gray-900"
167+
>
168+
<MarkdownRenderer content={part} />
169+
</div>
170+
{/if}
171+
{/each}
172+
{:else}
173+
<div
174+
class="prose max-w-none dark:prose-invert max-sm:prose-sm prose-headings:font-semibold prose-h1:text-lg prose-h2:text-base prose-h3:text-base prose-pre:bg-gray-800 dark:prose-pre:bg-gray-900"
175+
>
176+
<MarkdownRenderer content={message.content} />
177+
</div>
178+
{/if}
144179
</div>
145180
</div>
146181

0 commit comments

Comments
 (0)