This commit is contained in:
Timothy Jaeryang Baek
2026-08-22 08:43:46 -04:00
parent 1b3b9375bb
commit d17f06a235
5 changed files with 216 additions and 18 deletions
@@ -88,8 +88,8 @@
</script>
<div class={(filteredItems ?? []).length > 0 ? '' : 'hidden'} id="suggestions-container">
<DropdownMenu className="w-72 font-sans text-xs">
<div class="overflow-y-auto scrollbar-thin max-h-60">
<DropdownMenu className="w-72 max-w-[calc(100vw-1rem)] overflow-x-hidden font-sans text-xs">
<div class="max-h-60 overflow-y-auto overflow-x-hidden scrollbar-thin">
{#if char === '/'}
<SlashCommands
bind:this={suggestionElement}
@@ -192,6 +192,13 @@
type: 'file',
data: data
});
} else if (type === 'filesystem') {
insertTextHandler('');
onUpload({
type: 'filesystem',
data: data
});
} else if (type === 'web') {
insertTextHandler('');
@@ -2,11 +2,20 @@
import Fuse from 'fuse.js';
import { getContext, onDestroy, onMount, tick } from 'svelte';
import { folders, models } from '$lib/stores';
import {
chatId as activeChatId,
folders,
models,
selectedTerminalId,
settings,
terminalServers
} from '$lib/stores';
import { WEBUI_API_BASE_URL } from '$lib/constants';
import { getFolders } from '$lib/apis/folders';
import { searchKnowledgeBases, searchKnowledgeFiles } from '$lib/apis/knowledge';
import { searchFiles } from '$lib/apis/terminal';
import { decodeString, isValidHttpUrl, isYoutubeUrl } from '$lib/utils';
import { isTemporaryChatId } from '$lib/utils/chatId';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Database from '$lib/components/icons/Database.svelte';
@@ -26,6 +35,7 @@
let searchDebounceTimer: ReturnType<typeof setTimeout>;
let folderItems: any[] = [];
let filesystemItems: any[] = [];
let knowledgeItems: any[] = [];
let fileItems: any[] = [];
let modelItems: any[] = [];
@@ -48,6 +58,7 @@
$: filteredModels = query ? fuse.search(query).map((e) => e.item) : modelItems;
$: knowledgeResults = [
...filesystemItems,
...(query.startsWith('http')
? isYoutubeUrl(query)
? [{ type: 'youtube', name: query, description: query }]
@@ -81,11 +92,68 @@
});
const getItems = () => {
const terminal = getSelectedTerminal();
getFilesystemItems(terminal);
getFolderItems();
getKnowledgeItems();
getKnowledgeFileItems();
};
const getFilesystemItems = async (terminal = getSelectedTerminal()) => {
if (!terminal) {
filesystemItems = [];
return;
}
const res = await searchFiles(
terminal.url,
terminal.key,
query,
'.',
20,
'any',
$activeChatId || undefined,
localStorage.getItem('fileNav:showHidden') === 'true'
).catch(() => null);
filesystemItems = (res?.results ?? []).map((item: any) => ({
...item,
type: 'filesystem',
filesystem_type: item.type,
id: item.path,
url: item.path,
name: item.name,
description: item.path,
status: 'processed'
}));
};
const chatContext = (terminal: any) => terminal?.contexts?.chat ?? {};
const getSelectedTerminal = (): { url: string; key: string } | null => {
if (!$selectedTerminalId) return null;
const systemTerminal = ($terminalServers ?? []).find(
(t) => t.id && t.id === $selectedTerminalId
);
const systemChatContext = chatContext(systemTerminal);
if (systemTerminal) {
if (
systemChatContext === false ||
(isTemporaryChatId($activeChatId) && systemChatContext?.context_id === 'chat_id')
) {
return null;
}
return { url: systemTerminal.url, key: localStorage.token };
}
const directTerminal = ($settings?.terminalServers ?? []).find(
(t: any) => t.url === $selectedTerminalId && t.enabled
);
return directTerminal?.url ? { url: directTerminal.url, key: directTerminal.key ?? '' } : null;
};
const getFolderItems = () => {
folderItems = (($folders ?? []) as any[])
.map((folder) => ({
@@ -122,6 +190,11 @@
};
const selectKnowledgeItem = (item: any) => {
if (item.type === 'filesystem') {
onSelect({ type: 'filesystem', data: item });
return;
}
if (['youtube', 'web'].includes(item.type)) {
if (isValidHttpUrl(query)) {
onSelect({ type: 'web', data: query });
@@ -175,12 +248,14 @@
{$i18n.t('Collections')}
{:else if item?.type === 'file'}
{$i18n.t('Files')}
{:else if item?.type === 'filesystem'}
{$i18n.t('Filesystem')}
{/if}
</div>
{/if}
<button
class="flex h-[1.6875rem] w-full items-center justify-between rounded-xl px-2 text-left text-[0.8125rem] hover:bg-gray-50/40 dark:hover:bg-gray-800/40 {itemIdx ===
class="flex h-[1.6875rem] w-full max-w-full items-center justify-between overflow-hidden rounded-xl px-2 text-left text-[0.8125rem] hover:bg-gray-50/40 dark:hover:bg-gray-800/40 {itemIdx ===
selectedIdx
? 'bg-gray-50/40 dark:bg-gray-800/40 dark:text-gray-100 selected-command-option-button'
: ''}"
@@ -193,24 +268,29 @@
}}
data-selected={itemIdx === selectedIdx}
>
<div class="flex min-w-0 items-center gap-1.5 text-black dark:text-gray-100">
<div
class="flex w-full min-w-0 items-center gap-1.5 overflow-hidden text-black dark:text-gray-100"
>
<Tooltip
className="shrink-0 flex"
content={item?.legacy
? $i18n.t('Legacy')
: item?.type === 'file'
? `${item?.collection?.name} > ${$i18n.t('File')}`
: item?.type === 'collection'
? $i18n.t('Collection')
: item?.type === 'youtube'
? $i18n.t('YouTube')
: item?.type === 'web'
? $i18n.t('Web')
: ''}
: item?.type === 'filesystem'
? item?.path
: item?.type === 'file'
? `${item?.collection?.name} > ${$i18n.t('File')}`
: item?.type === 'collection'
? $i18n.t('Collection')
: item?.type === 'youtube'
? $i18n.t('YouTube')
: item?.type === 'web'
? $i18n.t('Web')
: ''}
placement="top"
>
{#if item?.type === 'collection'}
<Database className="size-3.5" />
{:else if item?.type === 'folder'}
{:else if item?.type === 'folder' || item?.filesystem_type === 'directory'}
<Folder className="size-3.5" />
{:else if item?.type === 'youtube'}
<Youtube className="size-3.5" />
@@ -221,8 +301,12 @@
{/if}
</Tooltip>
<Tooltip content={`${decodeString(item?.name)}`} placement="top-start">
<div class="min-w-0 flex-1 truncate">
<Tooltip
className="min-w-0 flex-1"
content={`${decodeString(item?.name)}`}
placement="top-start"
>
<div class="line-clamp-1 min-w-0 overflow-hidden break-all">
{decodeString(item?.name)}
</div>
</Tooltip>