mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-17 20:31:47 +02:00
ui : keep the model selector mounted across backend switches
Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
@@ -276,13 +276,6 @@
|
|||||||
class="w-full md:min-w-80 md:max-w-[26rem] max-w-[calc(100vw-2rem)] p-0!"
|
class="w-full md:min-w-80 md:max-w-[26rem] max-w-[calc(100vw-2rem)] p-0!"
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
<ModelsSelectorBackendSwitcher
|
|
||||||
activeId={ms.activeBackendId}
|
|
||||||
backends={ms.backends}
|
|
||||||
onAdd={handleAddBackend}
|
|
||||||
onSelect={(backendId) => void ms.handleBackendChange(backendId)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DropdownMenuSearchable
|
<DropdownMenuSearchable
|
||||||
emptyMessage="No models found."
|
emptyMessage="No models found."
|
||||||
isEmpty={ms.filteredOptions.length === 0 && ms.isCurrentModelInCache}
|
isEmpty={ms.filteredOptions.length === 0 && ms.isCurrentModelInCache}
|
||||||
@@ -292,6 +285,14 @@
|
|||||||
searchClass="bg-transparent"
|
searchClass="bg-transparent"
|
||||||
searchValue={ms.searchTerm}
|
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
|
<!-- Option list; the search header sticks to the top and the actions
|
||||||
footer to the bottom of the content scrollport. -->
|
footer to the bottom of the content scrollport. -->
|
||||||
<div class="models-list px-1.5">
|
<div class="models-list px-1.5">
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
? Math.round(modelLoadFraction(modelsStore.status.getLoadProgress(triggerModel)) * 100)
|
? Math.round(modelLoadFraction(modelsStore.status.getLoadProgress(triggerModel)) * 100)
|
||||||
: 0}
|
: 0}
|
||||||
|
|
||||||
{#if ms.isMultiModel}
|
{#if ms.isMultiModel || ms.switchingBackends}
|
||||||
<button
|
<button
|
||||||
class={[
|
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`,
|
`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" />
|
<Lightbulb class="h-3.5 w-3.5 shrink-0 text-amber-400" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if ms.updating || ms.isLoadingModel}
|
{#if ms.updating || ms.isLoadingModel || ms.switchingBackends}
|
||||||
<Loader2 class="h-3 w-3.5 shrink-0 animate-spin" />
|
<Loader2 class="h-3 w-3.5 shrink-0 animate-spin" />
|
||||||
{:else}
|
{:else}
|
||||||
<ChevronDown class="h-3 w-3.5 shrink-0" />
|
<ChevronDown class="h-3 w-3.5 shrink-0" />
|
||||||
@@ -149,13 +149,6 @@
|
|||||||
</Sheet.Header>
|
</Sheet.Header>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1 pb-4">
|
<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">
|
<div class="mb-3 px-4">
|
||||||
<SearchInput
|
<SearchInput
|
||||||
onInput={(v) => ms.setSearchTerm(v)}
|
onInput={(v) => ms.setSearchTerm(v)}
|
||||||
@@ -164,6 +157,14 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<div class="max-h-[60vh] overflow-y-auto px-2">
|
||||||
{#if !ms.isCurrentModelInCache && currentModel}
|
{#if !ms.isCurrentModelInCache && currentModel}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface UseModelsSelectorReturn {
|
|||||||
readonly filteredOptions: ModelOption[];
|
readonly filteredOptions: ModelOption[];
|
||||||
readonly groupedFilteredOptions: ReturnType<typeof groupModelOptions>;
|
readonly groupedFilteredOptions: ReturnType<typeof groupModelOptions>;
|
||||||
readonly isLoadingModel: boolean;
|
readonly isLoadingModel: boolean;
|
||||||
|
readonly switchingBackends: boolean;
|
||||||
readonly searchTerm: string;
|
readonly searchTerm: string;
|
||||||
readonly showModelDialog: boolean;
|
readonly showModelDialog: boolean;
|
||||||
readonly infoModelId: string | null;
|
readonly infoModelId: string | null;
|
||||||
@@ -88,6 +89,7 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
|
|||||||
});
|
});
|
||||||
|
|
||||||
let isLoadingModel = $state(false);
|
let isLoadingModel = $state(false);
|
||||||
|
let switchingBackends = $state(false);
|
||||||
let searchTerm = $state('');
|
let searchTerm = $state('');
|
||||||
let showModelDialog = $state(false);
|
let showModelDialog = $state(false);
|
||||||
let infoModelId = $state<string | null>(null);
|
let infoModelId = $state<string | null>(null);
|
||||||
@@ -141,11 +143,18 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
|
|||||||
backendsStore.setActive(backendId);
|
backendsStore.setActive(backendId);
|
||||||
searchTerm = '';
|
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 {
|
try {
|
||||||
await backendsModelsStore.ensureLoaded(backendId);
|
await backendsModelsStore.ensureLoaded(backendId);
|
||||||
await modelsStore.switchBackend();
|
await modelsStore.switchBackend();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to switch backend:', error);
|
console.error('Failed to switch backend:', error);
|
||||||
|
} finally {
|
||||||
|
switchingBackends = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,6 +330,10 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
|
|||||||
return showModelDialog;
|
return showModelDialog;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get switchingBackends() {
|
||||||
|
return switchingBackends;
|
||||||
|
},
|
||||||
|
|
||||||
get updating() {
|
get updating() {
|
||||||
return updating;
|
return updating;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user