From 10d730ab6dc76f8b166e5688ef32474cc6600f1e Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Tue, 15 Sep 2026 19:44:27 +0200 Subject: [PATCH] ui : cache the local backend's model list Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash --- tools/ui/src/lib/services/backends.service.ts | 11 ++++++++-- .../src/lib/stores/backendsModels.svelte.ts | 13 ++++++------ .../ui/src/lib/stores/models/index.svelte.ts | 21 ++++++++++++++----- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/tools/ui/src/lib/services/backends.service.ts b/tools/ui/src/lib/services/backends.service.ts index 37420ef79f..667f7ab34b 100644 --- a/tools/ui/src/lib/services/backends.service.ts +++ b/tools/ui/src/lib/services/backends.service.ts @@ -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 { - 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 }); diff --git a/tools/ui/src/lib/stores/backendsModels.svelte.ts b/tools/ui/src/lib/stores/backendsModels.svelte.ts index 6d0d7f5054..2b645d0e95 100644 --- a/tools/ui/src/lib/stores/backendsModels.svelte.ts +++ b/tools/ui/src/lib/stores/backendsModels.svelte.ts @@ -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 { const backend = backendsStore.enabled.find((candidate) => candidate.id === backendId); - if (!backend || !backend.baseUrl.trim()) return; + if (!backend) return; const state = this.states[backendId]; diff --git a/tools/ui/src/lib/stores/models/index.svelte.ts b/tools/ui/src/lib/stores/models/index.svelte.ts index 24c564a0d4..db7cd4320e 100644 --- a/tools/ui/src/lib/stores/models/index.svelte.ts +++ b/tools/ui/src/lib/stores/models/index.svelte.ts @@ -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();