ui : keep the model selector mounted across backend switches

Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
Aleksander Grygier
2026-09-16 19:33:34 +02:00
parent ce00f0bcd1
commit 5c9cd156d5
3 changed files with 31 additions and 16 deletions
@@ -276,13 +276,6 @@
class="w-full md:min-w-80 md:max-w-[26rem] max-w-[calc(100vw-2rem)] p-0!"
onOpenAutoFocus={(event) => event.preventDefault()}
>
<ModelsSelectorBackendSwitcher
activeId={ms.activeBackendId}
backends={ms.backends}
onAdd={handleAddBackend}
onSelect={(backendId) => void ms.handleBackendChange(backendId)}
/>
<DropdownMenuSearchable
emptyMessage="No models found."
isEmpty={ms.filteredOptions.length === 0 && ms.isCurrentModelInCache}
@@ -292,6 +285,14 @@
searchClass="bg-transparent"
searchValue={ms.searchTerm}
>
<!-- Provider tabs sit under the search input, above the option list. -->
<ModelsSelectorBackendSwitcher
activeId={ms.activeBackendId}
backends={ms.backends}
onAdd={handleAddBackend}
onSelect={(backendId) => void ms.handleBackendChange(backendId)}
/>
<!-- Option list; the search header sticks to the top and the actions
footer to the bottom of the content scrollport. -->
<div class="models-list px-1.5">
@@ -91,7 +91,7 @@
? Math.round(modelLoadFraction(modelsStore.status.getLoadProgress(triggerModel)) * 100)
: 0}
{#if ms.isMultiModel}
{#if ms.isMultiModel || ms.switchingBackends}
<button
class={[
`relative inline-flex cursor-pointer items-center gap-1.5 rounded-sm bg-background px-1.5 py-1 text-xs shadow-sm transition hover:bg-muted-foreground/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 max-sm:px-3 max-sm:py-2 max-sm:text-sm dark:bg-muted-foreground/15 dark:text-secondary-foreground`,
@@ -127,7 +127,7 @@
<Lightbulb class="h-3.5 w-3.5 shrink-0 text-amber-400" />
{/if}
{#if ms.updating || ms.isLoadingModel}
{#if ms.updating || ms.isLoadingModel || ms.switchingBackends}
<Loader2 class="h-3 w-3.5 shrink-0 animate-spin" />
{:else}
<ChevronDown class="h-3 w-3.5 shrink-0" />
@@ -149,13 +149,6 @@
</Sheet.Header>
<div class="flex flex-col gap-1 pb-4">
<ModelsSelectorBackendSwitcher
activeId={ms.activeBackendId}
backends={ms.backends}
onAdd={handleAddBackend}
onSelect={(backendId) => void ms.handleBackendChange(backendId)}
/>
<div class="mb-3 px-4">
<SearchInput
onInput={(v) => ms.setSearchTerm(v)}
@@ -164,6 +157,14 @@
/>
</div>
<!-- Provider tabs sit under the search input, above the option list. -->
<ModelsSelectorBackendSwitcher
activeId={ms.activeBackendId}
backends={ms.backends}
onAdd={handleAddBackend}
onSelect={(backendId) => void ms.handleBackendChange(backendId)}
/>
<div class="max-h-[60vh] overflow-y-auto px-2">
{#if !ms.isCurrentModelInCache && currentModel}
<button
@@ -30,6 +30,7 @@ export interface UseModelsSelectorReturn {
readonly filteredOptions: ModelOption[];
readonly groupedFilteredOptions: ReturnType<typeof groupModelOptions>;
readonly isLoadingModel: boolean;
readonly switchingBackends: boolean;
readonly searchTerm: string;
readonly showModelDialog: boolean;
readonly infoModelId: string | null;
@@ -88,6 +89,7 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
});
let isLoadingModel = $state(false);
let switchingBackends = $state(false);
let searchTerm = $state('');
let showModelDialog = $state(false);
let infoModelId = $state<string | null>(null);
@@ -141,11 +143,18 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
backendsStore.setActive(backendId);
searchTerm = '';
// keep the multi-model selector mounted while the new backend's props and
// models load; role flips (external MODEL mode -> local ROUTER mode) would
// otherwise unmount and remount the open dropdown mid switch
switchingBackends = true;
try {
await backendsModelsStore.ensureLoaded(backendId);
await modelsStore.switchBackend();
} catch (error) {
console.error('Failed to switch backend:', error);
} finally {
switchingBackends = false;
}
}
@@ -321,6 +330,10 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
return showModelDialog;
},
get switchingBackends() {
return switchingBackends;
},
get updating() {
return updating;
}