refac: think tag

This commit is contained in:
Timothy Jaeryang Baek 2025-01-22 09:24:40 -08:00
parent cf470e70e2
commit 9feed97f22
4 changed files with 42 additions and 30 deletions

View File

@ -1166,7 +1166,7 @@ async def process_chat_response(
) )
# Format reasoning with <details> tag # Format reasoning with <details> tag
content = f"{ongoing_content}<details>\n<summary>Thought for {reasoning_duration} seconds</summary>\n{reasoning_display_content}\n</details>\n" content = f'{ongoing_content}<details type="reasoning" done="true">\n<summary>Thought for {reasoning_duration} seconds</summary>\n{reasoning_display_content}\n</details>\n'
else: else:
content = "" content = ""
@ -1183,7 +1183,7 @@ async def process_chat_response(
) )
# Show ongoing thought process # Show ongoing thought process
content = f"{ongoing_content}<details>\n<summary>Thinking… <loading/></summary>\n{reasoning_display_content}\n</details>\n" content = f'{ongoing_content}<details type="reasoning" done="false">\n<summary>Thinking…</summary>\n{reasoning_display_content}\n</details>\n'
if ENABLE_REALTIME_CHAT_SAVE: if ENABLE_REALTIME_CHAT_SAVE:
# Save message in the database # Save message in the database

View File

@ -195,11 +195,7 @@
</ul> </ul>
{/if} {/if}
{:else if token.type === 'details'} {:else if token.type === 'details'}
<Collapsible <Collapsible title={token.summary} attributes={token?.attributes} className="w-fit space-y-1">
title={token.summary}
isLoading={token?.isLoading ?? false}
className="w-fit space-y-1"
>
<div class=" mb-1.5" slot="content"> <div class=" mb-1.5" slot="content">
<svelte:self id={`${id}-${tokenIdx}-d`} tokens={marked.lexer(token.text)} /> <svelte:self id={`${id}-${tokenIdx}-d`} tokens={marked.lexer(token.text)} />
</div> </div>

View File

@ -16,7 +16,7 @@
export let buttonClassName = export let buttonClassName =
'w-fit text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition'; 'w-fit text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition';
export let title = null; export let title = null;
export let isLoading = false; export let attributes = null;
export let grow = false; export let grow = false;
@ -37,12 +37,13 @@
}} }}
> >
<div <div
class=" w-full font-medium flex items-center justify-between gap-2 {isLoading === true class=" w-full font-medium flex items-center justify-between gap-2 {attributes?.done !==
'true'
? 'shimmer' ? 'shimmer'
: ''} : ''}
" "
> >
{#if isLoading} {#if attributes?.done !== 'true'}
<div> <div>
<Spinner className="size-4" /> <Spinner className="size-4" />
</div> </div>

View File

@ -1,5 +1,5 @@
// Helper function to find matching closing tag // Helper function to find matching closing tag
function findMatchingClosingTag(src, openTag, closeTag) { function findMatchingClosingTag(src: string, openTag: string, closeTag: string): number {
let depth = 1; let depth = 1;
let index = openTag.length; let index = openTag.length;
while (depth > 0 && index < src.length) { while (depth > 0 && index < src.length) {
@ -15,29 +15,37 @@ function findMatchingClosingTag(src, openTag, closeTag) {
return depth === 0 ? index + closeTag.length : -1; return depth === 0 ? index + closeTag.length : -1;
} }
function detailsTokenizer(src) { // Function to parse attributes from tag
const detailsRegex = /^<details>\n/; function parseAttributes(tag: string): { [key: string]: string } {
const summaryRegex = /^<summary>(.*?)<\/summary>\n/; const attributes: { [key: string]: string } = {};
const loadingRegex = /<loading\s*\/>/; // Detect <loading/> const attrRegex = /(\w+)="(.*?)"/g;
let match;
while ((match = attrRegex.exec(tag)) !== null) {
attributes[match[1]] = match[2];
}
return attributes;
}
if (detailsRegex.test(src)) { function detailsTokenizer(src: string) {
const endIndex = findMatchingClosingTag(src, '<details>', '</details>'); // Updated regex to capture attributes inside <details>
const detailsRegex = /^<details(\s+[^>]*)?>\n/;
const summaryRegex = /^<summary>(.*?)<\/summary>\n/;
const detailsMatch = detailsRegex.exec(src);
if (detailsMatch) {
const endIndex = findMatchingClosingTag(src, '<details', '</details>');
if (endIndex === -1) return; if (endIndex === -1) return;
const fullMatch = src.slice(0, endIndex); const fullMatch = src.slice(0, endIndex);
let content = fullMatch.slice(10, -10).trim(); // Remove <details> and </details> const detailsTag = detailsMatch[0];
const attributes = parseAttributes(detailsTag); // Parse attributes from <details>
let content = fullMatch.slice(detailsTag.length, -10).trim(); // Remove <details> and </details>
let summary = ''; let summary = '';
let isLoading = false;
const summaryMatch = summaryRegex.exec(content); const summaryMatch = summaryRegex.exec(content);
if (summaryMatch) { if (summaryMatch) {
summary = summaryMatch[1].trim(); summary = summaryMatch[1].trim();
// Detect and remove <loading/>
if (loadingRegex.test(summary)) {
isLoading = true;
summary = summary.replace(loadingRegex, '').trim(); // Remove <loading/> from summary
}
content = content.slice(summaryMatch[0].length).trim(); content = content.slice(summaryMatch[0].length).trim();
} }
@ -46,22 +54,29 @@ function detailsTokenizer(src) {
raw: fullMatch, raw: fullMatch,
summary: summary, summary: summary,
text: content, text: content,
isLoading: isLoading // Include loading property to indicate if <loading/> was present attributes: attributes // Include extracted attributes from <details>
}; };
} }
} }
function detailsStart(src) { function detailsStart(src: string) {
return src.match(/^<details>/) ? 0 : -1; return src.match(/^<details>/) ? 0 : -1;
} }
function detailsRenderer(token) { function detailsRenderer(token: any) {
return `<details> const attributesString = token.attributes
? Object.entries(token.attributes)
.map(([key, value]) => `${key}="${value}"`)
.join(' ')
: '';
return `<details ${attributesString}>
${token.summary ? `<summary>${token.summary}</summary>` : ''} ${token.summary ? `<summary>${token.summary}</summary>` : ''}
${token.text} ${token.text}
</details>`; </details>`;
} }
// Extension wrapper function
function detailsExtension() { function detailsExtension() {
return { return {
name: 'details', name: 'details',