refac
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { decode } from 'html-entities';
|
||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
import { quintOut } from 'svelte/easing';
|
import { quintOut } from 'svelte/easing';
|
||||||
@@ -9,6 +10,9 @@
|
|||||||
import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte';
|
import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte';
|
||||||
import Sparkles from '$lib/components/icons/Sparkles.svelte';
|
import Sparkles from '$lib/components/icons/Sparkles.svelte';
|
||||||
import CheckCircle from '$lib/components/icons/CheckCircle.svelte';
|
import CheckCircle from '$lib/components/icons/CheckCircle.svelte';
|
||||||
|
import FullHeightIframe from '$lib/components/common/FullHeightIframe.svelte';
|
||||||
|
|
||||||
|
import { settings } from '$lib/stores';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
@@ -20,6 +24,8 @@
|
|||||||
name?: string;
|
name?: string;
|
||||||
done?: string;
|
done?: string;
|
||||||
duration?: string;
|
duration?: string;
|
||||||
|
embeds?: string;
|
||||||
|
arguments?: string;
|
||||||
};
|
};
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
@@ -27,6 +33,14 @@
|
|||||||
|
|
||||||
let open = false;
|
let open = false;
|
||||||
|
|
||||||
|
function parseJSONString(str: string) {
|
||||||
|
try {
|
||||||
|
return parseJSONString(JSON.parse(str));
|
||||||
|
} catch (e) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: toolCallCount = tokens.filter((t) => t?.attributes?.type === 'tool_calls').length;
|
$: toolCallCount = tokens.filter((t) => t?.attributes?.type === 'tool_calls').length;
|
||||||
$: reasoningCount = tokens.filter((t) => t?.attributes?.type === 'reasoning').length;
|
$: reasoningCount = tokens.filter((t) => t?.attributes?.type === 'reasoning').length;
|
||||||
$: hasPending =
|
$: hasPending =
|
||||||
@@ -35,6 +49,28 @@
|
|||||||
|
|
||||||
$: codeInterpreterCount = tokens.filter((t) => t?.attributes?.type === 'code_interpreter').length;
|
$: codeInterpreterCount = tokens.filter((t) => t?.attributes?.type === 'code_interpreter').length;
|
||||||
|
|
||||||
|
// Collect all embeds from tool_calls tokens
|
||||||
|
$: allEmbeds = (() => {
|
||||||
|
const result: Array<{ name: string; embed: string; args: string }> = [];
|
||||||
|
for (const t of tokens) {
|
||||||
|
if (t?.attributes?.type !== 'tool_calls') continue;
|
||||||
|
const raw = decode(t.attributes?.embeds ?? '');
|
||||||
|
try {
|
||||||
|
const parsed = parseJSONString(raw);
|
||||||
|
if (Array.isArray(parsed) && parsed.length > 0) {
|
||||||
|
for (const embed of parsed) {
|
||||||
|
result.push({
|
||||||
|
name: t.attributes?.name ?? '',
|
||||||
|
embed,
|
||||||
|
args: decode(t.attributes?.arguments ?? '')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})();
|
||||||
|
|
||||||
$: summaryText = (() => {
|
$: summaryText = (() => {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
|
|
||||||
@@ -71,57 +107,73 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div {id} class="w-full">
|
<div {id} class="w-full">
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
{#if allEmbeds.length > 0}
|
||||||
<button
|
<!-- Embed mode: render iframes directly like ToolCallDisplay -->
|
||||||
class="w-fit text-left text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition cursor-pointer"
|
{#each allEmbeds as embedItem, idx}
|
||||||
aria-label={$i18n.t('Toggle details')}
|
<div id={`${id}-embed-${idx}`}>
|
||||||
aria-expanded={open}
|
<FullHeightIframe
|
||||||
on:click={() => {
|
src={embedItem.embed}
|
||||||
open = !open;
|
args={embedItem.args}
|
||||||
}}
|
allowScripts={true}
|
||||||
>
|
allowForms={$settings?.iframeSandboxAllowForms ?? false}
|
||||||
<div class="flex items-center gap-1.5">
|
allowSameOrigin={$settings?.iframeSandboxAllowSameOrigin ?? false}
|
||||||
<!-- Status icon -->
|
allowPopups={true}
|
||||||
{#if hasPending}
|
/>
|
||||||
<div>
|
|
||||||
<Spinner className="size-4" />
|
|
||||||
</div>
|
|
||||||
{:else if toolCallCount > 0}
|
|
||||||
<div class="text-emerald-500 dark:text-emerald-400">
|
|
||||||
<CheckCircle className="size-4" strokeWidth="2" />
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="text-gray-400 dark:text-gray-500">
|
|
||||||
<Sparkles className="size-3.5" />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- Summary text -->
|
|
||||||
<div class="flex-1 line-clamp-1">
|
|
||||||
<span class="text-gray-600 dark:text-gray-300 {hasPending ? 'shimmer' : ''}"
|
|
||||||
>{prefixText}</span
|
|
||||||
>
|
|
||||||
{#if summaryText}
|
|
||||||
<span class="text-gray-400 dark:text-gray-500">{summaryText}</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
{/each}
|
||||||
<!-- Chevron -->
|
{:else}
|
||||||
<div class="flex shrink-0 self-center text-gray-400 dark:text-gray-500">
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
{#if open}
|
<button
|
||||||
<ChevronUp strokeWidth="3.5" className="size-3" />
|
class="w-fit text-left text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition cursor-pointer"
|
||||||
|
aria-label={$i18n.t('Toggle details')}
|
||||||
|
aria-expanded={open}
|
||||||
|
on:click={() => {
|
||||||
|
open = !open;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-1.5">
|
||||||
|
<!-- Status icon -->
|
||||||
|
{#if hasPending}
|
||||||
|
<div>
|
||||||
|
<Spinner className="size-4" />
|
||||||
|
</div>
|
||||||
|
{:else if toolCallCount > 0}
|
||||||
|
<div class="text-emerald-500 dark:text-emerald-400">
|
||||||
|
<CheckCircle className="size-4" strokeWidth="2" />
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<ChevronDown strokeWidth="3.5" className="size-3" />
|
<div class="text-gray-400 dark:text-gray-500">
|
||||||
|
<Sparkles className="size-3.5" />
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if open}
|
<!-- Summary text -->
|
||||||
<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
|
<div class="flex-1 line-clamp-1">
|
||||||
<div class="mb-0.5 space-y-0.5">
|
<span class="text-gray-600 dark:text-gray-300 {hasPending ? 'shimmer' : ''}"
|
||||||
<slot name="content" />
|
>{prefixText}</span
|
||||||
|
>
|
||||||
|
{#if summaryText}
|
||||||
|
<span class="text-gray-400 dark:text-gray-500">{summaryText}</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chevron -->
|
||||||
|
<div class="flex shrink-0 self-center text-gray-400 dark:text-gray-500">
|
||||||
|
{#if open}
|
||||||
|
<ChevronUp strokeWidth="3.5" className="size-3" />
|
||||||
|
{:else}
|
||||||
|
<ChevronDown strokeWidth="3.5" className="size-3" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
|
{#if open}
|
||||||
|
<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
|
||||||
|
<div class="mb-0.5 space-y-0.5">
|
||||||
|
<slot name="content" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user