enh: workspace loading indicator

This commit is contained in:
Timothy Jaeryang Baek
2024-11-16 18:35:14 -08:00
parent b562067e91
commit b41e456c4f
4 changed files with 914 additions and 873 deletions
+18 -8
View File
@@ -23,6 +23,9 @@
import Badge from '../common/Badge.svelte'; import Badge from '../common/Badge.svelte';
import Search from '../icons/Search.svelte'; import Search from '../icons/Search.svelte';
import Plus from '../icons/Plus.svelte'; import Plus from '../icons/Plus.svelte';
import Spinner from '../common/Spinner.svelte';
let loaded = false;
let query = ''; let query = '';
let selectedItem = null; let selectedItem = null;
@@ -61,6 +64,7 @@
onMount(async () => { onMount(async () => {
knowledgeBases = await getKnowledgeBaseList(localStorage.token); knowledgeBases = await getKnowledgeBaseList(localStorage.token);
loaded = true;
}); });
</script> </script>
@@ -70,14 +74,15 @@
</title> </title>
</svelte:head> </svelte:head>
<DeleteConfirmDialog {#if loaded}
<DeleteConfirmDialog
bind:show={showDeleteConfirm} bind:show={showDeleteConfirm}
on:confirm={() => { on:confirm={() => {
deleteHandler(selectedItem); deleteHandler(selectedItem);
}} }}
/> />
<div class="flex flex-col gap-1 mt-1.5 mb-2"> <div class="flex flex-col gap-1 mt-1.5 mb-2">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex md:self-center text-xl font-medium px-0.5 items-center"> <div class="flex md:self-center text-xl font-medium px-0.5 items-center">
{$i18n.t('Knowledge')} {$i18n.t('Knowledge')}
@@ -112,9 +117,9 @@
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="my-3 mb-5 grid lg:grid-cols-2 xl:grid-cols-3 gap-2"> <div class="my-3 mb-5 grid lg:grid-cols-2 xl:grid-cols-3 gap-2">
{#each filteredItems as item} {#each filteredItems as item}
<button <button
class=" flex space-x-4 cursor-pointer text-left w-full px-4 py-3 border border-gray-50 dark:border-gray-850 dark:hover:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-xl" class=" flex space-x-4 cursor-pointer text-left w-full px-4 py-3 border border-gray-50 dark:border-gray-850 dark:hover:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-xl"
@@ -166,8 +171,13 @@
</div> </div>
</button> </button>
{/each} {/each}
</div> </div>
<div class=" text-gray-500 text-xs mt-1 mb-2"> <div class=" text-gray-500 text-xs mt-1 mb-2">
ⓘ {$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.")}
</div> </div>
{:else}
<div class="w-full h-full flex justify-center items-center">
<Spinner />
</div>
{/if}
+25 -13
View File
@@ -31,11 +31,13 @@
import Plus from '../icons/Plus.svelte'; import Plus from '../icons/Plus.svelte';
import ChevronRight from '../icons/ChevronRight.svelte'; import ChevronRight from '../icons/ChevronRight.svelte';
import Switch from '../common/Switch.svelte'; import Switch from '../common/Switch.svelte';
import Spinner from '../common/Spinner.svelte';
let shiftKey = false; let shiftKey = false;
let importFiles; let importFiles;
let modelsImportInputElement: HTMLInputElement; let modelsImportInputElement: HTMLInputElement;
let loaded = false;
let models = []; let models = [];
@@ -149,6 +151,8 @@
onMount(async () => { onMount(async () => {
models = await getWorkspaceModels(localStorage.token); models = await getWorkspaceModels(localStorage.token);
loaded = true;
const onKeyDown = (event) => { const onKeyDown = (event) => {
if (event.key === 'Shift') { if (event.key === 'Shift') {
shiftKey = true; shiftKey = true;
@@ -183,14 +187,15 @@
</title> </title>
</svelte:head> </svelte:head>
<ModelDeleteConfirmDialog {#if loaded}
<ModelDeleteConfirmDialog
bind:show={showModelDeleteConfirm} bind:show={showModelDeleteConfirm}
on:confirm={() => { on:confirm={() => {
deleteModelHandler(selectedModel); deleteModelHandler(selectedModel);
}} }}
/> />
<div class="flex flex-col gap-1 mt-1.5 mb-2"> <div class="flex flex-col gap-1 mt-1.5 mb-2">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex items-center md:self-center text-xl font-medium px-0.5"> <div class="flex items-center md:self-center text-xl font-medium px-0.5">
{$i18n.t('Models')} {$i18n.t('Models')}
@@ -222,9 +227,9 @@
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<a class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-1" href="/workspace/models/create"> <a class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-1" href="/workspace/models/create">
<div class=" self-center w-8 flex-shrink-0"> <div class=" self-center w-8 flex-shrink-0">
<div <div
class="w-full h-8 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200" class="w-full h-8 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
@@ -245,9 +250,9 @@
{$i18n.t('Customize models for a specific purpose')} {$i18n.t('Customize models for a specific purpose')}
</div> </div>
</div> </div>
</a> </a>
<div class=" my-2 mb-5" id="model-list"> <div class=" my-2 mb-5" id="model-list">
{#each filteredModels as model} {#each filteredModels as model}
<div <div
class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-lg transition" class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-lg transition"
@@ -365,9 +370,9 @@
</div> </div>
</div> </div>
{/each} {/each}
</div> </div>
{#if $user?.role === 'admin'} {#if $user?.role === 'admin'}
<div class=" flex justify-end w-full mb-3"> <div class=" flex justify-end w-full mb-3">
<div class="flex space-x-1"> <div class="flex space-x-1">
<input <input
@@ -388,9 +393,11 @@
for (const model of savedModels) { for (const model of savedModels) {
if (model?.info ?? false) { if (model?.info ?? false) {
if ($_models.find((m) => m.id === model.id)) { if ($_models.find((m) => m.id === model.id)) {
await updateModelById(localStorage.token, model.id, model.info).catch((error) => { await updateModelById(localStorage.token, model.id, model.info).catch(
(error) => {
return null; return null;
}); }
);
} else { } else {
await createNewModel(localStorage.token, model.info).catch((error) => { await createNewModel(localStorage.token, model.info).catch((error) => {
return null; return null;
@@ -456,9 +463,9 @@
</button> </button>
</div> </div>
</div> </div>
{/if} {/if}
{#if $config?.features.enable_community_sharing} {#if $config?.features.enable_community_sharing}
<div class=" my-16"> <div class=" my-16">
<div class=" text-lg font-semibold mb-0.5 line-clamp-1"> <div class=" text-lg font-semibold mb-0.5 line-clamp-1">
{$i18n.t('Made by OpenWebUI Community')} {$i18n.t('Made by OpenWebUI Community')}
@@ -483,4 +490,9 @@
</div> </div>
</a> </a>
</div> </div>
{/if}
{:else}
<div class="w-full h-full flex justify-center items-center">
<Spinner />
</div>
{/if} {/if}
+19 -10
View File
@@ -20,9 +20,11 @@
import Search from '../icons/Search.svelte'; import Search from '../icons/Search.svelte';
import Plus from '../icons/Plus.svelte'; import Plus from '../icons/Plus.svelte';
import ChevronRight from '../icons/ChevronRight.svelte'; import ChevronRight from '../icons/ChevronRight.svelte';
import Spinner from '../common/Spinner.svelte';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
let promptsImportInputElement: HTMLInputElement; let promptsImportInputElement: HTMLInputElement;
let loaded = false;
let importFiles = ''; let importFiles = '';
let query = ''; let query = '';
@@ -78,6 +80,7 @@
onMount(async () => { onMount(async () => {
await init(); await init();
loaded = true;
}); });
</script> </script>
@@ -87,19 +90,20 @@
</title> </title>
</svelte:head> </svelte:head>
<DeleteConfirmDialog {#if loaded}
<DeleteConfirmDialog
bind:show={showDeleteConfirm} bind:show={showDeleteConfirm}
title={$i18n.t('Delete prompt?')} title={$i18n.t('Delete prompt?')}
on:confirm={() => { on:confirm={() => {
deleteHandler(deletePrompt); deleteHandler(deletePrompt);
}} }}
> >
<div class=" text-sm text-gray-500"> <div class=" text-sm text-gray-500">
{$i18n.t('This will delete')} <span class=" font-semibold">{deletePrompt.command}</span>. {$i18n.t('This will delete')} <span class=" font-semibold">{deletePrompt.command}</span>.
</div> </div>
</DeleteConfirmDialog> </DeleteConfirmDialog>
<div class="flex flex-col gap-1 mt-1.5 mb-2"> <div class="flex flex-col gap-1 mt-1.5 mb-2">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex md:self-center text-xl font-medium px-0.5 items-center"> <div class="flex md:self-center text-xl font-medium px-0.5 items-center">
{$i18n.t('Prompts')} {$i18n.t('Prompts')}
@@ -131,9 +135,9 @@
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<div class="mb-5"> <div class="mb-5">
{#each filteredItems as prompt} {#each filteredItems as prompt}
<div <div
class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl" class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
@@ -196,9 +200,9 @@
</div> </div>
</div> </div>
{/each} {/each}
</div> </div>
{#if $user?.role === 'admin'} {#if $user?.role === 'admin'}
<div class=" flex justify-end w-full mb-3"> <div class=" flex justify-end w-full mb-3">
<div class="flex space-x-2"> <div class="flex space-x-2">
<input <input
@@ -289,9 +293,9 @@
</button> </button>
</div> </div>
</div> </div>
{/if} {/if}
{#if $config?.features.enable_community_sharing} {#if $config?.features.enable_community_sharing}
<div class=" my-16"> <div class=" my-16">
<div class=" text-lg font-semibold mb-0.5 line-clamp-1"> <div class=" text-lg font-semibold mb-0.5 line-clamp-1">
{$i18n.t('Made by OpenWebUI Community')} {$i18n.t('Made by OpenWebUI Community')}
@@ -316,4 +320,9 @@
</div> </div>
</a> </a>
</div> </div>
{/if}
{:else}
<div class="w-full h-full flex justify-center items-center">
<Spinner />
</div>
{/if} {/if}
+28 -18
View File
@@ -29,10 +29,12 @@
import Search from '../icons/Search.svelte'; import Search from '../icons/Search.svelte';
import Plus from '../icons/Plus.svelte'; import Plus from '../icons/Plus.svelte';
import ChevronRight from '../icons/ChevronRight.svelte'; import ChevronRight from '../icons/ChevronRight.svelte';
import Spinner from '../common/Spinner.svelte';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
let shiftKey = false; let shiftKey = false;
let loaded = false;
let toolsImportInputElement: HTMLInputElement; let toolsImportInputElement: HTMLInputElement;
let importFiles; let importFiles;
@@ -131,8 +133,10 @@
_tools.set(await getTools(localStorage.token)); _tools.set(await getTools(localStorage.token));
}; };
onMount(() => { onMount(async () => {
init(); await init();
loaded = true;
const onKeyDown = (event) => { const onKeyDown = (event) => {
if (event.key === 'Shift') { if (event.key === 'Shift') {
shiftKey = true; shiftKey = true;
@@ -167,7 +171,8 @@
</title> </title>
</svelte:head> </svelte:head>
<div class="flex flex-col gap-1 mt-1.5 mb-2"> {#if loaded}
<div class="flex flex-col gap-1 mt-1.5 mb-2">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex md:self-center text-xl font-medium px-0.5 items-center"> <div class="flex md:self-center text-xl font-medium px-0.5 items-center">
{$i18n.t('Tools')} {$i18n.t('Tools')}
@@ -199,9 +204,9 @@
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<div class="mb-5"> <div class="mb-5">
{#each filteredItems as tool} {#each filteredItems as tool}
<div <div
class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl" class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
@@ -332,9 +337,9 @@
</div> </div>
</div> </div>
{/each} {/each}
</div> </div>
{#if $user?.role === 'admin'} {#if $user?.role === 'admin'}
<div class=" flex justify-end w-full mb-2"> <div class=" flex justify-end w-full mb-2">
<div class="flex space-x-2"> <div class="flex space-x-2">
<input <input
@@ -409,9 +414,9 @@
</button> </button>
</div> </div>
</div> </div>
{/if} {/if}
{#if $config?.features.enable_community_sharing} {#if $config?.features.enable_community_sharing}
<div class=" my-16"> <div class=" my-16">
<div class=" text-lg font-semibold mb-0.5 line-clamp-1"> <div class=" text-lg font-semibold mb-0.5 line-clamp-1">
{$i18n.t('Made by OpenWebUI Community')} {$i18n.t('Made by OpenWebUI Community')}
@@ -436,24 +441,24 @@
</div> </div>
</a> </a>
</div> </div>
{/if} {/if}
<DeleteConfirmDialog <DeleteConfirmDialog
bind:show={showDeleteConfirm} bind:show={showDeleteConfirm}
title={$i18n.t('Delete tool?')} title={$i18n.t('Delete tool?')}
on:confirm={() => { on:confirm={() => {
deleteHandler(selectedTool); deleteHandler(selectedTool);
}} }}
> >
<div class=" text-sm text-gray-500"> <div class=" text-sm text-gray-500">
{$i18n.t('This will delete')} <span class=" font-semibold">{selectedTool.name}</span>. {$i18n.t('This will delete')} <span class=" font-semibold">{selectedTool.name}</span>.
</div> </div>
</DeleteConfirmDialog> </DeleteConfirmDialog>
<ValvesModal bind:show={showValvesModal} type="tool" id={selectedTool?.id ?? null} /> <ValvesModal bind:show={showValvesModal} type="tool" id={selectedTool?.id ?? null} />
<ManifestModal bind:show={showManifestModal} manifest={selectedTool?.meta?.manifest ?? {}} /> <ManifestModal bind:show={showManifestModal} manifest={selectedTool?.meta?.manifest ?? {}} />
<ConfirmDialog <ConfirmDialog
bind:show={showConfirm} bind:show={showConfirm}
on:confirm={() => { on:confirm={() => {
const reader = new FileReader(); const reader = new FileReader();
@@ -474,7 +479,7 @@
reader.readAsText(importFiles[0]); reader.readAsText(importFiles[0]);
}} }}
> >
<div class="text-sm text-gray-500"> <div class="text-sm text-gray-500">
<div class=" bg-yellow-500/20 text-yellow-700 dark:text-yellow-200 rounded-lg px-4 py-3"> <div class=" bg-yellow-500/20 text-yellow-700 dark:text-yellow-200 rounded-lg px-4 py-3">
<div>{$i18n.t('Please carefully review the following warnings:')}</div> <div>{$i18n.t('Please carefully review the following warnings:')}</div>
@@ -493,4 +498,9 @@
)} )}
</div> </div>
</div> </div>
</ConfirmDialog> </ConfirmDialog>
{:else}
<div class="w-full h-full flex justify-center items-center">
<Spinner />
</div>
{/if}