From 9dd4acb71d5875164892162a8411c788388ae9b7 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Tue, 15 Sep 2026 01:10:59 +0200 Subject: [PATCH] ui : switch backends from the models selector Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash --- .../app/backends/DialogBackendForm.svelte | 4 +- .../ModelsSelectorBackendSwitcher.svelte | 43 ++++++++++++++++++ .../ModelsSelectorDropdown.svelte | 28 ++++++++++-- .../ModelsSelector/ModelsSelectorSheet.svelte | 28 ++++++++++-- .../app/models/ModelsSelector/index.ts | 2 + .../lib/hooks/use-models-selector.svelte.ts | 45 ++++++++++++++++++- .../ui/src/lib/stores/models/index.svelte.ts | 16 +++++++ 7 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorBackendSwitcher.svelte diff --git a/tools/ui/src/lib/components/app/backends/DialogBackendForm.svelte b/tools/ui/src/lib/components/app/backends/DialogBackendForm.svelte index a7e437b5a2..fd464c6ff0 100644 --- a/tools/ui/src/lib/components/app/backends/DialogBackendForm.svelte +++ b/tools/ui/src/lib/components/app/backends/DialogBackendForm.svelte @@ -14,9 +14,10 @@ backend?: Backend | null; open?: boolean; onOpenChange?: (open: boolean) => void; + onSaved?: (backend: Backend) => void; } - let { backend = null, onOpenChange, open = $bindable(false) }: Props = $props(); + let { backend = null, onOpenChange, onSaved, open = $bindable(false) }: Props = $props(); let draft = $state(createBackend()); let selectedPresetId = $state(null); @@ -106,6 +107,7 @@ backendsStore.addBackend(next); } + onSaved?.(next); handleOpenChange(false); } diff --git a/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorBackendSwitcher.svelte b/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorBackendSwitcher.svelte new file mode 100644 index 0000000000..8200072eb1 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorBackendSwitcher.svelte @@ -0,0 +1,43 @@ + + +
+ {#each backends as backend (backend.id)} + + {/each} + + {#if backends.length === 0} + No backends configured + {/if} + + +
diff --git a/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorDropdown.svelte b/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorDropdown.svelte index 67ce031dee..879f8d9d03 100644 --- a/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorDropdown.svelte +++ b/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorDropdown.svelte @@ -5,10 +5,12 @@ DialogModelInformation, DropdownMenuSearchable, ModelId, + ModelsSelectorBackendSwitcher, ModelsSelectorList, ModelsSelectorOption, ModelsSelectorReasoningPanel } from '$lib/components/app'; + import { DialogBackendForm } from '$lib/components/app/backends'; import type { ModelItem } from '$lib/components/app/navigation/utils'; import * as DropdownMenu from '$lib/components/ui/dropdown-menu'; import * as Tooltip from '$lib/components/ui/tooltip'; @@ -43,6 +45,7 @@ let isOpen = $state(false); let highlightedId = $state(null); + let showAddBackend = $state(false); const ms = useModelsSelector({ currentModel: () => currentModel, @@ -123,6 +126,13 @@ highlightedId = null; } + function handleAddBackend() { + isOpen = false; + + // let the menu finish closing before the dialog takes focus + setTimeout(() => (showAddBackend = true), 0); + } + // Alt+Enter only unloads and keeps the dropdown open. async function handleModelKeyAction(modelId: string, unload: boolean) { if (!unload) { @@ -165,13 +175,13 @@
- {#if ms.loading && ms.options.length === 0 && ms.isRouter} + {#if ms.loading && ms.options.length === 0 && ms.isMultiModel}
Loading models…
- {:else if ms.options.length === 0 && ms.isRouter} + {:else if ms.options.length === 0 && ms.isMultiModel} {#if currentModel} @@ -263,6 +273,13 @@ class="w-full md:min-w-80 md:max-w-[26rem] max-w-[calc(100vw-2rem)] p-0!" onOpenAutoFocus={(event) => event.preventDefault()} > + void ms.handleBackendChange(backendId)} + /> + {/if} + + void ms.handleBackendChange(backend.id)} +/> diff --git a/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorSheet.svelte b/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorSheet.svelte index e861b330a5..36412d98c2 100644 --- a/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorSheet.svelte +++ b/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorSheet.svelte @@ -4,10 +4,12 @@ import { DialogModelInformation, ModelId, + ModelsSelectorBackendSwitcher, ModelsSelectorList, ModelsSelectorReasoningPanel, SearchInput } from '$lib/components/app'; + import { DialogBackendForm } from '$lib/components/app/backends'; import * as Sheet from '$lib/components/ui/sheet'; import { ServerModelStatus } from '$lib/enums'; import { useModelsSelector } from '$lib/hooks/use-models-selector.svelte'; @@ -36,6 +38,7 @@ }: Props = $props(); let sheetOpen = $state(false); + let showAddBackend = $state(false); const ms = useModelsSelector({ currentModel: () => currentModel, @@ -57,15 +60,22 @@ ms.handleOpenChange(false); } } + + function handleAddBackend() { + sheetOpen = false; + + // let the sheet finish closing before the dialog takes focus + setTimeout(() => (showAddBackend = true), 0); + }
- {#if ms.loading && ms.options.length === 0 && ms.isRouter} + {#if ms.loading && ms.options.length === 0 && ms.isMultiModel}
Loading models…
- {:else if ms.options.length === 0 && ms.isRouter} + {:else if ms.options.length === 0 && ms.isMultiModel}

No models available.

{:else} {@const selectedOption = ms.getDisplayOption()} @@ -81,7 +91,7 @@ ? Math.round(modelLoadFraction(modelsStore.status.getLoadProgress(triggerModel)) * 100) : 0} - {#if ms.isRouter} + {#if ms.isMultiModel}