ui : cache the local backend's model list

Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
Aleksander Grygier
2026-09-16 19:22:47 +02:00
parent 051c0a9bff
commit 10d730ab6d
3 changed files with 31 additions and 14 deletions
@@ -6,8 +6,10 @@
* consumed by the backends settings UI and the per-backend model cache.
*/
import { API_MODELS, LOCAL_BACKEND_ID } from '$lib/constants';
import type { Backend, ModelOption } from '$lib/types';
import { isAbortError } from '$lib/utils/abort';
import { apiUrl } from '$lib/utils/api-base';
import { getAuthHeadersForBackend } from '$lib/utils/api-headers';
import { backendModelsUrl } from '$lib/utils/backend';
@@ -35,12 +37,17 @@ export class BackendsService {
* @param signal - Optional abort signal for a cancelled request.
*/
static async listModels(backend: Backend, signal?: AbortSignal): Promise<BackendModelsResult> {
if (!backend.baseUrl.trim()) {
// the local backend has no base URL; its models endpoint is base relative
const url = backend.baseUrl.trim()
? backendModelsUrl(backend)
: apiUrl(API_MODELS.LIST, LOCAL_BACKEND_ID);
if (!backend.baseUrl.trim() && backend.id !== LOCAL_BACKEND_ID) {
return { error: 'Backend URL is required', models: [], ok: false, status: null };
}
try {
const response = await fetch(backendModelsUrl(backend), {
const response = await fetch(url, {
headers: getAuthHeadersForBackend(backend),
signal
});
@@ -1,10 +1,10 @@
/**
* backendsModelsStore - Per-backend model catalog cache.
*
* The models selector prefetches every enabled backend's model list the first
* time it opens, so switching backends is instant and the switcher can show
* load state. The active backend's list still lives in modelsStore, which owns
* selection and chat wiring; this cache is the prefetch layer for the others.
* Prefetches every enabled backend's model list, so switching backends is
* instant and the switcher can show load state. The active backend's list
* still lives in modelsStore, which owns selection and chat wiring; this
* cache is the prefetch layer the switches start from.
*/
import { BackendsService } from '$lib/services/backends.service';
@@ -28,13 +28,12 @@ class BackendsModelsStore {
}
/**
* Load a backend's models once. Local backends are skipped: their list is
* owned by modelsStore.
* Load a backend's models once.
*/
async ensureLoaded(backendId: string): Promise<void> {
const backend = backendsStore.enabled.find((candidate) => candidate.id === backendId);
if (!backend || !backend.baseUrl.trim()) return;
if (!backend) return;
const state = this.states[backendId];
+16 -5
View File
@@ -410,17 +410,28 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
this.clearSelection();
this.routerModels = [];
this.error = null;
serverStore.clear();
const backend = backendsStore.active;
// server props describe the local server; drop them only when the next
// backend is not the one they describe, refresh in place otherwise
if (backend.protocol !== 'llama.cpp') {
serverStore.clear();
}
// prefer the prefetched list so switching does not refetch
const backend = backendsStore.active;
const cached = backend.baseUrl.trim() ? backendsModelsStore.get(backend.id) : null;
const cached = backendsModelsStore.get(backend.id);
if (cached?.loaded) {
if (cached.loaded) {
this.activeModels = cached.models;
this.loading = false;
await serverStore.fetch();
await serverStore.fetch({ background: true });
// the cache carries names only; reload the router load status in place
if (serverStore.isRouterMode) {
await this.fetchRouterModels();
}
if (this.activeModels.length > 0) {
await this.ensureFirstModelSelected();