enh: workspace loading indicator
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user