From d0bea60581eaa07d41f92ad8f86007e83247e061 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 17 Jul 2026 05:31:32 -0400 Subject: [PATCH] refac --- .../admin/Functions/FunctionEditor.svelte | 272 +++++++++--------- .../components/admin/Settings/Models.svelte | 188 +++++++++++- .../admin/Settings/Models/ModelList.svelte | 92 ------ .../Settings/Models/ModelSettingsModal.svelte | 140 +-------- src/lib/components/common/CodeEditor.svelte | 3 +- .../workspace/Skills/SkillEditor.svelte | 217 +++++++------- .../workspace/Tools/ToolkitEditor.svelte | 264 ++++++++--------- 7 files changed, 558 insertions(+), 618 deletions(-) delete mode 100644 src/lib/components/admin/Settings/Models/ModelList.svelte diff --git a/src/lib/components/admin/Functions/FunctionEditor.svelte b/src/lib/components/admin/Functions/FunctionEditor.svelte index 6a3bc11917..316b86dd83 100644 --- a/src/lib/components/admin/Functions/FunctionEditor.svelte +++ b/src/lib/components/admin/Functions/FunctionEditor.svelte @@ -7,7 +7,7 @@ import { extractFrontmatter, formatSkillName, nameToId } from '$lib/utils'; import CodeEditor from '$lib/components/common/CodeEditor.svelte'; import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; - import Badge from '$lib/components/common/Badge.svelte'; + import Spinner from '$lib/components/common/Spinner.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte'; import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte'; @@ -15,7 +15,7 @@ let loading = false; let showConfirm = false; - export let onSave = () => {}; + export let onSave = /** @param {any} _value */ async (_value) => {}; export let edit = false; export let clone = false; @@ -305,12 +305,16 @@ class Pipe: const saveHandler = async () => { loading = true; - onSave({ - id, - name, - meta, - content - }); + try { + await onSave({ + id, + name, + meta, + content + }); + } finally { + loading = false; + } }; const submitHandler = async () => { @@ -333,146 +337,148 @@ class Pipe: }; -
-
-
{ - if (edit) { - submitHandler(); - } else { - showConfirm = true; - } +
+ { + if (edit) { + submitHandler(); + } else { + showConfirm = true; + } + }} + > + - + + {$i18n.t('Back')} + + +
+
+ + + + +
+ {#if edit} +
+ {id}
- -
- - - -
- -
- {#if !edit} - - {/if} - -
-
- -
- {#if edit} -
- {id} -
- {:else} - - - - {/if} - + {:else} -
-
+ {/if} - - -
-
-
- {$i18n.t('Warning:')} - {$i18n.t('Functions allow arbitrary code execution.')}
— - {$i18n.t(`don't install random functions from sources you don't trust.`)} -
-
- - + +
- -
+ +
+ {#if !edit} + + {/if} +
+
+ +
+ { + _content = e; + if (!edit) { + const fm = extractFrontmatter(e); + if (fm.title && !name) { + name = formatSkillName(fm.title); + id = nameToId(fm.title); + } + if (fm.description && !meta.description) { + meta = { ...meta, description: fm.description }; + } + } + }} + onSave={async () => { + if (formElement) { + formElement.requestSubmit(); + } + }} + /> +
+ +
+
+
+ {$i18n.t('Warning:')} + {$i18n.t('Functions can execute arbitrary code.')} + + {$i18n.t('Only install functions from sources you trust.')} + +
+ + +
+
+
import { marked } from 'marked'; + import Sortable from 'sortablejs'; import fileSaver from 'file-saver'; const { saveAs } = fileSaver; - import { onMount, getContext, tick } from 'svelte'; + import { onMount, onDestroy, getContext, tick } from 'svelte'; const i18n = getContext('i18n'); import { config, models as _models, settings, user } from '$lib/stores'; @@ -21,6 +22,7 @@ import { updateUserSettings } from '$lib/apis/users'; import { getModels } from '$lib/apis'; + import { getModelsConfig, setModelsConfig } from '$lib/apis/configs'; import Search from '$lib/components/icons/Search.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte'; import Switch from '$lib/components/common/Switch.svelte'; @@ -38,19 +40,20 @@ import Eye from '$lib/components/icons/Eye.svelte'; import ChevronDown from '$lib/components/icons/ChevronDown.svelte'; import CheckCircle from '$lib/components/icons/CheckCircle.svelte'; + import GarbageBin from '$lib/components/icons/GarbageBin.svelte'; import Minus from '$lib/components/icons/Minus.svelte'; import DocumentArrowUp from '$lib/components/icons/DocumentArrowUp.svelte'; import Download from '$lib/components/icons/Download.svelte'; + import EllipsisVertical from '$lib/components/icons/EllipsisVertical.svelte'; import Wrench from '$lib/components/icons/Wrench.svelte'; import SettingsIcon from '$lib/components/icons/Settings.svelte'; - import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; + import { WEBUI_API_BASE_URL } from '$lib/constants'; import { goto } from '$app/navigation'; import Dropdown from '$lib/components/common/Dropdown.svelte'; import DropdownMenu from '$lib/components/common/DropdownMenu.svelte'; import AdminViewSelector from './Models/AdminViewSelector.svelte'; import TagSelector from '$lib/components/workspace/common/TagSelector.svelte'; - import Pagination from '$lib/components/common/Pagination.svelte'; type ModelListItem = { id: string; name?: string }; @@ -62,8 +65,12 @@ let importFiles; let modelsImportInputElement: HTMLInputElement; let tagsContainerElement: HTMLDivElement; + let modelListElement: HTMLDivElement; + let sortable = null; let models = null; + let modelsConfig = null; + let modelOrderList: string[] = []; let workspaceModels: ModelListItem[] = []; let baseModels: ModelListItem[] = []; @@ -73,6 +80,9 @@ let showConfigModal = false; let showManageModal = false; + let showResetModal = false; + let savingModelOrder = false; + let modelOrderDirty = false; let viewOption = ''; // '' = All, 'enabled', 'disabled', 'visible', 'hidden' let tags: string[] = []; @@ -83,9 +93,6 @@ tabState = null; } - const perPage = 30; - let currentPage = 1; - const isPublicModel = (model) => { return (model?.access_grants ?? []).some( (g) => g.principal_type === 'user' && g.principal_id === '*' && g.permission === 'read' @@ -115,6 +122,8 @@ }; $: if (models) { + const modelOrder = new Map(modelOrderList.map((id, idx) => [id, idx])); + filteredModels = models .filter((m) => searchValue === '' || m.name.toLowerCase().includes(searchValue.toLowerCase())) .filter((m) => { @@ -127,15 +136,21 @@ return true; // All }) .sort((a, b) => { + const orderA = modelOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER; + const orderB = modelOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER; + + if (orderA !== orderB) { + return orderA - orderB; + } + return (a?.name ?? a?.id ?? '').localeCompare(b?.name ?? b?.id ?? ''); }); } let searchValue = ''; + let canReorderModels = false; - $: if (searchValue || viewOption !== undefined) { - currentPage = 1; - } + $: canReorderModels = searchValue === '' && viewOption === '' && selectedTag === ''; const enableAllHandler = async () => { const modelsToEnable = filteredModels.filter((m) => !(m.is_active ?? true)); @@ -213,6 +228,9 @@ const init = async () => { models = null; + modelsConfig = await getModelsConfig(localStorage.token); + modelOrderList = modelsConfig?.MODEL_ORDER_LIST ?? []; + tags = await getBaseModelTags(localStorage.token); if (selectedTag && !tags.includes(selectedTag)) { selectedTag = ''; @@ -243,6 +261,15 @@ } }); + modelOrderList = [ + ...modelOrderList.filter((id) => models.some((model) => model.id === id)), + ...models + .map((model) => model.id) + .filter((id) => !modelOrderList.includes(id)) + .sort((a, b) => a.localeCompare(b)) + ]; + modelOrderDirty = false; + _models.set( await getModels( localStorage.token, @@ -251,6 +278,79 @@ ); }; + const saveModelOrder = async (orderedModelIds: string[]) => { + savingModelOrder = true; + + const res = await setModelsConfig(localStorage.token, { + DEFAULT_MODELS: modelsConfig?.DEFAULT_MODELS ?? null, + DEFAULT_PINNED_MODELS: modelsConfig?.DEFAULT_PINNED_MODELS ?? null, + MODEL_ORDER_LIST: orderedModelIds, + DEFAULT_MODEL_METADATA: modelsConfig?.DEFAULT_MODEL_METADATA ?? null, + DEFAULT_MODEL_PARAMS: modelsConfig?.DEFAULT_MODEL_PARAMS ?? null + }).catch((error) => { + toast.error(`${error}`); + return null; + }); + + if (res) { + modelsConfig = res; + modelOrderDirty = false; + toast.success($i18n.t('Model order saved successfully')); + _models.set( + await getModels( + localStorage.token, + $config?.features?.enable_direct_connections && ($settings?.directConnections ?? null) + ) + ); + } + + savingModelOrder = false; + }; + + const positionChangeHandler = async (event) => { + const { oldIndex, newIndex, item } = event; + + if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) { + return; + } + + const parent = item.parentNode; + const target = parent.children[oldIndex < newIndex ? oldIndex : oldIndex + 1]; + parent.insertBefore(item, target); + + const updatedModels = [...filteredModels]; + const [movedModel] = updatedModels.splice(oldIndex, 1); + updatedModels.splice(newIndex, 0, movedModel); + + const orderedIds = updatedModels.map((model) => model.id); + const orderedSet = new Set(orderedIds); + + models = [...updatedModels, ...models.filter((model) => !orderedSet.has(model.id))]; + modelOrderList = models.map((model) => model.id); + modelOrderDirty = true; + }; + + const initSortable = () => { + if (sortable) { + sortable.destroy(); + sortable = null; + } + + if (modelListElement && filteredModels.length > 0 && canReorderModels) { + sortable = new Sortable(modelListElement, { + animation: 150, + handle: '.model-item-handle', + onUpdate: positionChangeHandler + }); + } + }; + + $: if (modelListElement && filteredModels) { + tick().then(() => { + initSortable(); + }); + } + const upsertModelHandler = async (model, overrides = {}, showToast = true) => { model = { ...model, base_model_id: null, ...overrides }; @@ -436,8 +536,27 @@ window.removeEventListener('blur', onBlur); }; }); + + onDestroy(() => { + if (sortable) { + sortable.destroy(); + } + }); + { + const res = await deleteAllModels(localStorage.token); + if (res) { + toast.success($i18n.t('All models deleted successfully')); + await init(); + } + }} +/> + @@ -552,7 +671,6 @@ return { value: tag, label: tag }; })} onChange={async () => { - currentPage = 1; await init(); }} /> @@ -608,6 +726,17 @@
{$i18n.t('Manage')}
+ +
+ {:else} diff --git a/src/lib/components/admin/Settings/Models/ModelList.svelte b/src/lib/components/admin/Settings/Models/ModelList.svelte deleted file mode 100644 index f91bd2ddc4..0000000000 --- a/src/lib/components/admin/Settings/Models/ModelList.svelte +++ /dev/null @@ -1,92 +0,0 @@ - - -{#if modelIds.length > 0} -
- {#each modelIds as modelId (modelId)} -
- -
- - -
- {#if $models.find((model) => model.id === modelId)} - {$models.find((model) => model.id === modelId).name} - {:else} - {modelId} - {/if} -
-
-
-
- {/each} -
-{:else} -
- {$i18n.t('No models found')} -
-{/if} diff --git a/src/lib/components/admin/Settings/Models/ModelSettingsModal.svelte b/src/lib/components/admin/Settings/Models/ModelSettingsModal.svelte index bdb5d38445..46d0b7757b 100644 --- a/src/lib/components/admin/Settings/Models/ModelSettingsModal.svelte +++ b/src/lib/components/admin/Settings/Models/ModelSettingsModal.svelte @@ -1,28 +1,20 @@ - { - const res = deleteAllModels(localStorage.token); - if (res) { - toast.success($i18n.t('All models deleted successfully')); - initHandler(); - } - }} -/> -
@@ -194,13 +161,7 @@ class="tabs flex flex-row overflow-x-auto gap-2.5 max-w-full lg:gap-1 lg:flex-col lg:flex-none lg:w-40 dark:text-gray-200 text-sm font-normal text-left scrollbar-none" > - -
- {#if selectedTab === 'defaults'} - !(model?.info?.meta?.hidden ?? false))} - bind:modelIds={defaultModelIds} - /> + !(model?.info?.meta?.hidden ?? false))} + bind:modelIds={defaultModelIds} + />
@@ -370,71 +314,9 @@
{/if}
- {:else if selectedTab === 'display'} -
-
- - - -
-
- {/if}
-
-
- - - -
+
- +
+ + + +
+
+ + + + +
+ {#if edit} +
+ {id}
- -
- - - -
- -
- {#if !disabled} - (showAccessControlModal = true)} /> - {:else} - {$i18n.t('Read Only')} - {/if} -
-
- -
- {#if edit} -
- {id} -
- {:else} - - - - {/if} - + {:else} -
-
- -