| | <script lang="ts"> |
| | import { decode } from 'html-entities'; |
| | import { v4 as uuidv4 } from 'uuid'; |
| |
|
| | import { getContext } from 'svelte'; |
| | const i18n = getContext('i18n'); |
| |
|
| | import { slide } from 'svelte/transition'; |
| | import { quintOut } from 'svelte/easing'; |
| |
|
| | import ChevronUp from '../icons/ChevronUp.svelte'; |
| | import ChevronDown from '../icons/ChevronDown.svelte'; |
| | import Spinner from './Spinner.svelte'; |
| | import Markdown from '../chat/Messages/Markdown.svelte'; |
| | import Image from './Image.svelte'; |
| | import FullHeightIframe from './FullHeightIframe.svelte'; |
| |
|
| | export let id: string = ''; |
| | export let attributes: { |
| | type?: string; |
| | id?: string; |
| | name?: string; |
| | arguments?: string; |
| | result?: string; |
| | files?: string; |
| | embeds?: string; |
| | done?: string; |
| | } = {}; |
| |
|
| | export let open = false; |
| | export let className = ''; |
| | export let buttonClassName = |
| | 'w-fit text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition'; |
| |
|
| | const componentId = id || uuidv4(); |
| |
|
| | function parseJSONString(str: string) { |
| | try { |
| | return parseJSONString(JSON.parse(str)); |
| | } catch (e) { |
| | return str; |
| | } |
| | } |
| |
|
| | function formatJSONString(str: string) { |
| | try { |
| | const parsed = parseJSONString(str); |
| | |
| | if (typeof parsed === 'object') { |
| | return JSON.stringify(parsed, null, 2); |
| | } else { |
| | |
| | return `${JSON.stringify(String(parsed))}`; |
| | } |
| | } catch (e) { |
| | |
| | return str; |
| | } |
| | } |
| |
|
| | |
| | $: args = decode(attributes?.arguments ?? ''); |
| | $: result = decode(attributes?.result ?? ''); |
| | $: files = parseJSONString(decode(attributes?.files ?? '')); |
| | $: embeds = parseJSONString(decode(attributes?.embeds ?? '')); |
| | $: isDone = attributes?.done === 'true'; |
| | $: isExecuting = attributes?.done && attributes?.done !== 'true'; |
| | </script> |
| |
|
| | <div {id} class={className}> |
| | {#if embeds && Array.isArray(embeds) && embeds.length > 0} |
| | |
| | <div class="py-1 w-full cursor-pointer"> |
| | <div class="w-full text-xs text-gray-500"> |
| | <div class=""> |
| | {attributes.name} |
| | </div> |
| | </div> |
| | |
| | {#each embeds as embed, idx} |
| | <div class="my-2" id={`${componentId}-tool-call-embed-${idx}`}> |
| | <FullHeightIframe |
| | src={embed} |
| | {args} |
| | allowScripts={true} |
| | allowForms={true} |
| | allowSameOrigin={true} |
| | allowPopups={true} |
| | /> |
| | </div> |
| | {/each} |
| | </div> |
| | {:else} |
| | |
| | <div |
| | class="{buttonClassName} cursor-pointer" |
| | on:pointerup={() => { |
| | open = !open; |
| | }} |
| | > |
| | <div |
| | class="w-full font-medium flex items-center justify-between gap-2 {isExecuting |
| | ? 'shimmer' |
| | : ''}" |
| | > |
| | {#if isExecuting} |
| | <div> |
| | <Spinner className="size-4" /> |
| | </div> |
| | {/if} |
| | |
| | <div class=""> |
| | {#if isDone} |
| | <Markdown |
| | id={`${componentId}-tool-call-title`} |
| | content={$i18n.t('View Result from **{{NAME}}**', { |
| | NAME: attributes.name |
| | })} |
| | /> |
| | {:else} |
| | <Markdown |
| | id={`${componentId}-tool-call-executing`} |
| | content={$i18n.t('Executing **{{NAME}}**...', { |
| | NAME: attributes.name |
| | })} |
| | /> |
| | {/if} |
| | </div> |
| | |
| | <div class="flex self-center translate-y-[1px]"> |
| | {#if open} |
| | <ChevronUp strokeWidth="3.5" className="size-3.5" /> |
| | {:else} |
| | <ChevronDown strokeWidth="3.5" className="size-3.5" /> |
| | {/if} |
| | </div> |
| | </div> |
| | </div> |
| | |
| | {#if open} |
| | <div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}> |
| | {#if isDone} |
| | <Markdown |
| | id={`${componentId}-tool-call-result`} |
| | content={`> \`\`\`json |
| | > ${formatJSONString(args)} |
| | > ${formatJSONString(result)} |
| | > \`\`\``} |
| | /> |
| | {:else} |
| | <Markdown |
| | id={`${componentId}-tool-call-args`} |
| | content={`> \`\`\`json |
| | > ${formatJSONString(args)} |
| | > \`\`\``} |
| | /> |
| | {/if} |
| | </div> |
| | {/if} |
| | {/if} |
| | |
| | |
| | {#if isDone} |
| | {#if typeof files === 'object'} |
| | {#each files ?? [] as file, idx} |
| | {#if typeof file === 'string'} |
| | {#if file.startsWith('data:image/')} |
| | <Image id={`${componentId}-tool-call-result-${idx}`} src={file} alt="Image" /> |
| | {/if} |
| | {:else if typeof file === 'object'} |
| | {#if (file.type === 'image' || (file?.content_type ?? '').startsWith('image/')) && file.url} |
| | <Image id={`${componentId}-tool-call-result-${idx}`} src={file.url} alt="Image" /> |
| | {/if} |
| | {/if} |
| | {/each} |
| | {/if} |
| | {/if} |
| | </div> |
| |
|