From 261aec8c864646eb7215be0d5c14a79cad3cb93f Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 24 Mar 2026 19:26:14 -0500 Subject: [PATCH] enh: consecutive details rendering --- .../Markdown/ConsecutiveDetailsGroup.svelte | 127 ++++++++++++++++++ .../Messages/Markdown/MarkdownTokens.svelte | 99 +++++++++++++- .../components/common/ToolCallDisplay.svelte | 4 +- 3 files changed, 224 insertions(+), 6 deletions(-) create mode 100644 src/lib/components/chat/Messages/Markdown/ConsecutiveDetailsGroup.svelte diff --git a/src/lib/components/chat/Messages/Markdown/ConsecutiveDetailsGroup.svelte b/src/lib/components/chat/Messages/Markdown/ConsecutiveDetailsGroup.svelte new file mode 100644 index 000000000..e19fe35dd --- /dev/null +++ b/src/lib/components/chat/Messages/Markdown/ConsecutiveDetailsGroup.svelte @@ -0,0 +1,127 @@ + + +
+ + + + {#if open} +
+
+ +
+
+ {/if} +
diff --git a/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte b/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte index cac7cdde6..2c6df670d 100644 --- a/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte +++ b/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte @@ -20,6 +20,7 @@ import ToolCallDisplay from '$lib/components/common/ToolCallDisplay.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte'; import Download from '$lib/components/icons/Download.svelte'; + import ConsecutiveDetailsGroup from './ConsecutiveDetailsGroup.svelte'; import HtmlToken from './HTMLToken.svelte'; import Clipboard from '$lib/components/icons/Clipboard.svelte'; @@ -52,6 +53,51 @@ return 'h' + depth; }; + const GROUPABLE_DETAIL_TYPES = new Set(['tool_calls', 'reasoning', 'code_interpreter']); + + const isGroupableDetailToken = (token: Token & { attributes?: { type?: string } }) => { + return token?.type === 'details' && GROUPABLE_DETAIL_TYPES.has(token?.attributes?.type ?? ''); + }; + + const getDisplayTokens = (tokenList: Token[] = []) => { + const displayTokens = []; + let detailGroup = []; + + const flushDetailGroup = () => { + if (detailGroup.length > 1) { + displayTokens.push({ + type: 'detail_group', + items: [...detailGroup] + }); + } else if (detailGroup.length === 1) { + displayTokens.push(detailGroup[0]); + } + + detailGroup = []; + }; + + for (const token of tokenList) { + if (isGroupableDetailToken(token)) { + detailGroup.push(token); + } else { + flushDetailGroup(); + displayTokens.push(token); + } + } + + flushDetailGroup(); + + return displayTokens; + }; + + const getDetailTextContent = (token) => { + return decode(token?.text || '') + .replace(/.*?<\/summary>/gi, '') + .trim(); + }; + + $: displayTokens = getDisplayTokens(tokens); + const exportTableToCSVHandler = (token, tokenIdx = 0) => { console.log('Exporting table to CSV'); @@ -92,7 +138,7 @@ -{#each tokens as token, tokenIdx (tokenIdx)} +{#each displayTokens as token, tokenIdx (tokenIdx)} {#if token.type === 'hr'}
{:else if token.type === 'heading'} @@ -320,10 +366,55 @@ {/each} {/if} + {:else if token.type === 'detail_group'} + +
+ {#each token.items as detailToken, detailIdx} + {@const textContent = getDetailTextContent(detailToken)} + + {#if detailToken?.attributes?.type === 'tool_calls'} + + {:else if textContent.length > 0} + +
+ +
+
+ {:else} + + {/if} + {/each} +
+
{:else if token.type === 'details'} - {@const textContent = decode(token.text || '') - .replace(/.*?<\/summary>/gi, '') - .trim()} + {@const textContent = getDetailTextContent(token)} {#if token?.attributes?.type === 'tool_calls'} diff --git a/src/lib/components/common/ToolCallDisplay.svelte b/src/lib/components/common/ToolCallDisplay.svelte index 2cdcd5e37..2d9a3eb81 100644 --- a/src/lib/components/common/ToolCallDisplay.svelte +++ b/src/lib/components/common/ToolCallDisplay.svelte @@ -138,9 +138,9 @@
- {attributes.name} + {attributes.name} -