Jelajahi Sumber

refac: lazy load prompts/tools/functions/tags

Timothy Jaeryang Baek 5 bulan lalu
induk
melakukan
f10d0df490

+ 88 - 69
src/lib/components/chat/Controls/Valves.svelte

@@ -7,12 +7,14 @@
 	import {
 	import {
 		getUserValvesSpecById as getToolUserValvesSpecById,
 		getUserValvesSpecById as getToolUserValvesSpecById,
 		getUserValvesById as getToolUserValvesById,
 		getUserValvesById as getToolUserValvesById,
-		updateUserValvesById as updateToolUserValvesById
+		updateUserValvesById as updateToolUserValvesById,
+		getTools
 	} from '$lib/apis/tools';
 	} from '$lib/apis/tools';
 	import {
 	import {
 		getUserValvesSpecById as getFunctionUserValvesSpecById,
 		getUserValvesSpecById as getFunctionUserValvesSpecById,
 		getUserValvesById as getFunctionUserValvesById,
 		getUserValvesById as getFunctionUserValvesById,
-		updateUserValvesById as updateFunctionUserValvesById
+		updateUserValvesById as updateFunctionUserValvesById,
+		getFunctions
 	} from '$lib/apis/functions';
 	} from '$lib/apis/functions';
 
 
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
@@ -112,77 +114,94 @@
 	$: if (selectedId) {
 	$: if (selectedId) {
 		getUserValves();
 		getUserValves();
 	}
 	}
-</script>
 
 
-<form
-	class="flex flex-col h-full justify-between space-y-3 text-sm"
-	on:submit|preventDefault={() => {
-		submitHandler();
-		dispatch('save');
-	}}
->
-	<div class="flex flex-col">
-		<div class="space-y-1">
-			<div class="flex gap-2">
-				<div class="flex-1">
-					<select
-						class="  w-full rounded text-xs py-2 px-1 bg-transparent outline-none"
-						bind:value={tab}
-						placeholder="Select"
-					>
-						<option value="tools" class="bg-gray-100 dark:bg-gray-800">{$i18n.t('Tools')}</option>
-						<option value="functions" class="bg-gray-100 dark:bg-gray-800"
-							>{$i18n.t('Functions')}</option
-						>
-					</select>
-				</div>
+	onMount(async () => {
+		loading = true;
 
 
-				<div class="flex-1">
-					<select
-						class="w-full rounded py-2 px-1 text-xs bg-transparent outline-none"
-						bind:value={selectedId}
-						on:change={async () => {
-							await tick();
-						}}
-					>
-						{#if tab === 'tools'}
-							<option value="" selected disabled class="bg-gray-100 dark:bg-gray-800"
-								>{$i18n.t('Select a tool')}</option
-							>
+		if ($functions.length === 0) {
+			functions.set(await getFunctions(localStorage.token));
+		}
+		if ($tools.length === 0) {
+			tools.set(await getTools(localStorage.token));
+		}
 
 
-							{#each $tools as tool, toolIdx}
-								<option value={tool.id} class="bg-gray-100 dark:bg-gray-800">{tool.name}</option>
-							{/each}
-						{:else if tab === 'functions'}
-							<option value="" selected disabled class="bg-gray-100 dark:bg-gray-800"
-								>{$i18n.t('Select a function')}</option
-							>
+		loading = false;
+	});
+</script>
 
 
-							{#each $functions as func, funcIdx}
-								<option value={func.id} class="bg-gray-100 dark:bg-gray-800">{func.name}</option>
-							{/each}
-						{/if}
-					</select>
+{#if !loading}
+	<form
+		class="flex flex-col h-full justify-between space-y-3 text-sm"
+		on:submit|preventDefault={() => {
+			submitHandler();
+			dispatch('save');
+		}}
+	>
+		<div class="flex flex-col">
+			<div class="space-y-1">
+				<div class="flex gap-2">
+					<div class="flex-1">
+						<select
+							class="  w-full rounded text-xs py-2 px-1 bg-transparent outline-none"
+							bind:value={tab}
+							placeholder="Select"
+						>
+							<option value="tools" class="bg-gray-100 dark:bg-gray-800">{$i18n.t('Tools')}</option>
+							<option value="functions" class="bg-gray-100 dark:bg-gray-800"
+								>{$i18n.t('Functions')}</option
+							>
+						</select>
+					</div>
+
+					<div class="flex-1">
+						<select
+							class="w-full rounded py-2 px-1 text-xs bg-transparent outline-none"
+							bind:value={selectedId}
+							on:change={async () => {
+								await tick();
+							}}
+						>
+							{#if tab === 'tools'}
+								<option value="" selected disabled class="bg-gray-100 dark:bg-gray-800"
+									>{$i18n.t('Select a tool')}</option
+								>
+
+								{#each $tools as tool, toolIdx}
+									<option value={tool.id} class="bg-gray-100 dark:bg-gray-800">{tool.name}</option>
+								{/each}
+							{:else if tab === 'functions'}
+								<option value="" selected disabled class="bg-gray-100 dark:bg-gray-800"
+									>{$i18n.t('Select a function')}</option
+								>
+
+								{#each $functions as func, funcIdx}
+									<option value={func.id} class="bg-gray-100 dark:bg-gray-800">{func.name}</option>
+								{/each}
+							{/if}
+						</select>
+					</div>
 				</div>
 				</div>
 			</div>
 			</div>
-		</div>
 
 
-		{#if selectedId}
-			<hr class="dark:border-gray-800 my-1 w-full" />
-
-			<div class="my-2 text-xs">
-				{#if !loading}
-					<Valves
-						{valvesSpec}
-						bind:valves
-						on:change={() => {
-							debounceSubmitHandler();
-						}}
-					/>
-				{:else}
-					<Spinner className="size-5" />
-				{/if}
-			</div>
-		{/if}
-	</div>
-</form>
+			{#if selectedId}
+				<hr class="dark:border-gray-800 my-1 w-full" />
+
+				<div class="my-2 text-xs">
+					{#if !loading}
+						<Valves
+							{valvesSpec}
+							bind:valves
+							on:change={() => {
+								debounceSubmitHandler();
+							}}
+						/>
+					{:else}
+						<Spinner className="size-5" />
+					{/if}
+				</div>
+			{/if}
+		</div>
+	</form>
+{:else}
+	<Spinner className="size-6" />
+{/if}

+ 1 - 10
src/lib/components/chat/MessageInput.svelte

@@ -519,16 +519,7 @@
 									<InputMenu
 									<InputMenu
 										bind:webSearchEnabled
 										bind:webSearchEnabled
 										bind:selectedToolIds
 										bind:selectedToolIds
-										tools={$tools.reduce((a, e, i, arr) => {
-											if (availableToolIds.includes(e.id) || ($_user?.role ?? 'user') === 'admin') {
-												a[e.id] = {
-													name: e.name,
-													description: e.meta.description,
-													enabled: false
-												};
-											}
-											return a;
-										}, {})}
+										{availableToolIds}
 										uploadFilesHandler={() => {
 										uploadFilesHandler={() => {
 											filesInputElement.click();
 											filesInputElement.click();
 										}}
 										}}

+ 90 - 50
src/lib/components/chat/MessageInput/Commands.svelte

@@ -1,19 +1,24 @@
 <script>
 <script>
-	import { createEventDispatcher } from 'svelte';
+	import { createEventDispatcher, onMount } from 'svelte';
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
 
 
 	const dispatch = createEventDispatcher();
 	const dispatch = createEventDispatcher();
 
 
+	import { knowledge, prompts } from '$lib/stores';
+
+	import { removeLastWordFromString } from '$lib/utils';
+	import { getPrompts } from '$lib/apis/prompts';
+	import { getKnowledgeItems } from '$lib/apis/knowledge';
+
 	import Prompts from './Commands/Prompts.svelte';
 	import Prompts from './Commands/Prompts.svelte';
 	import Knowledge from './Commands/Knowledge.svelte';
 	import Knowledge from './Commands/Knowledge.svelte';
 	import Models from './Commands/Models.svelte';
 	import Models from './Commands/Models.svelte';
-
-	import { removeLastWordFromString } from '$lib/utils';
-	import { processWeb, processYoutubeVideo } from '$lib/apis/retrieval';
+	import Spinner from '$lib/components/common/Spinner.svelte';
 
 
 	export let prompt = '';
 	export let prompt = '';
 	export let files = [];
 	export let files = [];
 
 
+	let loading = false;
 	let commandElement = null;
 	let commandElement = null;
 
 
 	export const selectUp = () => {
 	export const selectUp = () => {
@@ -26,55 +31,90 @@
 
 
 	let command = '';
 	let command = '';
 	$: command = prompt?.split('\n').pop()?.split(' ')?.pop() ?? '';
 	$: command = prompt?.split('\n').pop()?.split(' ')?.pop() ?? '';
+
+	let show = false;
+	$: show = ['/', '#', '@'].includes(command?.charAt(0)) || '\\#' === command.slice(0, 2);
+
+	$: if (show) {
+		init();
+	}
+
+	const init = async () => {
+		loading = true;
+		await Promise.all([
+			(async () => {
+				prompts.set(await getPrompts(localStorage.token));
+			})(),
+			(async () => {
+				knowledge.set(await getKnowledgeItems(localStorage.token));
+			})()
+		]);
+		loading = false;
+	};
 </script>
 </script>
 
 
-{#if ['/', '#', '@'].includes(command?.charAt(0)) || '\\#' === command.slice(0, 2)}
-	{#if command?.charAt(0) === '/'}
-		<Prompts bind:this={commandElement} bind:prompt bind:files {command} />
-	{:else if (command?.charAt(0) === '#' && command.startsWith('#') && !command.includes('# ')) || ('\\#' === command.slice(0, 2) && command.startsWith('#') && !command.includes('# '))}
-		<Knowledge
-			bind:this={commandElement}
-			bind:prompt
-			command={command.includes('\\#') ? command.slice(2) : command}
-			on:youtube={(e) => {
-				console.log(e);
-				dispatch('upload', {
-					type: 'youtube',
-					data: e.detail
-				});
-			}}
-			on:url={(e) => {
-				console.log(e);
-				dispatch('upload', {
-					type: 'web',
-					data: e.detail
-				});
-			}}
-			on:select={(e) => {
-				console.log(e);
-				files = [
-					...files,
-					{
-						...e.detail,
-						status: 'processed'
-					}
-				];
+{#if show}
+	{#if !loading}
+		{#if command?.charAt(0) === '/'}
+			<Prompts bind:this={commandElement} bind:prompt bind:files {command} />
+		{:else if (command?.charAt(0) === '#' && command.startsWith('#') && !command.includes('# ')) || ('\\#' === command.slice(0, 2) && command.startsWith('#') && !command.includes('# '))}
+			<Knowledge
+				bind:this={commandElement}
+				bind:prompt
+				command={command.includes('\\#') ? command.slice(2) : command}
+				on:youtube={(e) => {
+					console.log(e);
+					dispatch('upload', {
+						type: 'youtube',
+						data: e.detail
+					});
+				}}
+				on:url={(e) => {
+					console.log(e);
+					dispatch('upload', {
+						type: 'web',
+						data: e.detail
+					});
+				}}
+				on:select={(e) => {
+					console.log(e);
+					files = [
+						...files,
+						{
+							...e.detail,
+							status: 'processed'
+						}
+					];
 
 
-				dispatch('select');
-			}}
-		/>
-	{:else if command?.charAt(0) === '@'}
-		<Models
-			bind:this={commandElement}
-			{command}
-			on:select={(e) => {
-				prompt = removeLastWordFromString(prompt, command);
+					dispatch('select');
+				}}
+			/>
+		{:else if command?.charAt(0) === '@'}
+			<Models
+				bind:this={commandElement}
+				{command}
+				on:select={(e) => {
+					prompt = removeLastWordFromString(prompt, command);
 
 
-				dispatch('select', {
-					type: 'model',
-					data: e.detail
-				});
-			}}
-		/>
+					dispatch('select', {
+						type: 'model',
+						data: e.detail
+					});
+				}}
+			/>
+		{/if}
+	{:else}
+		<div
+			id="commands-container"
+			class="pl-3 pr-14 mb-3 text-left w-full absolute bottom-0 left-0 right-0 z-10"
+		>
+			<div class="flex w-full rounded-xl border border-gray-50 dark:border-gray-850">
+				<div
+					class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
+				>
+					<Spinner />
+				</div>
+			</div>
+		</div>
 	{/if}
 	{/if}
 {/if}
 {/if}

+ 28 - 13
src/lib/components/chat/MessageInput/InputMenu.svelte

@@ -1,37 +1,52 @@
 <script lang="ts">
 <script lang="ts">
 	import { DropdownMenu } from 'bits-ui';
 	import { DropdownMenu } from 'bits-ui';
 	import { flyAndScale } from '$lib/utils/transitions';
 	import { flyAndScale } from '$lib/utils/transitions';
-	import { getContext } from 'svelte';
+	import { getContext, onMount } from 'svelte';
+
+	import { config, user, tools as _tools } from '$lib/stores';
 
 
 	import Dropdown from '$lib/components/common/Dropdown.svelte';
 	import Dropdown from '$lib/components/common/Dropdown.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import DocumentArrowUpSolid from '$lib/components/icons/DocumentArrowUpSolid.svelte';
 	import DocumentArrowUpSolid from '$lib/components/icons/DocumentArrowUpSolid.svelte';
 	import Switch from '$lib/components/common/Switch.svelte';
 	import Switch from '$lib/components/common/Switch.svelte';
 	import GlobeAltSolid from '$lib/components/icons/GlobeAltSolid.svelte';
 	import GlobeAltSolid from '$lib/components/icons/GlobeAltSolid.svelte';
-	import { config } from '$lib/stores';
 	import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte';
 	import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte';
+	import { getTools } from '$lib/apis/tools';
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
 	export let uploadFilesHandler: Function;
 	export let uploadFilesHandler: Function;
 
 
+	export let availableToolIds: string[] = [];
 	export let selectedToolIds: string[] = [];
 	export let selectedToolIds: string[] = [];
+
 	export let webSearchEnabled: boolean;
 	export let webSearchEnabled: boolean;
 
 
-	export let tools = {};
 	export let onClose: Function;
 	export let onClose: Function;
 
 
-	$: tools = Object.fromEntries(
-		Object.keys(tools).map((toolId) => [
-			toolId,
-			{
-				...tools[toolId],
-				enabled: selectedToolIds.includes(toolId)
-			}
-		])
-	);
-
+	let tools = {};
 	let show = false;
 	let show = false;
+
+	$: if (show) {
+		init();
+	}
+
+	const init = async () => {
+		if ($_tools === null) {
+			await _tools.set(await getTools(localStorage.token));
+		}
+
+		tools = $_tools.reduce((a, tool, i, arr) => {
+			if (availableToolIds.includes(tool.id) || ($user?.role ?? 'user') === 'admin') {
+				a[tool.id] = {
+					name: tool.name,
+					description: tool.meta.description,
+					enabled: selectedToolIds.includes(tool.id)
+				};
+			}
+			return a;
+		}, {});
+	};
 </script>
 </script>
 
 
 <Dropdown
 <Dropdown

+ 10 - 2
src/lib/components/layout/Sidebar/SearchInput.svelte

@@ -1,6 +1,6 @@
 <script lang="ts">
 <script lang="ts">
+	import { getAllTags } from '$lib/apis/chats';
 	import { tags } from '$lib/stores';
 	import { tags } from '$lib/stores';
-	import { stringify } from 'postcss';
 	import { getContext, createEventDispatcher, onMount, onDestroy, tick } from 'svelte';
 	import { getContext, createEventDispatcher, onMount, onDestroy, tick } from 'svelte';
 	import { fade } from 'svelte/transition';
 	import { fade } from 'svelte/transition';
 
 
@@ -15,13 +15,14 @@
 	let lastWord = '';
 	let lastWord = '';
 	$: lastWord = value ? value.split(' ').at(-1) : value;
 	$: lastWord = value ? value.split(' ').at(-1) : value;
 
 
-	let focused = false;
 	let options = [
 	let options = [
 		{
 		{
 			name: 'tag:',
 			name: 'tag:',
 			description: $i18n.t('search for tags')
 			description: $i18n.t('search for tags')
 		}
 		}
 	];
 	];
+	let focused = false;
+	let loading = false;
 
 
 	let filteredOptions = options;
 	let filteredOptions = options;
 	$: filteredOptions = options.filter((option) => {
 	$: filteredOptions = options.filter((option) => {
@@ -52,6 +53,12 @@
 			})
 			})
 		: [];
 		: [];
 
 
+	const initTags = async () => {
+		loading = true;
+		await tags.set(await getAllTags(localStorage.token));
+		loading = false;
+	};
+
 	const documentClickHandler = (e) => {
 	const documentClickHandler = (e) => {
 		const searchContainer = document.getElementById('search-container');
 		const searchContainer = document.getElementById('search-container');
 		const chatSearch = document.getElementById('chat-search');
 		const chatSearch = document.getElementById('chat-search');
@@ -99,6 +106,7 @@
 			}}
 			}}
 			on:focus={() => {
 			on:focus={() => {
 				focused = true;
 				focused = true;
+				initTags();
 			}}
 			}}
 			on:keydown={(e) => {
 			on:keydown={(e) => {
 				if (e.key === 'Enter') {
 				if (e.key === 'Enter') {

+ 6 - 4
src/lib/stores/index.ts

@@ -28,11 +28,13 @@ export const pinnedChats = writable([]);
 export const tags = writable([]);
 export const tags = writable([]);
 
 
 export const models: Writable<Model[]> = writable([]);
 export const models: Writable<Model[]> = writable([]);
-export const prompts: Writable<Prompt[]> = writable([]);
-export const knowledge: Writable<Document[]> = writable([]);
 
 
-export const tools = writable([]);
-export const functions = writable([]);
+
+export const prompts: Writable<null | Prompt[]> = writable(null);
+export const knowledge: Writable<null | Document[]> = writable(null);
+export const tools = writable(null);
+export const functions = writable(null);
+
 
 
 export const banners: Writable<Banner[]> = writable([]);
 export const banners: Writable<Banner[]> = writable([]);
 
 

+ 0 - 15
src/routes/(app)/+layout.svelte

@@ -101,23 +101,8 @@
 				(async () => {
 				(async () => {
 					models.set(await getModels());
 					models.set(await getModels());
 				})(),
 				})(),
-				(async () => {
-					prompts.set(await getPrompts(localStorage.token));
-				})(),
-				(async () => {
-					knowledge.set(await getKnowledgeItems(localStorage.token));
-				})(),
-				(async () => {
-					tools.set(await getTools(localStorage.token));
-				})(),
-				(async () => {
-					functions.set(await getFunctions(localStorage.token));
-				})(),
 				(async () => {
 				(async () => {
 					banners.set(await getBanners(localStorage.token));
 					banners.set(await getBanners(localStorage.token));
-				})(),
-				(async () => {
-					tags.set(await getAllTags(localStorage.token));
 				})()
 				})()
 			]);
 			]);
 
 

+ 35 - 1
src/routes/(app)/workspace/+layout.svelte

@@ -1,10 +1,25 @@
 <script lang="ts">
 <script lang="ts">
 	import { onMount, getContext } from 'svelte';
 	import { onMount, getContext } from 'svelte';
-	import { WEBUI_NAME, showSidebar, functions, user, mobile } from '$lib/stores';
+	import {
+		WEBUI_NAME,
+		showSidebar,
+		functions,
+		user,
+		mobile,
+		models,
+		prompts,
+		knowledge,
+		tools
+	} from '$lib/stores';
 	import { page } from '$app/stores';
 	import { page } from '$app/stores';
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
 
 
 	import MenuLines from '$lib/components/icons/MenuLines.svelte';
 	import MenuLines from '$lib/components/icons/MenuLines.svelte';
+	import { getModels } from '$lib/apis';
+	import { getPrompts } from '$lib/apis/prompts';
+	import { getKnowledgeItems } from '$lib/apis/knowledge';
+	import { getTools } from '$lib/apis/tools';
+	import { getFunctions } from '$lib/apis/functions';
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
@@ -14,6 +29,25 @@
 		if ($user?.role !== 'admin') {
 		if ($user?.role !== 'admin') {
 			await goto('/');
 			await goto('/');
 		}
 		}
+
+		await Promise.all([
+			(async () => {
+				models.set(await getModels());
+			})(),
+			(async () => {
+				knowledge.set(await getKnowledgeItems(localStorage.token));
+			})(),
+			(async () => {
+				prompts.set(await getPrompts(localStorage.token));
+			})(),
+			(async () => {
+				tools.set(await getTools(localStorage.token));
+			})(),
+			(async () => {
+				functions.set(await getFunctions(localStorage.token));
+			})()
+		]);
+
 		loaded = true;
 		loaded = true;
 	});
 	});
 </script>
 </script>