|
@@ -18,6 +18,7 @@
|
|
|
import Terminal from '$lib/components/icons/Terminal.svelte';
|
|
|
import ChevronRight from '$lib/components/icons/ChevronRight.svelte';
|
|
|
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
|
|
|
+ import { crossfade, fade, fly, slide } from 'svelte/transition';
|
|
|
|
|
|
const i18n = getContext('i18n');
|
|
|
|
|
@@ -39,9 +40,10 @@
|
|
|
|
|
|
export let onClose: Function;
|
|
|
|
|
|
- let tools = null;
|
|
|
let show = false;
|
|
|
- let showAllTools = false;
|
|
|
+ let tab = '';
|
|
|
+
|
|
|
+ let tools = null;
|
|
|
|
|
|
$: if (show) {
|
|
|
init();
|
|
@@ -81,129 +83,114 @@
|
|
|
</Tooltip>
|
|
|
<div slot="content">
|
|
|
<DropdownMenu.Content
|
|
|
- class="w-full max-w-[240px] rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg max-h-72 overflow-y-auto scrollbar-thin"
|
|
|
+ class="w-full max-w-[240px] rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg max-h-72 overflow-y-auto overflow-x-hidden scrollbar-thin"
|
|
|
sideOffset={4}
|
|
|
alignOffset={-6}
|
|
|
side="bottom"
|
|
|
align="start"
|
|
|
transition={flyAndScale}
|
|
|
>
|
|
|
- {#if tools}
|
|
|
- {#if Object.keys(tools).length > 0}
|
|
|
- <button
|
|
|
- class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
|
- on:click={() => {
|
|
|
- showAllTools = !showAllTools;
|
|
|
- }}
|
|
|
- >
|
|
|
- {#if !showAllTools}
|
|
|
- <Wrench />
|
|
|
-
|
|
|
- <div class="flex items-center w-full justify-between">
|
|
|
- <div>
|
|
|
- {$i18n.t('Tools')}
|
|
|
- <span class="ml-0.5 text-gray-500">{Object.keys(tools).length}</span>
|
|
|
- </div>
|
|
|
+ {#if tab === ''}
|
|
|
+ <div in:fly={{ x: -20, duration: 150 }}>
|
|
|
+ {#if tools}
|
|
|
+ {#if Object.keys(tools).length > 0}
|
|
|
+ <button
|
|
|
+ class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
|
+ on:click={() => {
|
|
|
+ tab = 'tools';
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Wrench />
|
|
|
|
|
|
- <div class="text-gray-500">
|
|
|
- <ChevronRight />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- {:else}
|
|
|
- <ChevronLeft />
|
|
|
+ <div class="flex items-center w-full justify-between">
|
|
|
+ <div>
|
|
|
+ {$i18n.t('Tools')}
|
|
|
+ <span class="ml-0.5 text-gray-500">{Object.keys(tools).length}</span>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="flex items-center w-full justify-between">
|
|
|
- <div>
|
|
|
- {$i18n.t('Tools')}
|
|
|
- <span class="ml-0.5 text-gray-500">{Object.keys(tools).length}</span>
|
|
|
+ <div class="text-gray-500">
|
|
|
+ <ChevronRight />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </button>
|
|
|
{/if}
|
|
|
- </button>
|
|
|
- {/if}
|
|
|
- {:else}
|
|
|
- <div class="py-4">
|
|
|
- <Spinner />
|
|
|
- </div>
|
|
|
- {/if}
|
|
|
+ {:else}
|
|
|
+ <div class="py-4">
|
|
|
+ <Spinner />
|
|
|
+ </div>
|
|
|
+ {/if}
|
|
|
|
|
|
- {#if showAllTools}
|
|
|
- {#each Object.keys(tools) as toolId}
|
|
|
- <button
|
|
|
- class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
|
- on:click={() => {
|
|
|
- tools[toolId].enabled = !tools[toolId].enabled;
|
|
|
- }}
|
|
|
- >
|
|
|
- <div class="flex-1 truncate">
|
|
|
- <div class="flex flex-1 gap-2 items-center">
|
|
|
- <Tooltip content={tools[toolId]?.name ?? ''} placement="top">
|
|
|
- <div class="shrink-0">
|
|
|
- <Wrench />
|
|
|
+ {#if toggleFilters && toggleFilters.length > 0}
|
|
|
+ {#each toggleFilters.sort( (a, b) => a.name.localeCompare( b.name, undefined, { sensitivity: 'base' } ) ) as filter, filterIdx (filter.id)}
|
|
|
+ <Tooltip content={filter?.description} placement="top-start">
|
|
|
+ <button
|
|
|
+ class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
|
+ on:click={() => {
|
|
|
+ if (selectedFilterIds.includes(filter.id)) {
|
|
|
+ selectedFilterIds = selectedFilterIds.filter((id) => id !== filter.id);
|
|
|
+ } else {
|
|
|
+ selectedFilterIds = [...selectedFilterIds, filter.id];
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div class="flex-1 truncate">
|
|
|
+ <div class="flex flex-1 gap-2 items-center">
|
|
|
+ <div class="shrink-0">
|
|
|
+ {#if filter?.icon}
|
|
|
+ <div class="size-4 items-center flex justify-center">
|
|
|
+ <img
|
|
|
+ src={filter.icon}
|
|
|
+ class="size-3.5 {filter.icon.includes('svg')
|
|
|
+ ? 'dark:invert-[80%]'
|
|
|
+ : ''}"
|
|
|
+ style="fill: currentColor;"
|
|
|
+ alt={filter.name}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ {:else}
|
|
|
+ <Sparkles className="size-4" strokeWidth="1.75" />
|
|
|
+ {/if}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class=" truncate">{filter?.name}</div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </Tooltip>
|
|
|
- <Tooltip content={tools[toolId]?.description ?? ''} placement="top-start">
|
|
|
- <div class=" truncate">{tools[toolId].name}</div>
|
|
|
- </Tooltip>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
|
|
|
- <div class=" shrink-0">
|
|
|
- <Switch
|
|
|
- state={tools[toolId].enabled}
|
|
|
- on:change={async (e) => {
|
|
|
- const state = e.detail;
|
|
|
- await tick();
|
|
|
- if (state) {
|
|
|
- selectedToolIds = [...selectedToolIds, toolId];
|
|
|
- } else {
|
|
|
- selectedToolIds = selectedToolIds.filter((id) => id !== toolId);
|
|
|
- }
|
|
|
- }}
|
|
|
- />
|
|
|
- </div>
|
|
|
- </button>
|
|
|
- {/each}
|
|
|
- {:else}
|
|
|
- {#if toggleFilters && toggleFilters.length > 0}
|
|
|
- {#each toggleFilters.sort( (a, b) => a.name.localeCompare( b.name, undefined, { sensitivity: 'base' } ) ) as filter, filterIdx (filter.id)}
|
|
|
- <Tooltip content={filter?.description} placement="top-start">
|
|
|
+ <div class=" shrink-0">
|
|
|
+ <Switch
|
|
|
+ state={selectedFilterIds.includes(filter.id)}
|
|
|
+ on:change={async (e) => {
|
|
|
+ const state = e.detail;
|
|
|
+ await tick();
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </button>
|
|
|
+ </Tooltip>
|
|
|
+ {/each}
|
|
|
+ {/if}
|
|
|
+
|
|
|
+ {#if showWebSearchButton}
|
|
|
+ <Tooltip content={$i18n.t('Search the internet')} placement="top-start">
|
|
|
<button
|
|
|
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
|
on:click={() => {
|
|
|
- if (selectedFilterIds.includes(filter.id)) {
|
|
|
- selectedFilterIds = selectedFilterIds.filter((id) => id !== filter.id);
|
|
|
- } else {
|
|
|
- selectedFilterIds = [...selectedFilterIds, filter.id];
|
|
|
- }
|
|
|
+ webSearchEnabled = !webSearchEnabled;
|
|
|
}}
|
|
|
>
|
|
|
<div class="flex-1 truncate">
|
|
|
<div class="flex flex-1 gap-2 items-center">
|
|
|
<div class="shrink-0">
|
|
|
- {#if filter?.icon}
|
|
|
- <div class="size-4 items-center flex justify-center">
|
|
|
- <img
|
|
|
- src={filter.icon}
|
|
|
- class="size-3.5 {filter.icon.includes('svg')
|
|
|
- ? 'dark:invert-[80%]'
|
|
|
- : ''}"
|
|
|
- style="fill: currentColor;"
|
|
|
- alt={filter.name}
|
|
|
- />
|
|
|
- </div>
|
|
|
- {:else}
|
|
|
- <Sparkles className="size-4" strokeWidth="1.75" />
|
|
|
- {/if}
|
|
|
+ <GlobeAlt />
|
|
|
</div>
|
|
|
|
|
|
- <div class=" truncate">{filter?.name}</div>
|
|
|
+ <div class=" truncate">{$i18n.t('Web Search')}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class=" shrink-0">
|
|
|
<Switch
|
|
|
- state={selectedFilterIds.includes(filter.id)}
|
|
|
+ state={webSearchEnabled}
|
|
|
on:change={async (e) => {
|
|
|
const state = e.detail;
|
|
|
await tick();
|
|
@@ -212,105 +199,129 @@
|
|
|
</div>
|
|
|
</button>
|
|
|
</Tooltip>
|
|
|
- {/each}
|
|
|
- {/if}
|
|
|
+ {/if}
|
|
|
|
|
|
- {#if showWebSearchButton}
|
|
|
- <Tooltip content={$i18n.t('Search the internet')} placement="top-start">
|
|
|
- <button
|
|
|
- class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
|
- on:click={() => {
|
|
|
- webSearchEnabled = !webSearchEnabled;
|
|
|
- }}
|
|
|
- >
|
|
|
- <div class="flex-1 truncate">
|
|
|
- <div class="flex flex-1 gap-2 items-center">
|
|
|
- <div class="shrink-0">
|
|
|
- <GlobeAlt />
|
|
|
+ {#if showImageGenerationButton}
|
|
|
+ <Tooltip content={$i18n.t('Generate an image')} placement="top-start">
|
|
|
+ <button
|
|
|
+ class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
|
+ on:click={() => {
|
|
|
+ imageGenerationEnabled = !imageGenerationEnabled;
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div class="flex-1 truncate">
|
|
|
+ <div class="flex flex-1 gap-2 items-center">
|
|
|
+ <div class="shrink-0">
|
|
|
+ <Photo className="size-4" strokeWidth="1.5" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class=" truncate">{$i18n.t('Image')}</div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class=" truncate">{$i18n.t('Web Search')}</div>
|
|
|
+ <div class=" shrink-0">
|
|
|
+ <Switch
|
|
|
+ state={imageGenerationEnabled}
|
|
|
+ on:change={async (e) => {
|
|
|
+ const state = e.detail;
|
|
|
+ await tick();
|
|
|
+ }}
|
|
|
+ />
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </button>
|
|
|
+ </Tooltip>
|
|
|
+ {/if}
|
|
|
|
|
|
- <div class=" shrink-0">
|
|
|
- <Switch
|
|
|
- state={webSearchEnabled}
|
|
|
- on:change={async (e) => {
|
|
|
- const state = e.detail;
|
|
|
- await tick();
|
|
|
- }}
|
|
|
- />
|
|
|
- </div>
|
|
|
- </button>
|
|
|
- </Tooltip>
|
|
|
- {/if}
|
|
|
+ {#if showCodeInterpreterButton}
|
|
|
+ <Tooltip content={$i18n.t('Execute code for analysis')} placement="top-start">
|
|
|
+ <button
|
|
|
+ class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
|
+ aria-pressed={codeInterpreterEnabled}
|
|
|
+ aria-label={codeInterpreterEnabled
|
|
|
+ ? $i18n.t('Disable Code Interpreter')
|
|
|
+ : $i18n.t('Enable Code Interpreter')}
|
|
|
+ on:click={() => {
|
|
|
+ codeInterpreterEnabled = !codeInterpreterEnabled;
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div class="flex-1 truncate">
|
|
|
+ <div class="flex flex-1 gap-2 items-center">
|
|
|
+ <div class="shrink-0">
|
|
|
+ <Terminal className="size-3.5" strokeWidth="1.75" />
|
|
|
+ </div>
|
|
|
|
|
|
- {#if showImageGenerationButton}
|
|
|
- <Tooltip content={$i18n.t('Generate an image')} placement="top-start">
|
|
|
- <button
|
|
|
- class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
|
- on:click={() => {
|
|
|
- imageGenerationEnabled = !imageGenerationEnabled;
|
|
|
- }}
|
|
|
- >
|
|
|
- <div class="flex-1 truncate">
|
|
|
- <div class="flex flex-1 gap-2 items-center">
|
|
|
- <div class="shrink-0">
|
|
|
- <Photo className="size-4" strokeWidth="1.5" />
|
|
|
+ <div class=" truncate">{$i18n.t('Code Interpreter')}</div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class=" truncate">{$i18n.t('Image')}</div>
|
|
|
+ <div class=" shrink-0">
|
|
|
+ <Switch
|
|
|
+ state={codeInterpreterEnabled}
|
|
|
+ on:change={async (e) => {
|
|
|
+ const state = e.detail;
|
|
|
+ await tick();
|
|
|
+ }}
|
|
|
+ />
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </button>
|
|
|
+ </Tooltip>
|
|
|
+ {/if}
|
|
|
+ </div>
|
|
|
+ {:else if tab === 'tools' && tools}
|
|
|
+ <div in:fly={{ x: 20, duration: 150 }}>
|
|
|
+ <button
|
|
|
+ class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
|
+ on:click={() => {
|
|
|
+ tab = '';
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <ChevronLeft />
|
|
|
|
|
|
- <div class=" shrink-0">
|
|
|
- <Switch
|
|
|
- state={imageGenerationEnabled}
|
|
|
- on:change={async (e) => {
|
|
|
- const state = e.detail;
|
|
|
- await tick();
|
|
|
- }}
|
|
|
- />
|
|
|
+ <div class="flex items-center w-full justify-between">
|
|
|
+ <div>
|
|
|
+ {$i18n.t('Tools')}
|
|
|
+ <span class="ml-0.5 text-gray-500">{Object.keys(tools).length}</span>
|
|
|
</div>
|
|
|
- </button>
|
|
|
- </Tooltip>
|
|
|
- {/if}
|
|
|
+ </div>
|
|
|
+ </button>
|
|
|
|
|
|
- {#if showCodeInterpreterButton}
|
|
|
- <Tooltip content={$i18n.t('Execute code for analysis')} placement="top-start">
|
|
|
+ {#each Object.keys(tools) as toolId}
|
|
|
<button
|
|
|
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
|
- aria-pressed={codeInterpreterEnabled}
|
|
|
- aria-label={codeInterpreterEnabled
|
|
|
- ? $i18n.t('Disable Code Interpreter')
|
|
|
- : $i18n.t('Enable Code Interpreter')}
|
|
|
on:click={() => {
|
|
|
- codeInterpreterEnabled = !codeInterpreterEnabled;
|
|
|
+ tools[toolId].enabled = !tools[toolId].enabled;
|
|
|
}}
|
|
|
>
|
|
|
<div class="flex-1 truncate">
|
|
|
<div class="flex flex-1 gap-2 items-center">
|
|
|
- <div class="shrink-0">
|
|
|
- <Terminal className="size-3.5" strokeWidth="1.75" />
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class=" truncate">{$i18n.t('Code Interpreter')}</div>
|
|
|
+ <Tooltip content={tools[toolId]?.name ?? ''} placement="top">
|
|
|
+ <div class="shrink-0">
|
|
|
+ <Wrench />
|
|
|
+ </div>
|
|
|
+ </Tooltip>
|
|
|
+ <Tooltip content={tools[toolId]?.description ?? ''} placement="top-start">
|
|
|
+ <div class=" truncate">{tools[toolId].name}</div>
|
|
|
+ </Tooltip>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class=" shrink-0">
|
|
|
<Switch
|
|
|
- state={codeInterpreterEnabled}
|
|
|
+ state={tools[toolId].enabled}
|
|
|
on:change={async (e) => {
|
|
|
const state = e.detail;
|
|
|
await tick();
|
|
|
+ if (state) {
|
|
|
+ selectedToolIds = [...selectedToolIds, toolId];
|
|
|
+ } else {
|
|
|
+ selectedToolIds = selectedToolIds.filter((id) => id !== toolId);
|
|
|
+ }
|
|
|
}}
|
|
|
/>
|
|
|
</div>
|
|
|
</button>
|
|
|
- </Tooltip>
|
|
|
- {/if}
|
|
|
+ {/each}
|
|
|
+ </div>
|
|
|
{/if}
|
|
|
</DropdownMenu.Content>
|
|
|
</div>
|