Skip to content

Commit 4c872a8

Browse files
committed
refac
1 parent 925f77f commit 4c872a8

1 file changed

Lines changed: 98 additions & 46 deletions

File tree

Lines changed: 98 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script lang="ts">
2+
import { decode } from 'html-entities';
23
import { getContext } from 'svelte';
34
import { slide } from 'svelte/transition';
45
import { quintOut } from 'svelte/easing';
@@ -9,6 +10,9 @@
910
import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte';
1011
import Sparkles from '$lib/components/icons/Sparkles.svelte';
1112
import CheckCircle from '$lib/components/icons/CheckCircle.svelte';
13+
import FullHeightIframe from '$lib/components/common/FullHeightIframe.svelte';
14+
15+
import { settings } from '$lib/stores';
1216
1317
const i18n = getContext('i18n');
1418
@@ -20,13 +24,23 @@
2024
name?: string;
2125
done?: string;
2226
duration?: string;
27+
embeds?: string;
28+
arguments?: string;
2329
};
2430
}> = [];
2531
2632
export let messageDone = true;
2733
2834
let open = false;
2935
36+
function parseJSONString(str: string) {
37+
try {
38+
return parseJSONString(JSON.parse(str));
39+
} catch (e) {
40+
return str;
41+
}
42+
}
43+
3044
$: toolCallCount = tokens.filter((t) => t?.attributes?.type === 'tool_calls').length;
3145
$: reasoningCount = tokens.filter((t) => t?.attributes?.type === 'reasoning').length;
3246
$: hasPending =
@@ -35,6 +49,28 @@
3549
3650
$: codeInterpreterCount = tokens.filter((t) => t?.attributes?.type === 'code_interpreter').length;
3751
52+
// Collect all embeds from tool_calls tokens
53+
$: allEmbeds = (() => {
54+
const result: Array<{ name: string; embed: string; args: string }> = [];
55+
for (const t of tokens) {
56+
if (t?.attributes?.type !== 'tool_calls') continue;
57+
const raw = decode(t.attributes?.embeds ?? '');
58+
try {
59+
const parsed = parseJSONString(raw);
60+
if (Array.isArray(parsed) && parsed.length > 0) {
61+
for (const embed of parsed) {
62+
result.push({
63+
name: t.attributes?.name ?? '',
64+
embed,
65+
args: decode(t.attributes?.arguments ?? '')
66+
});
67+
}
68+
}
69+
} catch {}
70+
}
71+
return result;
72+
})();
73+
3874
$: summaryText = (() => {
3975
const parts = [];
4076
@@ -71,57 +107,73 @@
71107
</script>
72108

73109
<div {id} class="w-full">
74-
<!-- svelte-ignore a11y-no-static-element-interactions -->
75-
<button
76-
class="w-fit text-left text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition cursor-pointer"
77-
aria-label={$i18n.t('Toggle details')}
78-
aria-expanded={open}
79-
on:click={() => {
80-
open = !open;
81-
}}
82-
>
83-
<div class="flex items-center gap-1.5">
84-
<!-- Status icon -->
85-
{#if hasPending}
86-
<div>
87-
<Spinner className="size-4" />
88-
</div>
89-
{:else if toolCallCount > 0}
90-
<div class="text-emerald-500 dark:text-emerald-400">
91-
<CheckCircle className="size-4" strokeWidth="2" />
92-
</div>
93-
{:else}
94-
<div class="text-gray-400 dark:text-gray-500">
95-
<Sparkles className="size-3.5" />
96-
</div>
97-
{/if}
98-
99-
<!-- Summary text -->
100-
<div class="flex-1 line-clamp-1">
101-
<span class="text-gray-600 dark:text-gray-300 {hasPending ? 'shimmer' : ''}"
102-
>{prefixText}</span
103-
>
104-
{#if summaryText}
105-
<span class="text-gray-400 dark:text-gray-500">{summaryText}</span>
106-
{/if}
110+
{#if allEmbeds.length > 0}
111+
<!-- Embed mode: render iframes directly like ToolCallDisplay -->
112+
{#each allEmbeds as embedItem, idx}
113+
<div id={`${id}-embed-${idx}`}>
114+
<FullHeightIframe
115+
src={embedItem.embed}
116+
args={embedItem.args}
117+
allowScripts={true}
118+
allowForms={$settings?.iframeSandboxAllowForms ?? false}
119+
allowSameOrigin={$settings?.iframeSandboxAllowSameOrigin ?? false}
120+
allowPopups={true}
121+
/>
107122
</div>
108-
109-
<!-- Chevron -->
110-
<div class="flex shrink-0 self-center text-gray-400 dark:text-gray-500">
111-
{#if open}
112-
<ChevronUp strokeWidth="3.5" className="size-3" />
123+
{/each}
124+
{:else}
125+
<!-- svelte-ignore a11y-no-static-element-interactions -->
126+
<button
127+
class="w-fit text-left text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition cursor-pointer"
128+
aria-label={$i18n.t('Toggle details')}
129+
aria-expanded={open}
130+
on:click={() => {
131+
open = !open;
132+
}}
133+
>
134+
<div class="flex items-center gap-1.5">
135+
<!-- Status icon -->
136+
{#if hasPending}
137+
<div>
138+
<Spinner className="size-4" />
139+
</div>
140+
{:else if toolCallCount > 0}
141+
<div class="text-emerald-500 dark:text-emerald-400">
142+
<CheckCircle className="size-4" strokeWidth="2" />
143+
</div>
113144
{:else}
114-
<ChevronDown strokeWidth="3.5" className="size-3" />
145+
<div class="text-gray-400 dark:text-gray-500">
146+
<Sparkles className="size-3.5" />
147+
</div>
115148
{/if}
149+
150+
<!-- Summary text -->
151+
<div class="flex-1 line-clamp-1">
152+
<span class="text-gray-600 dark:text-gray-300 {hasPending ? 'shimmer' : ''}"
153+
>{prefixText}</span
154+
>
155+
{#if summaryText}
156+
<span class="text-gray-400 dark:text-gray-500">{summaryText}</span>
157+
{/if}
158+
</div>
159+
160+
<!-- Chevron -->
161+
<div class="flex shrink-0 self-center text-gray-400 dark:text-gray-500">
162+
{#if open}
163+
<ChevronUp strokeWidth="3.5" className="size-3" />
164+
{:else}
165+
<ChevronDown strokeWidth="3.5" className="size-3" />
166+
{/if}
167+
</div>
116168
</div>
117-
</div>
118-
</button>
169+
</button>
119170

120-
{#if open}
121-
<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
122-
<div class="mb-0.5 space-y-0.5">
123-
<slot name="content" />
171+
{#if open}
172+
<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
173+
<div class="mb-0.5 space-y-0.5">
174+
<slot name="content" />
175+
</div>
124176
</div>
125-
</div>
177+
{/if}
126178
{/if}
127179
</div>

0 commit comments

Comments
 (0)