From b41e456c4fa4186ac8a479ad49590d6d5d3f4555 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 16 Nov 2024 18:35:14 -0800 Subject: [PATCH] enh: workspace loading indicator --- src/lib/components/workspace/Knowledge.svelte | 190 +++--- src/lib/components/workspace/Models.svelte | 590 +++++++++--------- src/lib/components/workspace/Prompts.svelte | 441 ++++++------- src/lib/components/workspace/Tools.svelte | 566 ++++++++--------- 4 files changed, 914 insertions(+), 873 deletions(-) diff --git a/src/lib/components/workspace/Knowledge.svelte b/src/lib/components/workspace/Knowledge.svelte index 7bb1ec5f0..e550239df 100644 --- a/src/lib/components/workspace/Knowledge.svelte +++ b/src/lib/components/workspace/Knowledge.svelte @@ -23,6 +23,9 @@ import Badge from '../common/Badge.svelte'; import Search from '../icons/Search.svelte'; import Plus from '../icons/Plus.svelte'; + import Spinner from '../common/Spinner.svelte'; + + let loaded = false; let query = ''; let selectedItem = null; @@ -61,6 +64,7 @@ onMount(async () => { knowledgeBases = await getKnowledgeBaseList(localStorage.token); + loaded = true; }); @@ -70,104 +74,110 @@ - { - deleteHandler(selectedItem); - }} -/> +{#if loaded} + { + deleteHandler(selectedItem); + }} + /> -
-
-
- {$i18n.t('Knowledge')} -
- {filteredItems.length} +
+
+
+ {$i18n.t('Knowledge')} +
+ {filteredItems.length} +
+
+ +
+
+
+ +
+ +
+ +
+ +
-
-
-
- -
- -
- -
+
+ {#each filteredItems as item} -
+ {/each}
-
-
- {#each filteredItems as item} - - {/each} -
- -
- ⓘ {$i18n.t("Use '#' in the prompt input to load and include your knowledge.")} -
+
+ ⓘ {$i18n.t("Use '#' in the prompt input to load and include your knowledge.")} +
+{:else} +
+ +
+{/if} diff --git a/src/lib/components/workspace/Models.svelte b/src/lib/components/workspace/Models.svelte index 29fe2c4c3..3c77abb70 100644 --- a/src/lib/components/workspace/Models.svelte +++ b/src/lib/components/workspace/Models.svelte @@ -31,11 +31,13 @@ import Plus from '../icons/Plus.svelte'; import ChevronRight from '../icons/ChevronRight.svelte'; import Switch from '../common/Switch.svelte'; + import Spinner from '../common/Spinner.svelte'; let shiftKey = false; let importFiles; let modelsImportInputElement: HTMLInputElement; + let loaded = false; let models = []; @@ -149,6 +151,8 @@ onMount(async () => { models = await getWorkspaceModels(localStorage.token); + loaded = true; + const onKeyDown = (event) => { if (event.key === 'Shift') { shiftKey = true; @@ -183,304 +187,312 @@ - { - deleteModelHandler(selectedModel); - }} -/> +{#if loaded} + { + deleteModelHandler(selectedModel); + }} + /> -
-
-
- {$i18n.t('Models')} -
- {filteredModels.length} -
-
- -
-
-
- +
+
+
+ {$i18n.t('Models')} +
+ {filteredModels.length}
-
-
- - - -
-
-
- - -
-
- - +
+
+ +
+ - -
-
- -
-
{$i18n.t('Create a model')}
-
- {$i18n.t('Customize models for a specific purpose')} -
-
-
- -
- {#each filteredModels as model} -
- -
-
- modelfile profile -
-
- -
- -
{model.name}
-
-
- {model?.meta?.description ?? model.id} -
-
-
-
- {#if shiftKey} - - - - {:else} - {#if $user?.role === 'admin' || model.user_id === $user?.id} - - - - - - {/if} - - { - shareModelHandler(model); - }} - cloneHandler={() => { - cloneModelHandler(model); - }} - exportHandler={() => { - exportModelHandler(model); - }} - hideHandler={() => { - hideModelHandler(model); - }} - deleteHandler={() => { - selectedModel = model; - showModelDeleteConfirm = true; - }} - onClose={() => {}} - > - - - -
- - { - toggleModelById(localStorage.token, model.id); - _models.set(await getModels(localStorage.token)); - }} - /> - -
- {/if} -
-
- {/each} -
- -{#if $user?.role === 'admin'} -
-
- { - console.log(importFiles); - - let reader = new FileReader(); - reader.onload = async (event) => { - let savedModels = JSON.parse(event.target.result); - console.log(savedModels); - - for (const model of savedModels) { - if (model?.info ?? false) { - if ($_models.find((m) => m.id === model.id)) { - await updateModelById(localStorage.token, model.id, model.info).catch((error) => { - return null; - }); - } else { - await createNewModel(localStorage.token, model.info).catch((error) => { - return null; - }); - } - } - } - - await _models.set(await getModels(localStorage.token)); - models = await getWorkspaceModels(localStorage.token); - }; - - reader.readAsText(importFiles[0]); - }} - /> - - - - -
-
-{/if} - -{#if $config?.features.enable_community_sharing} - + + +
+
+ + + +
+
+ +
+
{$i18n.t('Create a model')}
+
+ {$i18n.t('Customize models for a specific purpose')} +
+
+
+ +
+ {#each filteredModels as model} +
+ +
+
+ modelfile profile +
+
+ +
+ +
{model.name}
+
+
+ {model?.meta?.description ?? model.id} +
+
+
+
+ {#if shiftKey} + + + + {:else} + {#if $user?.role === 'admin' || model.user_id === $user?.id} + + + + + + {/if} + + { + shareModelHandler(model); + }} + cloneHandler={() => { + cloneModelHandler(model); + }} + exportHandler={() => { + exportModelHandler(model); + }} + hideHandler={() => { + hideModelHandler(model); + }} + deleteHandler={() => { + selectedModel = model; + showModelDeleteConfirm = true; + }} + onClose={() => {}} + > + + + +
+ + { + toggleModelById(localStorage.token, model.id); + _models.set(await getModels(localStorage.token)); + }} + /> + +
+ {/if}
- + {/each} +
+ + {#if $user?.role === 'admin'} +
+
+ { + console.log(importFiles); + + let reader = new FileReader(); + reader.onload = async (event) => { + let savedModels = JSON.parse(event.target.result); + console.log(savedModels); + + for (const model of savedModels) { + if (model?.info ?? false) { + if ($_models.find((m) => m.id === model.id)) { + await updateModelById(localStorage.token, model.id, model.info).catch( + (error) => { + return null; + } + ); + } else { + await createNewModel(localStorage.token, model.info).catch((error) => { + return null; + }); + } + } + } + + await _models.set(await getModels(localStorage.token)); + models = await getWorkspaceModels(localStorage.token); + }; + + reader.readAsText(importFiles[0]); + }} + /> + + + + +
+
+ {/if} + + {#if $config?.features.enable_community_sharing} + + {/if} +{:else} +
+
{/if} diff --git a/src/lib/components/workspace/Prompts.svelte b/src/lib/components/workspace/Prompts.svelte index f7bc08f76..8996b33ac 100644 --- a/src/lib/components/workspace/Prompts.svelte +++ b/src/lib/components/workspace/Prompts.svelte @@ -20,9 +20,11 @@ import Search from '../icons/Search.svelte'; import Plus from '../icons/Plus.svelte'; import ChevronRight from '../icons/ChevronRight.svelte'; + import Spinner from '../common/Spinner.svelte'; const i18n = getContext('i18n'); let promptsImportInputElement: HTMLInputElement; + let loaded = false; let importFiles = ''; let query = ''; @@ -78,6 +80,7 @@ onMount(async () => { await init(); + loaded = true; }); @@ -87,233 +90,239 @@ - { - deleteHandler(deletePrompt); - }} -> -
- {$i18n.t('This will delete')} {deletePrompt.command}. -
-
- -
-
-
- {$i18n.t('Prompts')} -
- {filteredItems.length} +{#if loaded} + { + deleteHandler(deletePrompt); + }} + > +
+ {$i18n.t('This will delete')} {deletePrompt.command}.
-
+ -
-
-
- -
- -
- -
- - - -
-
-
- -
- {#each filteredItems as prompt} -
- -
- +
+
+ {$i18n.t('Prompts')} +
+ {filteredItems.length} - - - - - - { - shareHandler(prompt); - }} - cloneHandler={() => { - cloneHandler(prompt); - }} - exportHandler={() => { - exportHandler(prompt); - }} - deleteHandler={async () => { - deletePrompt = prompt; - showDeleteConfirm = true; - }} - onClose={() => {}} - > - -
- {/each} -
-{#if $user?.role === 'admin'} -
-
- { - console.log(importFiles); - - const reader = new FileReader(); - reader.onload = async (event) => { - const savedPrompts = JSON.parse(event.target.result); - console.log(savedPrompts); - - for (const prompt of savedPrompts) { - await createNewPrompt( - localStorage.token, - prompt.command.charAt(0) === '/' ? prompt.command.slice(1) : prompt.command, - prompt.title, - prompt.content - ).catch((error) => { - toast.error(error); - return null; - }); - } - - prompts = await getPromptList(localStorage.token); - await _prompts.set(await getPrompts(localStorage.token)); - }; - - reader.readAsText(importFiles[0]); - }} - /> - - - - -
-
-{/if} - -{#if $config?.features.enable_community_sharing} -
-
- {$i18n.t('Made by OpenWebUI Community')} -
- - -
-
{$i18n.t('Discover a prompt')}
-
- {$i18n.t('Discover, download, and explore custom prompts')} + + +
+ {#each filteredItems as prompt} +
+ +
+ + + + + + + { + shareHandler(prompt); + }} + cloneHandler={() => { + cloneHandler(prompt); + }} + exportHandler={() => { + exportHandler(prompt); + }} + deleteHandler={async () => { + deletePrompt = prompt; + showDeleteConfirm = true; + }} + onClose={() => {}} + > + +
- + {/each} +
+ + {#if $user?.role === 'admin'} +
+
+ { + console.log(importFiles); + + const reader = new FileReader(); + reader.onload = async (event) => { + const savedPrompts = JSON.parse(event.target.result); + console.log(savedPrompts); + + for (const prompt of savedPrompts) { + await createNewPrompt( + localStorage.token, + prompt.command.charAt(0) === '/' ? prompt.command.slice(1) : prompt.command, + prompt.title, + prompt.content + ).catch((error) => { + toast.error(error); + return null; + }); + } + + prompts = await getPromptList(localStorage.token); + await _prompts.set(await getPrompts(localStorage.token)); + }; + + reader.readAsText(importFiles[0]); + }} + /> + + + + +
+
+ {/if} + + {#if $config?.features.enable_community_sharing} + + {/if} +{:else} +
+
{/if} diff --git a/src/lib/components/workspace/Tools.svelte b/src/lib/components/workspace/Tools.svelte index 4dbebf421..358b06f67 100644 --- a/src/lib/components/workspace/Tools.svelte +++ b/src/lib/components/workspace/Tools.svelte @@ -29,10 +29,12 @@ import Search from '../icons/Search.svelte'; import Plus from '../icons/Plus.svelte'; import ChevronRight from '../icons/ChevronRight.svelte'; + import Spinner from '../common/Spinner.svelte'; const i18n = getContext('i18n'); let shiftKey = false; + let loaded = false; let toolsImportInputElement: HTMLInputElement; let importFiles; @@ -131,8 +133,10 @@ _tools.set(await getTools(localStorage.token)); }; - onMount(() => { - init(); + onMount(async () => { + await init(); + loaded = true; + const onKeyDown = (event) => { if (event.key === 'Shift') { shiftKey = true; @@ -167,330 +171,336 @@ -
-
-
- {$i18n.t('Tools')} -
- {filteredItems.length} -
-
- -
-
-
- +{#if loaded} +
+
+
+ {$i18n.t('Tools')} +
+ {filteredItems.length}
-
-
- - - +
+
+
+ +
+ +
+ +
+ + + +
-
-
- {#each filteredItems as tool} -
- + {#each filteredItems as tool} +
-
-
-
-
- TOOL -
- - {#if tool?.meta?.manifest?.version} +
+
+
+
- v{tool?.meta?.manifest?.version ?? ''} + TOOL
- {/if} -
- {tool.name} + {#if tool?.meta?.manifest?.version} +
+ v{tool?.meta?.manifest?.version ?? ''} +
+ {/if} + +
+ {tool.name} +
-
-
-
{tool.id}
+
+
{tool.id}
-
- {tool.meta.description} +
+ {tool.meta.description} +
-
-
-
- {#if shiftKey} - - - - {:else} - {#if tool?.meta?.manifest?.funding_url ?? false} - + +
+ {#if shiftKey} + + + + {:else} + {#if tool?.meta?.manifest?.funding_url ?? false} + + + + {/if} + + - {/if} - - - + + + + {/if} +
+
+ {/each} +
- { - goto(`/workspace/tools/edit?id=${encodeURIComponent(tool.id)}`); - }} - shareHandler={() => { - shareHandler(tool); - }} - cloneHandler={() => { - cloneHandler(tool); - }} - exportHandler={() => { - exportHandler(tool); - }} - deleteHandler={async () => { - selectedTool = tool; - showDeleteConfirm = true; - }} - onClose={() => {}} - > - - - {/if} + + +
+ + +
- {/each} -
+ {/if} -{#if $user?.role === 'admin'} -
-
- { - console.log(importFiles); - showConfirm = true; - }} - /> + {#if $config?.features.enable_community_sharing} + + {/if} + + { + deleteHandler(selectedTool); + }} + > +
+ {$i18n.t('This will delete')} {selectedTool.name}. +
+
+ + + + + { + const reader = new FileReader(); + reader.onload = async (event) => { + const _tools = JSON.parse(event.target.result); + console.log(_tools); + + for (const tool of _tools) { + const res = await createNewTool(localStorage.token, tool).catch((error) => { toast.error(error); return null; }); + } - if (_tools) { - let blob = new Blob([JSON.stringify(_tools)], { - type: 'application/json' - }); - saveAs(blob, `tools-export-${Date.now()}.json`); - } - }} - > -
{$i18n.t('Export Tools')}
+ toast.success($i18n.t('Tool imported successfully')); + tools.set(await getTools(localStorage.token)); + }; -
- - - -
- -
-
-{/if} + reader.readAsText(importFiles[0]); + }} + > +
+
+
{$i18n.t('Please carefully review the following warnings:')}
-{#if $config?.features.enable_community_sharing} -
-
- {$i18n.t('Made by OpenWebUI Community')} -
- - -
-
{$i18n.t('Discover a tool')}
-
- {$i18n.t('Discover, download, and explore custom tools')} -
+
    +
  • + {$i18n.t('Tools have a function calling system that allows arbitrary code execution')}. +
  • +
  • {$i18n.t('Do not install tools from sources you do not fully trust.')}
  • +
-
+ +{:else} +
+
{/if} - - { - deleteHandler(selectedTool); - }} -> -
- {$i18n.t('This will delete')} {selectedTool.name}. -
-
- - - - - { - const reader = new FileReader(); - reader.onload = async (event) => { - const _tools = JSON.parse(event.target.result); - console.log(_tools); - - for (const tool of _tools) { - const res = await createNewTool(localStorage.token, tool).catch((error) => { - toast.error(error); - return null; - }); - } - - toast.success($i18n.t('Tool imported successfully')); - tools.set(await getTools(localStorage.token)); - }; - - reader.readAsText(importFiles[0]); - }} -> -
-
-
{$i18n.t('Please carefully review the following warnings:')}
- -
    -
  • - {$i18n.t('Tools have a function calling system that allows arbitrary code execution')}. -
  • -
  • {$i18n.t('Do not install tools from sources you do not fully trust.')}
  • -
-
- -
- {$i18n.t( - 'I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.' - )} -
-
-