Pre-MCP UI and architecture cleanup (#19689)

This commit is contained in:
Aleksander Grygier
2026-02-18 12:02:02 +01:00
committed by GitHub
parent d0061be838
commit ea003229d3
52 changed files with 5551 additions and 4323 deletions
@@ -1,8 +1,9 @@
import { SvelteSet } from 'svelte/reactivity';
import { ModelsService } from '$lib/services/models.service';
import { PropsService } from '$lib/services/props.service';
import { ServerModelStatus, ModelModality } from '$lib/enums';
import { ModelsService, PropsService } from '$lib/services';
import { serverStore } from '$lib/stores/server.svelte';
import { TTLCache } from '$lib/utils';
import { MODEL_PROPS_CACHE_TTL_MS, MODEL_PROPS_CACHE_MAX_ENTRIES } from '$lib/constants/cache';
/**
* modelsStore - Reactive store for model management in both MODEL and ROUTER modes
@@ -32,9 +33,13 @@ import { serverStore } from '$lib/stores/server.svelte';
* - **Lazy loading**: ensureModelLoaded() loads models on demand
*/
class ModelsStore {
// ─────────────────────────────────────────────────────────────────────────────
// State
// ─────────────────────────────────────────────────────────────────────────────
/**
*
*
* State
*
*
*/
models = $state<ModelOption[]>([]);
routerModels = $state<ApiModelDataEntry[]>([]);
@@ -48,10 +53,14 @@ class ModelsStore {
private modelLoadingStates = $state<Map<string, boolean>>(new Map());
/**
* Model-specific props cache
* Model-specific props cache with TTL
* Key: modelId, Value: props data including modalities
* TTL: 10 minutes - props don't change frequently
*/
private modelPropsCache = $state<Map<string, ApiLlamaCppServerProps>>(new Map());
private modelPropsCache = new TTLCache<string, ApiLlamaCppServerProps>({
ttlMs: MODEL_PROPS_CACHE_TTL_MS,
maxEntries: MODEL_PROPS_CACHE_MAX_ENTRIES
});
private modelPropsFetching = $state<Set<string>>(new Set());
/**
@@ -59,9 +68,13 @@ class ModelsStore {
*/
propsCacheVersion = $state(0);
// ─────────────────────────────────────────────────────────────────────────────
// Computed Getters
// ─────────────────────────────────────────────────────────────────────────────
/**
*
*
* Computed Getters
*
*
*/
get selectedModel(): ModelOption | null {
if (!this.selectedModelId) return null;
@@ -95,22 +108,24 @@ class ModelsStore {
return props.model_path.split(/(\\|\/)/).pop() || null;
}
// ─────────────────────────────────────────────────────────────────────────────
// Modalities
// ─────────────────────────────────────────────────────────────────────────────
/**
*
*
* Modalities
*
*
*/
/**
* Get modalities for a specific model
* Returns cached modalities from model props
*/
getModelModalities(modelId: string): ModelModalities | null {
// First check if modalities are stored in the model option
const model = this.models.find((m) => m.model === modelId || m.id === modelId);
if (model?.modalities) {
return model.modalities;
}
// Fall back to props cache
const props = this.modelPropsCache.get(modelId);
if (props?.modalities) {
return {
@@ -155,15 +170,17 @@ class ModelsStore {
* Get props for a specific model (from cache)
*/
getModelProps(modelId: string): ApiLlamaCppServerProps | null {
return this.modelPropsCache.get(modelId) ?? null;
return this.modelPropsCache.get(modelId);
}
/**
* Get context size (n_ctx) for a specific model from cached props
*/
getModelContextSize(modelId: string): number | null {
const props = this.modelPropsCache.get(modelId);
return props?.default_generation_settings?.n_ctx ?? null;
const props = this.getModelProps(modelId);
const nCtx = props?.default_generation_settings?.n_ctx;
return typeof nCtx === 'number' ? nCtx : null;
}
/**
@@ -181,9 +198,13 @@ class ModelsStore {
return this.modelPropsFetching.has(modelId);
}
// ─────────────────────────────────────────────────────────────────────────────
// Status Queries
// ─────────────────────────────────────────────────────────────────────────────
/**
*
*
* Status Queries
*
*
*/
isModelLoaded(modelId: string): boolean {
const model = this.routerModels.find((m) => m.id === modelId);
@@ -208,9 +229,13 @@ class ModelsStore {
return usage !== undefined && usage.size > 0;
}
// ─────────────────────────────────────────────────────────────────────────────
// Data Fetching
// ─────────────────────────────────────────────────────────────────────────────
/**
*
*
* Data Fetching
*
*
*/
/**
* Fetch list of models from server and detect server role
@@ -224,7 +249,6 @@ class ModelsStore {
this.error = null;
try {
// Ensure server props are loaded (for role detection and MODEL mode modalities)
if (!serverStore.props) {
await serverStore.fetch();
}
@@ -251,7 +275,6 @@ class ModelsStore {
this.models = models;
// In MODEL mode, populate modalities from serverStore.props (single model)
// WORKAROUND: In MODEL mode, /props returns modalities for the single model,
// but /v1/models doesn't include modalities. We bridge this gap here.
const serverProps = serverStore.props;
@@ -260,9 +283,7 @@ class ModelsStore {
vision: serverProps.modalities.vision ?? false,
audio: serverProps.modalities.audio ?? false
};
// Cache props for the single model
this.modelPropsCache.set(this.models[0].model, serverProps);
// Update model with modalities
this.models = this.models.map((model, index) =>
index === 0 ? { ...model, modalities } : model
);
@@ -302,7 +323,6 @@ class ModelsStore {
* @returns Props data or null if fetch failed or model not loaded
*/
async fetchModelProps(modelId: string): Promise<ApiLlamaCppServerProps | null> {
// Return cached props if available
const cached = this.modelPropsCache.get(modelId);
if (cached) return cached;
@@ -310,7 +330,6 @@ class ModelsStore {
return null;
}
// Avoid duplicate fetches
if (this.modelPropsFetching.has(modelId)) return null;
this.modelPropsFetching.add(modelId);
@@ -335,7 +354,6 @@ class ModelsStore {
const loadedModelIds = this.loadedModelIds;
if (loadedModelIds.length === 0) return;
// Fetch props for each loaded model in parallel
const propsPromises = loadedModelIds.map((modelId) => this.fetchModelProps(modelId));
try {
@@ -357,7 +375,6 @@ class ModelsStore {
return { ...model, modalities };
});
// Increment version to trigger reactivity
this.propsCacheVersion++;
} catch (error) {
console.warn('Failed to fetch modalities for loaded models:', error);
@@ -382,16 +399,19 @@ class ModelsStore {
model.model === modelId ? { ...model, modalities } : model
);
// Increment version to trigger reactivity
this.propsCacheVersion++;
} catch (error) {
console.warn(`Failed to update modalities for model ${modelId}:`, error);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Model Selection
// ─────────────────────────────────────────────────────────────────────────────
/**
*
*
* Model Selection
*
*
*/
/**
* Select a model for new conversations
@@ -443,9 +463,13 @@ class ModelsStore {
return this.models.some((model) => model.model === modelName);
}
// ─────────────────────────────────────────────────────────────────────────────
// Loading/Unloading Models
// ─────────────────────────────────────────────────────────────────────────────
/**
*
*
* Loading/Unloading Models
*
*
*/
/**
* WORKAROUND: Polling for model status after load/unload operations.
@@ -486,7 +510,6 @@ class ModelsStore {
return;
}
// Wait before next poll
await new Promise((resolve) => setTimeout(resolve, ModelsStore.STATUS_POLL_INTERVAL));
}
@@ -511,8 +534,6 @@ class ModelsStore {
try {
await ModelsService.load(modelId);
// Poll until model is loaded
await this.pollForModelStatus(modelId, ServerModelStatus.LOADED);
await this.updateModelModalities(modelId);
@@ -562,9 +583,13 @@ class ModelsStore {
await this.loadModel(modelId);
}
// ─────────────────────────────────────────────────────────────────────────────
// Utilities
// ─────────────────────────────────────────────────────────────────────────────
/**
*
*
* Utilities
*
*
*/
private toDisplayName(id: string): string {
const segments = id.split(/\\|\//);
@@ -586,6 +611,14 @@ class ModelsStore {
this.modelPropsCache.clear();
this.modelPropsFetching.clear();
}
/**
* Prune expired entries from caches.
* Call periodically for proactive memory cleanup.
*/
pruneExpiredCache(): number {
return this.modelPropsCache.prune();
}
}
export const modelsStore = new ModelsStore();