|
20 | 20 | import ToolCallDisplay from '$lib/components/common/ToolCallDisplay.svelte'; |
21 | 21 | import Tooltip from '$lib/components/common/Tooltip.svelte'; |
22 | 22 | import Download from '$lib/components/icons/Download.svelte'; |
| 23 | + import ConsecutiveDetailsGroup from './ConsecutiveDetailsGroup.svelte'; |
23 | 24 |
|
24 | 25 | import HtmlToken from './HTMLToken.svelte'; |
25 | 26 | import Clipboard from '$lib/components/icons/Clipboard.svelte'; |
|
52 | 53 | return 'h' + depth; |
53 | 54 | }; |
54 | 55 |
|
| 56 | + const GROUPABLE_DETAIL_TYPES = new Set(['tool_calls', 'reasoning', 'code_interpreter']); |
| 57 | +
|
| 58 | + const isGroupableDetailToken = (token: Token & { attributes?: { type?: string } }) => { |
| 59 | + return token?.type === 'details' && GROUPABLE_DETAIL_TYPES.has(token?.attributes?.type ?? ''); |
| 60 | + }; |
| 61 | +
|
| 62 | + const getDisplayTokens = (tokenList: Token[] = []) => { |
| 63 | + const displayTokens = []; |
| 64 | + let detailGroup = []; |
| 65 | +
|
| 66 | + const flushDetailGroup = () => { |
| 67 | + if (detailGroup.length > 1) { |
| 68 | + displayTokens.push({ |
| 69 | + type: 'detail_group', |
| 70 | + items: [...detailGroup] |
| 71 | + }); |
| 72 | + } else if (detailGroup.length === 1) { |
| 73 | + displayTokens.push(detailGroup[0]); |
| 74 | + } |
| 75 | +
|
| 76 | + detailGroup = []; |
| 77 | + }; |
| 78 | +
|
| 79 | + for (const token of tokenList) { |
| 80 | + if (isGroupableDetailToken(token)) { |
| 81 | + detailGroup.push(token); |
| 82 | + } else { |
| 83 | + flushDetailGroup(); |
| 84 | + displayTokens.push(token); |
| 85 | + } |
| 86 | + } |
| 87 | +
|
| 88 | + flushDetailGroup(); |
| 89 | +
|
| 90 | + return displayTokens; |
| 91 | + }; |
| 92 | +
|
| 93 | + const getDetailTextContent = (token) => { |
| 94 | + return decode(token?.text || '') |
| 95 | + .replace(/<summary>.*?<\/summary>/gi, '') |
| 96 | + .trim(); |
| 97 | + }; |
| 98 | +
|
| 99 | + $: displayTokens = getDisplayTokens(tokens); |
| 100 | +
|
55 | 101 | const exportTableToCSVHandler = (token, tokenIdx = 0) => { |
56 | 102 | console.log('Exporting table to CSV'); |
57 | 103 |
|
|
92 | 138 | </script> |
93 | 139 |
|
94 | 140 | <!-- {JSON.stringify(tokens)} --> |
95 | | -{#each tokens as token, tokenIdx (tokenIdx)} |
| 141 | +{#each displayTokens as token, tokenIdx (tokenIdx)} |
96 | 142 | {#if token.type === 'hr'} |
97 | 143 | <hr class=" border-gray-100/30 dark:border-gray-850/30" /> |
98 | 144 | {:else if token.type === 'heading'} |
|
320 | 366 | {/each} |
321 | 367 | </ul> |
322 | 368 | {/if} |
| 369 | + {:else if token.type === 'detail_group'} |
| 370 | + <ConsecutiveDetailsGroup id={`${id}-${tokenIdx}-detail-group`} tokens={token.items} messageDone={done}> |
| 371 | + <div slot="content" class="space-y-1"> |
| 372 | + {#each token.items as detailToken, detailIdx} |
| 373 | + {@const textContent = getDetailTextContent(detailToken)} |
| 374 | + |
| 375 | + {#if detailToken?.attributes?.type === 'tool_calls'} |
| 376 | + <ToolCallDisplay |
| 377 | + id={`${id}-${tokenIdx}-${detailIdx}-tc`} |
| 378 | + attributes={detailToken.attributes} |
| 379 | + open={false} |
| 380 | + className="w-full space-y-1" |
| 381 | + /> |
| 382 | + {:else if textContent.length > 0} |
| 383 | + <Collapsible |
| 384 | + title={detailToken.summary} |
| 385 | + open={$settings?.expandDetails ?? false} |
| 386 | + attributes={detailToken?.attributes} |
| 387 | + className="w-full space-y-1" |
| 388 | + dir="auto" |
| 389 | + > |
| 390 | + <div class="mb-1.5" slot="content"> |
| 391 | + <svelte:self |
| 392 | + id={`${id}-${tokenIdx}-${detailIdx}-d`} |
| 393 | + tokens={marked.lexer(decode(detailToken.text))} |
| 394 | + attributes={detailToken?.attributes} |
| 395 | + {done} |
| 396 | + {editCodeBlock} |
| 397 | + {onTaskClick} |
| 398 | + {sourceIds} |
| 399 | + {onSourceClick} |
| 400 | + /> |
| 401 | + </div> |
| 402 | + </Collapsible> |
| 403 | + {:else} |
| 404 | + <Collapsible |
| 405 | + title={detailToken.summary} |
| 406 | + open={false} |
| 407 | + disabled={true} |
| 408 | + attributes={detailToken?.attributes} |
| 409 | + className="w-full space-y-1" |
| 410 | + dir="auto" |
| 411 | + /> |
| 412 | + {/if} |
| 413 | + {/each} |
| 414 | + </div> |
| 415 | + </ConsecutiveDetailsGroup> |
323 | 416 | {:else if token.type === 'details'} |
324 | | - {@const textContent = decode(token.text || '') |
325 | | - .replace(/<summary>.*?<\/summary>/gi, '') |
326 | | - .trim()} |
| 417 | + {@const textContent = getDetailTextContent(token)} |
327 | 418 |
|
328 | 419 | {#if token?.attributes?.type === 'tool_calls'} |
329 | 420 | <!-- Tool calls have dedicated handling with ToolCallDisplay component --> |
|
0 commit comments