refac
This commit is contained in:
1 parent
fee4c280f8
commit
fa889837e9
10 files changed
+278
-100
No files matched your search
@@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class={layout === 'grid'
|
class={layout === 'grid'
|
||||||
? 'grid grid-cols-1 gap-x-5 gap-y-1 pb-safe-bottom text-xs sm:grid-cols-2 xl:grid-cols-3'
|
? 'grid grid-cols-1 gap-x-5 gap-y-1 pb-safe-bottom text-xs sm:grid-cols-2 lg:grid-cols-3'
|
||||||
: 'space-y-1 text-xs pb-safe-bottom'}
|
: 'space-y-1 text-xs pb-safe-bottom'}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -912,7 +912,7 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
|
|||||||
|
|
||||||
<PaneGroup direction="horizontal" class="w-full h-full">
|
<PaneGroup direction="horizontal" class="w-full h-full">
|
||||||
<Pane defaultSize={70} minSize={30} class="h-full flex flex-col w-full relative">
|
<Pane defaultSize={70} minSize={30} class="h-full flex flex-col w-full relative">
|
||||||
<div class="relative flex-1 w-full h-full flex justify-center pt-[11px]" id="note-editor">
|
<div class="relative flex-1 w-full h-full flex justify-center pt-2" id="note-editor">
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class=" absolute top-0 bottom-0 left-0 right-0 flex">
|
<div class=" absolute top-0 bottom-0 left-0 right-0 flex">
|
||||||
<div class="m-auto">
|
<div class="m-auto">
|
||||||
@@ -921,7 +921,7 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class=" w-full flex flex-col {loading ? 'opacity-20' : ''}">
|
<div class=" w-full flex flex-col {loading ? 'opacity-20' : ''}">
|
||||||
<div class="shrink-0 w-full flex justify-between items-center px-3.5 mb-1.5">
|
<div class="shrink-0 w-full flex justify-between items-center px-2.5 mb-1.5">
|
||||||
<div class="w-full min-w-0 flex items-center">
|
<div class="w-full min-w-0 flex items-center">
|
||||||
{#if $mobile}
|
{#if $mobile}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -944,7 +944,9 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<input
|
<input
|
||||||
class="w-full text-2xl font-normal bg-transparent outline-hidden"
|
class="w-full text-sm font-normal bg-transparent outline-hidden {$mobile
|
||||||
|
? 'ml-1'
|
||||||
|
: ''}"
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={note.title}
|
bind:value={note.title}
|
||||||
placeholder={titleGenerating ? $i18n.t('Generating...') : $i18n.t('Title')}
|
placeholder={titleGenerating ? $i18n.t('Generating...') : $i18n.t('Title')}
|
||||||
@@ -1124,7 +1126,7 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" px-2.5">
|
<div class=" px-1.5">
|
||||||
<div
|
<div
|
||||||
class=" flex w-full bg-transparent overflow-x-auto scrollbar-none"
|
class=" flex w-full bg-transparent overflow-x-auto scrollbar-none"
|
||||||
on:wheel={(e) => {
|
on:wheel={(e) => {
|
||||||
@@ -1180,7 +1182,7 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class=" flex-1 w-full h-full overflow-auto px-3.5 relative"
|
class=" flex-1 w-full h-full overflow-auto px-2.5 relative"
|
||||||
id="note-content-container"
|
id="note-content-container"
|
||||||
>
|
>
|
||||||
{#if editing}
|
{#if editing}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-1.5 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Builtin Tools')}</div>
|
<div class="mb-1.5 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Builtin Tools')}</div>
|
||||||
<div class="grid grid-cols-1 gap-x-5 gap-y-1 sm:grid-cols-2 xl:grid-cols-3">
|
<div class="grid grid-cols-1 gap-x-5 gap-y-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{#each allTools as tool}
|
{#each allTools as tool}
|
||||||
<div class="flex min-h-6 items-center justify-between gap-2.5">
|
<div class="flex min-h-6 items-center justify-between gap-2.5">
|
||||||
<div class="min-w-0 text-xs text-gray-600 dark:text-gray-400">
|
<div class="min-w-0 text-xs text-gray-600 dark:text-gray-400">
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-1.5 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Capabilities')}</div>
|
<div class="mb-1.5 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Capabilities')}</div>
|
||||||
<div class="grid grid-cols-1 gap-x-5 gap-y-1 sm:grid-cols-2 xl:grid-cols-3">
|
<div class="grid grid-cols-1 gap-x-5 gap-y-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{#each visibleCapabilities as capability}
|
{#each visibleCapabilities as capability}
|
||||||
<div class="flex min-h-6 items-center justify-between gap-2.5">
|
<div class="flex min-h-6 items-center justify-between gap-2.5">
|
||||||
<div class="min-w-0 text-xs text-gray-600 dark:text-gray-400">
|
<div class="min-w-0 text-xs text-gray-600 dark:text-gray-400">
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-1.5 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Default Features')}</div>
|
<div class="mb-1.5 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Default Features')}</div>
|
||||||
<div class="grid grid-cols-1 gap-x-5 gap-y-1 sm:grid-cols-2 xl:grid-cols-3">
|
<div class="grid grid-cols-1 gap-x-5 gap-y-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{#each availableFeatures as feature}
|
{#each availableFeatures as feature}
|
||||||
<div class="flex min-h-6 items-center justify-between gap-2.5">
|
<div class="flex min-h-6 items-center justify-between gap-2.5">
|
||||||
<div class="min-w-0 text-xs text-gray-600 dark:text-gray-400">
|
<div class="min-w-0 text-xs text-gray-600 dark:text-gray-400">
|
||||||
|
|||||||
@@ -3,7 +3,14 @@
|
|||||||
import { config, knowledge, settings, user } from '$lib/stores';
|
import { config, knowledge, settings, user } from '$lib/stores';
|
||||||
|
|
||||||
import KnowledgeSelector from './Knowledge/KnowledgeSelector.svelte';
|
import KnowledgeSelector from './Knowledge/KnowledgeSelector.svelte';
|
||||||
import FileItem from '$lib/components/common/FileItem.svelte';
|
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||||
|
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||||
|
import ChatBubble from '$lib/components/icons/ChatBubble.svelte';
|
||||||
|
import Database from '$lib/components/icons/Database.svelte';
|
||||||
|
import DocumentPage from '$lib/components/icons/DocumentPage.svelte';
|
||||||
|
import Folder from '$lib/components/icons/Folder.svelte';
|
||||||
|
import PageEdit from '$lib/components/icons/PageEdit.svelte';
|
||||||
|
import XMark from '$lib/components/icons/XMark.svelte';
|
||||||
|
|
||||||
import { getKnowledgeBases } from '$lib/apis/knowledge';
|
import { getKnowledgeBases } from '$lib/apis/knowledge';
|
||||||
import { uploadFile } from '$lib/apis/files';
|
import { uploadFile } from '$lib/apis/files';
|
||||||
@@ -155,79 +162,118 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<slot name="label">
|
<div class="mb-2">
|
||||||
<div class="mb-2">
|
<div class="flex w-full items-center gap-2 mb-1">
|
||||||
<div class="flex w-full justify-between mb-1">
|
<div class="min-w-0">
|
||||||
<div class=" self-center text-xs text-gray-500">
|
<slot name="label">
|
||||||
{$i18n.t('Knowledge')}
|
<div class=" self-center text-xs text-gray-500">
|
||||||
</div>
|
{$i18n.t('Knowledge')}
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if loaded}
|
||||||
|
<div class="flex shrink-0 items-center gap-2">
|
||||||
|
<div class="min-w-0">
|
||||||
|
<KnowledgeSelector
|
||||||
|
on:select={(e) => {
|
||||||
|
const item = e.detail;
|
||||||
|
|
||||||
|
if (!selectedItems.find((k) => k.id === item.id)) {
|
||||||
|
selectedItems = [
|
||||||
|
...selectedItems,
|
||||||
|
{
|
||||||
|
...item
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex min-w-0 items-center bg-transparent text-xs text-gray-500 outline-hidden hover:underline dark:text-gray-400"
|
||||||
|
>
|
||||||
|
<span class="truncate">{$i18n.t('Select Knowledge')}</span>
|
||||||
|
</div>
|
||||||
|
</KnowledgeSelector>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if $user?.role === 'admin' || $user?.permissions?.chat?.file_upload}
|
||||||
|
<button
|
||||||
|
class="self-center bg-transparent text-xs text-gray-500 hover:underline dark:text-gray-400"
|
||||||
|
type="button"
|
||||||
|
aria-label={$i18n.t('Upload Files')}
|
||||||
|
on:click={() => {
|
||||||
|
filesInputElement.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('Upload')}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col mb-1">
|
<div class="flex flex-col mb-1">
|
||||||
{#if selectedItems?.length > 0}
|
{#if selectedItems?.length > 0}
|
||||||
<div class=" flex flex-wrap items-center gap-2 mb-2.5">
|
<div class=" flex flex-wrap items-center gap-1.5 mb-2.5">
|
||||||
{#each selectedItems as file, fileIdx}
|
{#each selectedItems as file, fileIdx}
|
||||||
<FileItem
|
<Tooltip content={file.description || file.name || file.id}>
|
||||||
{file}
|
|
||||||
small={true}
|
|
||||||
item={file}
|
|
||||||
name={file.name}
|
|
||||||
modal={true}
|
|
||||||
edit={true}
|
|
||||||
loading={file.status === 'uploading'}
|
|
||||||
type={file?.legacy
|
|
||||||
? `Legacy${file.type ? ` ${file.type}` : ''}`
|
|
||||||
: (file?.type ?? 'collection')}
|
|
||||||
dismissible
|
|
||||||
on:dismiss={(e) => {
|
|
||||||
selectedItems = selectedItems.filter((_, idx) => idx !== fileIdx);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if loaded}
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<div class="min-w-0">
|
|
||||||
<KnowledgeSelector
|
|
||||||
on:select={(e) => {
|
|
||||||
const item = e.detail;
|
|
||||||
|
|
||||||
if (!selectedItems.find((k) => k.id === item.id)) {
|
|
||||||
selectedItems = [
|
|
||||||
...selectedItems,
|
|
||||||
{
|
|
||||||
...item
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="flex min-w-0 items-center bg-transparent text-xs text-gray-500 outline-hidden hover:underline dark:text-gray-400"
|
class="flex max-w-56 items-center gap-1.5 py-0.5 pr-2 text-xs text-gray-700 dark:text-gray-200"
|
||||||
>
|
>
|
||||||
<span class="truncate">{$i18n.t('Select Knowledge')}</span>
|
<div class="shrink-0 text-gray-500 dark:text-gray-400">
|
||||||
</div>
|
{#if file.status === 'uploading'}
|
||||||
</KnowledgeSelector>
|
<Spinner className="size-3.5" />
|
||||||
</div>
|
{:else if file.type === 'collection'}
|
||||||
|
<Database className="size-3.5" />
|
||||||
|
{:else if file.type === 'note'}
|
||||||
|
<PageEdit className="size-3.5" />
|
||||||
|
{:else if file.type === 'chat'}
|
||||||
|
<ChatBubble className="size-3.5" />
|
||||||
|
{:else if file.type === 'folder'}
|
||||||
|
<Folder className="size-3.5" />
|
||||||
|
{:else}
|
||||||
|
<DocumentPage className="size-3.5" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if $user?.role === 'admin' || $user?.permissions?.chat?.file_upload}
|
<div class="min-w-0 truncate">
|
||||||
<button
|
{file.name || file.id}
|
||||||
class="self-center bg-transparent text-xs text-gray-500 hover:underline dark:text-gray-400"
|
</div>
|
||||||
type="button"
|
|
||||||
aria-label={$i18n.t('Upload Files')}
|
{#if file.status === 'uploading'}
|
||||||
on:click={() => {
|
<div class="shrink-0 text-gray-400 dark:text-gray-500">
|
||||||
filesInputElement.click();
|
{$i18n.t('Uploading')}
|
||||||
}}
|
</div>
|
||||||
>
|
{/if}
|
||||||
{$i18n.t('Upload')}
|
|
||||||
</button>
|
<button
|
||||||
{/if}
|
type="button"
|
||||||
|
class="flex size-4 shrink-0 items-center justify-center text-gray-400 dark:text-gray-500"
|
||||||
|
aria-label={$i18n.t('Remove File')}
|
||||||
|
on:click={() => {
|
||||||
|
selectedItems = selectedItems.filter((_, idx) => idx !== fileIdx);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<XMark className="size-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="py-0.5 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
|
||||||
|
on:click={() => {
|
||||||
|
selectedItems = [];
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('Disable all')}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- {knowledge} -->
|
<!-- {knowledge} -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="h-[1.6875rem] px-2 rounded-xl w-full text-left flex justify-between items-center text-[13px] bg-transparent hover:text-gray-900 dark:hover:text-gray-100 selected-command-option-button"
|
class="h-[1.6875rem] px-2 rounded-xl w-full text-left flex justify-between items-center text-[13px] bg-transparent transition-colors hover:bg-gray-50/40 hover:text-gray-900 dark:hover:bg-gray-800/40 dark:hover:text-gray-100 selected-command-option-button"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="w-full flex-1"
|
class="w-full flex-1"
|
||||||
|
|||||||
@@ -27,18 +27,18 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="flex w-full justify-between mb-1">
|
<div class="flex w-full items-center gap-2 mb-1">
|
||||||
<div class=" self-center text-xs text-gray-500">{$i18n.t('Skills')}</div>
|
<div class=" self-center text-xs text-gray-500">{$i18n.t('Skills')}</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col mb-1">
|
|
||||||
{#if activeSkills.length > 0}
|
{#if activeSkills.length > 0}
|
||||||
<TypeaheadSelector
|
<TypeaheadSelector
|
||||||
id="model-skills-selector"
|
id="model-skills-selector"
|
||||||
items={activeSkills}
|
items={activeSkills}
|
||||||
selectedIds={selectedSkillIds}
|
selectedIds={selectedSkillIds}
|
||||||
className="w-48 max-w-full"
|
|
||||||
placeholder={$i18n.t('Search skills')}
|
placeholder={$i18n.t('Search skills')}
|
||||||
|
triggerLabel={$i18n.t('Select Skill')}
|
||||||
|
emptyLabel={$i18n.t('No skills found')}
|
||||||
|
variant="dropdown"
|
||||||
on:select={(e) => {
|
on:select={(e) => {
|
||||||
toggleSkill(e.detail);
|
toggleSkill(e.detail);
|
||||||
}}
|
}}
|
||||||
@@ -48,8 +48,12 @@
|
|||||||
];
|
];
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class=" flex items-center flex-wrap">
|
<div class="flex flex-col mb-1">
|
||||||
|
{#if activeSkills.length > 0}
|
||||||
|
<div class=" flex items-center flex-wrap mt-1">
|
||||||
{#each selectedSkills as skill, skillIdx}
|
{#each selectedSkills as skill, skillIdx}
|
||||||
<div class=" flex items-center gap-2 mr-3">
|
<div class=" flex items-center gap-2 mr-3">
|
||||||
<div class="self-center flex items-center">
|
<div class="self-center flex items-center">
|
||||||
@@ -79,7 +83,7 @@
|
|||||||
selectedSkillIds = [];
|
selectedSkillIds = [];
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Disable all
|
{$i18n.t('Disable all')}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,18 +27,18 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="flex w-full justify-between mb-1">
|
<div class="flex w-full items-center gap-2 mb-1">
|
||||||
<div class=" self-center text-xs text-gray-500">{$i18n.t('Tools')}</div>
|
<div class=" self-center text-xs text-gray-500">{$i18n.t('Tools')}</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col mb-1">
|
|
||||||
{#if tools.length > 0}
|
{#if tools.length > 0}
|
||||||
<TypeaheadSelector
|
<TypeaheadSelector
|
||||||
id="model-tools-selector"
|
id="model-tools-selector"
|
||||||
items={tools}
|
items={tools}
|
||||||
selectedIds={selectedToolIds}
|
selectedIds={selectedToolIds}
|
||||||
className="w-48 max-w-full"
|
|
||||||
placeholder={$i18n.t('Search tools')}
|
placeholder={$i18n.t('Search tools')}
|
||||||
|
triggerLabel={$i18n.t('Select Tool')}
|
||||||
|
emptyLabel={$i18n.t('No tools found')}
|
||||||
|
variant="dropdown"
|
||||||
on:select={(e) => {
|
on:select={(e) => {
|
||||||
toggleTool(e.detail);
|
toggleTool(e.detail);
|
||||||
}}
|
}}
|
||||||
@@ -46,8 +46,12 @@
|
|||||||
selectedToolIds = [...new Set([...selectedToolIds, ...e.detail.map((tool) => tool.id)])];
|
selectedToolIds = [...new Set([...selectedToolIds, ...e.detail.map((tool) => tool.id)])];
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class=" flex items-center flex-wrap">
|
<div class="flex flex-col mb-1">
|
||||||
|
{#if tools.length > 0}
|
||||||
|
<div class=" flex items-center flex-wrap mt-1">
|
||||||
{#each selectedTools as tool, toolIdx}
|
{#each selectedTools as tool, toolIdx}
|
||||||
<div class=" flex items-center gap-2 mr-3">
|
<div class=" flex items-center gap-2 mr-3">
|
||||||
<div class="self-center flex items-center">
|
<div class="self-center flex items-center">
|
||||||
@@ -77,7 +81,7 @@
|
|||||||
selectedToolIds = [];
|
selectedToolIds = [];
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Disable all
|
{$i18n.t('Disable all')}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher, tick } from 'svelte';
|
||||||
|
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||||
|
import Search from '$lib/components/icons/Search.svelte';
|
||||||
import TTSVoiceInput from './TTSVoiceInput.svelte';
|
import TTSVoiceInput from './TTSVoiceInput.svelte';
|
||||||
|
|
||||||
type Item = {
|
type Item = {
|
||||||
@@ -13,29 +15,149 @@
|
|||||||
|
|
||||||
export let items: Item[] = [];
|
export let items: Item[] = [];
|
||||||
export let placeholder = '';
|
export let placeholder = '';
|
||||||
|
export let triggerLabel = '';
|
||||||
|
export let emptyLabel = '';
|
||||||
export let id = 'typeahead-selector';
|
export let id = 'typeahead-selector';
|
||||||
export let className = 'w-full';
|
export let className = 'w-full';
|
||||||
export let selectedIds: string[] | null = null;
|
export let selectedIds: string[] | null = null;
|
||||||
|
export let variant: 'inline' | 'dropdown' = 'inline';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
select: Item;
|
select: Item;
|
||||||
enableall: Item[];
|
enableall: Item[];
|
||||||
}>();
|
}>();
|
||||||
let value = '';
|
let value = '';
|
||||||
|
let show = false;
|
||||||
|
let inputElement: HTMLInputElement | null = null;
|
||||||
|
|
||||||
|
$: query = value.trim().toLowerCase();
|
||||||
|
$: matchedItems = (items ?? []).filter((item) => {
|
||||||
|
const id = item.id.toLowerCase();
|
||||||
|
const name = (item.name ?? '').toLowerCase();
|
||||||
|
const description = (item.description ?? item.meta?.description ?? '').toLowerCase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
query === '' || id.includes(query) || name.includes(query) || description.includes(query)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$: if (variant === 'dropdown' && show) {
|
||||||
|
tick().then(() => inputElement?.focus());
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectItem = (item: Item) => {
|
||||||
|
dispatch('select', item);
|
||||||
|
show = false;
|
||||||
|
value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const enableItems = () => {
|
||||||
|
dispatch('enableall', matchedItems);
|
||||||
|
show = false;
|
||||||
|
value = '';
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mb-1 block">
|
{#if variant === 'dropdown'}
|
||||||
<TTSVoiceInput
|
<Dropdown
|
||||||
{id}
|
bind:show
|
||||||
voices={items}
|
onOpenChange={(state) => {
|
||||||
{placeholder}
|
if (!state) {
|
||||||
{className}
|
value = '';
|
||||||
{selectedIds}
|
}
|
||||||
bind:value
|
|
||||||
on:select={(e) => {
|
|
||||||
dispatch('select', e.detail);
|
|
||||||
if (selectedIds === null) value = '';
|
|
||||||
}}
|
}}
|
||||||
on:enableall={(e) => dispatch('enableall', e.detail)}
|
>
|
||||||
/>
|
<div
|
||||||
</div>
|
class="flex min-w-0 items-center bg-transparent text-xs text-gray-500 outline-hidden hover:underline dark:text-gray-400"
|
||||||
|
>
|
||||||
|
<span class="truncate">{triggerLabel || placeholder}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div slot="content">
|
||||||
|
<div
|
||||||
|
class="z-[10000] flex w-64 flex-col rounded-xl border border-gray-200 bg-white p-0.5 text-black shadow-lg dark:border-gray-800 dark:bg-gray-850 dark:text-white"
|
||||||
|
>
|
||||||
|
<div class="flex w-full space-x-1.5 px-1.5 pb-0.5">
|
||||||
|
<div class="flex flex-1">
|
||||||
|
<div class="self-center mr-1.5">
|
||||||
|
<Search className="size-3.5" />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
bind:this={inputElement}
|
||||||
|
bind:value
|
||||||
|
id={`${id}-input`}
|
||||||
|
class="w-full bg-transparent py-0.5 text-[13px] outline-hidden"
|
||||||
|
type="text"
|
||||||
|
{placeholder}
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex max-h-56 flex-col gap-0.5 overflow-y-scroll">
|
||||||
|
{#if selectedIds !== null && matchedItems.length > 0}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="h-[1.6875rem] w-full rounded-xl px-2 text-left text-[13px] text-gray-700 transition-colors hover:bg-gray-50/40 hover:text-gray-900 dark:text-gray-200 dark:hover:bg-gray-800/40 dark:hover:text-gray-100"
|
||||||
|
on:click={enableItems}
|
||||||
|
>
|
||||||
|
<span class="truncate">Enable all ({matchedItems.length})</span>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if matchedItems.length === 0}
|
||||||
|
<div class="pt-4 pb-6 text-center text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{emptyLabel || placeholder}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
{#each matchedItems as item (item.id)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex h-[1.6875rem] w-full items-center justify-between gap-2 rounded-xl px-2 text-left text-[13px] transition-colors hover:bg-gray-50/40 hover:text-gray-900 dark:hover:bg-gray-800/40 dark:hover:text-gray-100 selected-command-option-button"
|
||||||
|
aria-pressed={selectedIds?.includes(item.id) ?? false}
|
||||||
|
on:click={() => {
|
||||||
|
selectItem(item);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="min-w-0 flex-1 truncate">{item.name || item.id}</span>
|
||||||
|
{#if selectedIds !== null && selectedIds.includes(item.id)}
|
||||||
|
<svg
|
||||||
|
class="size-3.5 shrink-0 text-gray-500 dark:text-gray-400"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="3"
|
||||||
|
d="m5 12 4.7 4.5 9.3-9"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
{:else}
|
||||||
|
<div class="mb-1 block">
|
||||||
|
<TTSVoiceInput
|
||||||
|
{id}
|
||||||
|
voices={items}
|
||||||
|
{placeholder}
|
||||||
|
{className}
|
||||||
|
{selectedIds}
|
||||||
|
bind:value
|
||||||
|
on:select={(e) => {
|
||||||
|
dispatch('select', e.detail);
|
||||||
|
if (selectedIds === null) value = '';
|
||||||
|
}}
|
||||||
|
on:enableall={(e) => dispatch('enableall', e.detail)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
Reference in new issue
Block a user