ui : stop refetching model props after a failure

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 a035f923c9
commit a1df2dd804
+27 -1
View File
@@ -14,12 +14,17 @@ import { MODEL_PROPS_CACHE } from '$lib/constants';
import { FileTypeCategory, ModelModality } from '$lib/enums';
import { PropsService } from '$lib/services/props.service';
// direct imports between stores, not via the barrel, to avoid circular deps
import { backendsStore } from '$lib/stores/backends.svelte';
import { serverStore } from '$lib/stores/server.svelte';
// deep imports, not the '$lib/utils' barrel: it re-exports modules that reach back
// into the stores, and going through it here would read a half-built module
import { TTLCache } from '$lib/utils/cache-ttl';
import { detectThinkingSupport } from '$lib/utils/chat-template-thinking-detector';
import { SvelteSet } from 'svelte/reactivity';
import { rawModelId } from '$lib/utils/model-option-id';
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
/** Grace period before /props is retried for a model after a failed fetch. */
const PROPS_FETCH_RETRY_COOLDOWN_MS = 30_000;
/**
* The slice of modelsStore the manager reads. Kept narrow on purpose so it
@@ -29,6 +34,8 @@ import { SvelteSet } from 'svelte/reactivity';
export interface ModelPropsHost {
/** Model rows the manager mirrors fetched modalities onto. */
activeModels: ModelOption[];
/** Every enabled backend's models, used to resolve a model's owner. */
readonly models: ModelOption[];
readonly selectedModelName: string | null;
readonly loadedModelIds: string[];
isModelLoaded(modelId: string): boolean;
@@ -45,6 +52,8 @@ export class ModelPropsManager {
maxEntries: MODEL_PROPS_CACHE.MAX_ENTRIES,
ttlMs: MODEL_PROPS_CACHE.TTL_MS
});
/** Last failed /props fetch per model; requests stay on cooldown after it. */
private failedPropsAt = new SvelteMap<string, number>();
private fetching = new SvelteSet<string>();
/**
@@ -159,10 +168,25 @@ export class ModelPropsManager {
if (cached) return cached;
// never ask a server about a model another backend serves; a name that
// exists on two backends resolves to the active one's entry first
const option = this.host.models.find(
(m) => m.model === modelId || m.id === modelId || rawModelId(m.id) === modelId
);
if (option?.backendId && option.backendId !== backendsStore.active.id) return null;
if (serverStore.isRouterMode && !this.host.isModelLoaded(modelId)) {
return null;
}
// a failing model would otherwise be refetched on every reactive update
const failedAt = this.failedPropsAt.get(modelId);
if (failedAt !== undefined && Date.now() - failedAt < PROPS_FETCH_RETRY_COOLDOWN_MS) {
return null;
}
if (this.fetching.has(modelId)) return null;
this.fetching.add(modelId);
@@ -172,9 +196,11 @@ export class ModelPropsManager {
this.cache.set(modelId, props);
this.cacheVersion++;
this.failedPropsAt.delete(modelId);
return props;
} catch (error) {
this.failedPropsAt.set(modelId, Date.now());
console.warn(`Failed to fetch props for model ${modelId}:`, error);
return null;