Commit b12b405c1 for llama.cpp
commit b12b405c171bc2ec98ce7a860a69c073dcac41b3
Author: Aleksander Grygier <aleksander.grygier@gmail.com>
Date: Mon Nov 2 12:33:28 2026 +0100
ui: don't reload a loading model on select, prefer it in pre-selection (#31326)
* ui: don't reload a loading model on select, prefer it in pre-selection
Assisted-by: Claude (Opus 5.5 Medium)
* ui: skip hidden models in automatic model selection
Assisted-by: Claude (Opus 5.5 Medium)
* ui: don't count a model being unloaded as loading
Assisted-by: Claude (Opus 5.5 Medium)
diff --git a/tools/ui/src/lib/hooks/use-chat-form-model.svelte.ts b/tools/ui/src/lib/hooks/use-chat-form-model.svelte.ts
index a32acba6a..c86f492a8 100644
--- a/tools/ui/src/lib/hooks/use-chat-form-model.svelte.ts
+++ b/tools/ui/src/lib/hooks/use-chat-form-model.svelte.ts
@@ -2,10 +2,10 @@
* Model state for the chat form's action bar.
*
* Syncs the conversation's model into the global selection (a conversation
- * opened from history names the model that produced it), falls back to the
- * first loaded model on a router, and reactively tracks which modalities
- * (vision / audio / video) the active model supports - fetching model props
- * from the server on demand if they are not cached yet.
+ * opened from history names the model that produced it), falls back to a
+ * loading, then the first loaded, model on a router, and reactively tracks
+ * which modalities (vision / audio / video) the active model supports -
+ * fetching model props from the server on demand if they are not cached yet.
*/
import { conversationsStore, modelsStore, serverStore } from '$lib/stores';
@@ -36,12 +36,16 @@ export function useChatFormModel() {
} else if (
isRouter &&
!modelsStore.selectedModelId &&
- modelsStore.loadedModelIds.length > 0 &&
conversationsStore.activeMessages.length > 0 &&
!conversationModel
) {
lastSyncedConversationModel = null;
- const first = modelsStore.models.find((m) => modelsStore.loadedModelIds.includes(m.model));
+ // a model that is loading wins over one that is already up; a model the
+ // selector doesn't list is never picked
+ const visible = modelsStore.getVisibleModels();
+ const first =
+ visible.find((m) => modelsStore.isModelLoading(m.model)) ??
+ visible.find((m) => modelsStore.loadedModelIds.includes(m.model));
if (first) modelsStore.selectModelById(first.id);
}
diff --git a/tools/ui/src/lib/hooks/use-models-selector.svelte.ts b/tools/ui/src/lib/hooks/use-models-selector.svelte.ts
index 844a7d4fb..6fe35e444 100644
--- a/tools/ui/src/lib/hooks/use-models-selector.svelte.ts
+++ b/tools/ui/src/lib/hooks/use-models-selector.svelte.ts
@@ -167,8 +167,14 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
});
}
- // only the built-in server loads on request, and only in router mode
- if (!onModelChange && isRouter && !modelsStore.isModelLoaded(option.model)) {
+ // only the built-in server loads on request, and only in router mode; a model
+ // that is already loading is only selected, not loaded a second time
+ if (
+ !onModelChange &&
+ isRouter &&
+ !modelsStore.isModelLoaded(option.model) &&
+ !modelsStore.isModelLoading(option.model)
+ ) {
isLoadingModel = true;
modelsStore.status
diff --git a/tools/ui/src/lib/stores/models/index.svelte.ts b/tools/ui/src/lib/stores/models/index.svelte.ts
index 79bd5b1ad..0a683c24b 100644
--- a/tools/ui/src/lib/stores/models/index.svelte.ts
+++ b/tools/ui/src/lib/stores/models/index.svelte.ts
@@ -205,12 +205,22 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
* Prioritizes:
* 1. Model from active conversation's last assistant response (if loaded)
* 2. Model from active conversation's last assistant response (if not loaded)
- * 3. First loaded model (not from active conversation)
- * 4. A favorite model
- * 5. First available model
+ * 3. A model that is loading
+ * 4. First loaded model (not from active conversation)
+ * 5. A favorite model
+ * 6. First available model
+ *
+ * A chat without a model of its own also trades an idle selection for a
+ * model that is loading.
*/
async ensureFirstModelSelected(): Promise<void> {
- if (this.selectedModelName) return;
+ if (this.selectedModelName) {
+ const loadingModel = this.getLoadingModelToPreselect();
+
+ if (loadingModel) await this.selectModelById(loadingModel.id);
+
+ return;
+ }
const availableModels = this.getVisibleModels();
@@ -233,6 +243,15 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
}
}
+ // a model that is loading is the one the user is waiting for
+ const loadingModel = availableModels.find((m) => this.isModelLoading(m.model));
+
+ if (loadingModel) {
+ await this.selectModelById(loadingModel.id);
+
+ return;
+ }
+
// Try a loaded model first
const loadedModel = availableModels.find((m) => this.isModelLoaded(m.model));
@@ -321,6 +340,20 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
);
}
+ /**
+ * The loading model a chat without a model of its own should switch to: none
+ * while the current selection is loaded or loading itself.
+ */
+ getLoadingModelToPreselect(): ModelOption | null {
+ if (this.getModelFromLastAssistantResponse()) return null;
+
+ const selected = this.selectedModelName;
+
+ if (selected && (this.isModelLoaded(selected) || this.isModelLoading(selected))) return null;
+
+ return this.getVisibleModels().find((m) => this.isModelLoading(m.model)) ?? null;
+ }
+
/**
* Gets the model name from the last assistant message in the active conversation.
* Used by both the chat page and settings page to maintain model consistency.
@@ -345,6 +378,16 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
return (model?.status?.value as ServerModelStatus) ?? null;
}
+ /**
+ * Models the selector lists: not flagged out of the UI (ui === false) and not
+ * hidden by the user, so an automatic pick never lands on one it doesn't show.
+ */
+ getVisibleModels(): ModelOption[] {
+ return this.models.filter(
+ (option) => this.props.getModelProps(option.model)?.ui !== false && !this.isHidden(option.id)
+ );
+ }
+
hasModel(modelName: string): boolean {
return this.models.some((model) => model.model === modelName);
}
@@ -367,6 +410,15 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
return status === ServerModelStatus.LOADED || status === ServerModelStatus.SLEEPING;
}
+ /** Loading on the server, or a load this client started that has not finished yet. */
+ isModelLoading(modelId: string): boolean {
+ // an unload raises the same in-progress flag, but the model stays loaded until it ends
+ return (
+ this.getModelStatus(modelId) === ServerModelStatus.LOADING ||
+ (this._status.isOperationInProgress(modelId) && !this.isModelLoaded(modelId))
+ );
+ }
+
/** Loaded, or sleeping, and not mid-operation: what a row shows as running. */
isModelRunning(modelId: string): boolean {
return this.isModelLoaded(modelId) && !this._status.isOperationInProgress(modelId);
@@ -422,13 +474,24 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
}
/**
- * Auto-selects the model from the last assistant response if available and loaded.
+ * Auto-selects the model from the last assistant response if available and loaded,
+ * or a loading model when the conversation has no model yet.
* Returns true if a model was selected, false otherwise.
*/
async selectModelFromLastAssistantResponse(): Promise<boolean> {
const lastModel = this.getModelFromLastAssistantResponse();
- if (!lastModel || this.selectedModelName === lastModel) return false;
+ if (!lastModel) {
+ const loadingModel = this.getLoadingModelToPreselect();
+
+ if (!loadingModel) return false;
+
+ await this.selectModelById(loadingModel.id);
+
+ return true;
+ }
+
+ if (this.selectedModelName === lastModel) return false;
const matchingModel = this.models.find((option) => option.model === lastModel);
@@ -612,19 +675,12 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
})
);
}
-
/** Fetch models in MODEL mode (single model, standard OpenAI-compatible). */
private async fetchModelModeInternal(): Promise<ModelOption[]> {
const response = await ModelsService.list();
return this.buildModelOptions(response);
}
- /**
- * Filter to models visible in the UI (ui !== false).
- */
- private getVisibleModels(): ModelOption[] {
- return this.models.filter((option) => this.props.getModelProps(option.model)?.ui !== false);
- }
private loadFavoritesFromStorage(): Set<string> {
try {