ui : type-safe API types, fetch helpers and download-ready models store plumbing

Assisted-by: pi:GLM-5.3-Flash
This commit is contained in:
Aleksander Grygier
2026-09-07 16:08:15 +02:00
parent 296b0f8881
commit 8635b58aae
10 changed files with 41 additions and 128 deletions
+8 -5
View File
@@ -71,7 +71,7 @@
"svelte-check": "4.6.0",
"svelte-sonner": "1.1.1",
"tailwind-merge": "3.6.0",
"tailwind-variants": "3.2.2",
"tailwind-variants": "3.3.1",
"tailwindcss": "4.3.0",
"tw-animate-css": "1.4.0",
"typescript": "5.9.3",
@@ -16048,13 +16048,13 @@
}
},
"node_modules/tailwind-variants": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.2.2.tgz",
"integrity": "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.3.1.tgz",
"integrity": "sha512-4pAvwUtM4HKBiRZftncAbpn6V9Hhwoa5Fl7O2u5zbp7Z5Cvu+/o/6+176WY3WCEES209543quG8zFIcXCsc5Jw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16.x",
"node": ">=16.9.x",
"pnpm": ">=7.x"
},
"peerDependencies": {
@@ -16064,6 +16064,9 @@
"peerDependenciesMeta": {
"tailwind-merge": {
"optional": true
},
"tailwindcss": {
"optional": true
}
}
},
+2 -1
View File
@@ -13,6 +13,7 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"reset": "rm -rf .svelte-kit node_modules",
"format": "eslint --fix . && prettier --write .",
"format:files": "sh -c 'eslint --fix \"$@\" && prettier --write \"$@\"' sh",
"lint": "prettier --check . && eslint .",
"test": "npm run test:ui -- --run && npm run test:client -- --run && npm run test:unit -- --run && npm run test:e2e",
"test:e2e": "playwright test",
@@ -90,7 +91,7 @@
"svelte-check": "4.6.0",
"svelte-sonner": "1.1.1",
"tailwind-merge": "3.6.0",
"tailwind-variants": "3.2.2",
"tailwind-variants": "3.3.1",
"tailwindcss": "4.3.0",
"tw-animate-css": "1.4.0",
"typescript": "5.9.3",
+6 -18
View File
@@ -18,20 +18,14 @@ import type {
ApiErrorResponse,
ApiLlamaCppServerProps,
ApiModelDataEntry,
ApiModelListResponse,
ApiModelLoadStage,
ApiModelsListResponse,
ApiModelsLoadResponse,
ApiModelsSseData,
ApiModelsSseEvent,
ApiModelsSseProgress,
ApiModelsUnloadResponse,
ApiProcessingState,
ApiRouterModelMeta,
ApiRouterModelsListResponse,
ApiRouterModelsLoadRequest,
ApiRouterModelsLoadResponse,
ApiRouterModelsStatusRequest,
ApiRouterModelsStatusResponse,
ApiRouterModelsUnloadRequest,
ApiRouterModelsUnloadResponse,
ChatAttachmentDisplayItem,
// Chat types
ChatMessagePromptProgress,
@@ -89,16 +83,10 @@ declare global {
ApiModelsSseProgress,
ApiModelsSseData,
ApiModelsSseEvent,
ApiModelListResponse,
ApiModelsListResponse,
ApiModelsLoadResponse,
ApiModelsUnloadResponse,
ApiProcessingState,
ApiRouterModelMeta,
ApiRouterModelsLoadRequest,
ApiRouterModelsLoadResponse,
ApiRouterModelsStatusRequest,
ApiRouterModelsStatusResponse,
ApiRouterModelsListResponse,
ApiRouterModelsUnloadRequest,
ApiRouterModelsUnloadResponse,
// Chat types
ChatAttachmentDisplayItem,
ChatMessagePromptProgress,
@@ -220,8 +220,7 @@ export { default as ChatFormActionModels } from './ChatForm/ChatFormActions/Chat
*/
export { default as ChatFormActionAddToolsSubmenu } from './ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddToolsSubmenu.svelte';
/**
* Dropdown submenu for selecting reasoning effort level.
/** Dropdown submenu for selecting reasoning effort level.
*
* Shows a "Reasoning" sub-menu item with a lightbulb icon indicating
* thinking status, and a nested list of effort levels.
+1 -1
View File
@@ -136,7 +136,7 @@ export { ConversationTransferService } from './conversation-transfer.service';
*
* **Server Mode Behavior:**
* - **MODEL mode**: Only `list()` is relevant — single model always loaded
* - **ROUTER mode**: Full lifecycle — `list()`, `listRouter()`, `load()`, `unload()`
* - **ROUTER mode**: Full lifecycle — `list()`, `load()`, `unload()`
*
* **Endpoints:**
* - `GET /v1/models` — OpenAI-compatible model list (both modes)
+6 -17
View File
@@ -56,19 +56,8 @@ export class ModelsService {
*
* @returns List of available models with basic metadata
*/
static async list(): Promise<ApiModelListResponse> {
return apiFetch<ApiModelListResponse>(API_MODELS.LIST);
}
/**
* Fetch list of all models with detailed metadata (ROUTER mode).
* Returns models with load status, paths, and other metadata
* beyond what the OpenAI-compatible endpoint provides.
*
* @returns List of models with detailed status and configuration info
*/
static async listRouter(): Promise<ApiRouterModelsListResponse> {
return apiFetch<ApiRouterModelsListResponse>(API_MODELS.LIST);
static async list(): Promise<ApiModelsListResponse> {
return apiFetch<ApiModelsListResponse>(API_MODELS.LIST);
}
/**
@@ -80,14 +69,14 @@ export class ModelsService {
* @param extraArgs - Optional additional arguments to pass to the model instance
* @returns Load response from the server
*/
static async load(modelId: string, extraArgs?: string[]): Promise<ApiRouterModelsLoadResponse> {
static async load(modelId: string, extraArgs?: string[]): Promise<ApiModelsLoadResponse> {
const payload: { model: string; extra_args?: string[] } = { model: modelId };
if (extraArgs && extraArgs.length > 0) {
payload.extra_args = extraArgs;
}
return apiPost<ApiRouterModelsLoadResponse>(API_MODELS.LOAD, payload);
return apiPost<ApiModelsLoadResponse>(API_MODELS.LOAD, payload);
}
/**
@@ -220,8 +209,8 @@ export class ModelsService {
* @param modelId - Model identifier to unload
* @returns Unload response from the server
*/
static async unload(modelId: string): Promise<ApiRouterModelsUnloadResponse> {
return apiPost<ApiRouterModelsUnloadResponse>(API_MODELS.UNLOAD, { model: modelId });
static async unload(modelId: string): Promise<ApiModelsUnloadResponse> {
return apiPost<ApiModelsUnloadResponse>(API_MODELS.UNLOAD, { model: modelId });
}
/**
@@ -193,15 +193,15 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
}
/**
* Fetch router models with full metadata (ROUTER mode only).
* No-op in router mode fetch() already calls listRouter() internally.
* Fetch models with full metadata (ROUTER mode only).
* No-op in MODEL mode - fetch() already calls list() internally.
* Kept for API compatibility (e.g. handleOpenChange dropdown open handler).
*/
async fetchRouterModels(): Promise<void> {
if (!serverStore.isRouterMode) return;
try {
const response = await ModelsService.listRouter();
const response = await ModelsService.list();
this.routerModels = response.data;
await this.props.fetchModalitiesForLoadedModels();
@@ -358,9 +358,7 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
* Both MODEL and ROUTER modes share the same mapping logic;
* they differ only in which endpoint is called.
*/
private buildModelOptions(
response: ApiModelListResponse | ApiRouterModelsListResponse
): ModelOption[] {
private buildModelOptions(response: ApiModelsListResponse): ModelOption[] {
return response.data.map((item: ApiModelDataEntry, index: number) => {
const details = response.models?.[index];
const rawCapabilities = Array.isArray(details?.capabilities) ? details?.capabilities : [];
@@ -390,7 +388,6 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
return this.buildModelOptions(response);
}
/**
* Filter to models visible in the UI (ui !== false).
*/
@@ -422,7 +419,7 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
const router = serverStore.isRouterMode;
if (router) {
const response = await ModelsService.listRouter();
const response = await ModelsService.list();
this.routerModels = response.data;
this.models = this.buildModelOptions(response);
+8 -66
View File
@@ -174,12 +174,6 @@ export interface ApiModelDetails {
};
}
export interface ApiModelListResponse {
object: string;
data: ApiModelDataEntry[];
models?: ApiModelDetails[];
}
export interface ApiLlamaCppServerProps {
default_generation_settings: {
id: number;
@@ -448,79 +442,27 @@ export interface ApiProcessingState {
}
/**
* Router model metadata - extended from ApiModelDataEntry with additional router-specific fields
* @deprecated Use ApiModelDataEntry instead - the /models endpoint returns this structure directly
* Response from POST /models/load
*/
export interface ApiRouterModelMeta {
/** Model identifier (e.g., "ggml-org/Qwen2.5-Omni-7B-GGUF:latest") */
name: string;
/** Path to model file or manifest */
path: string;
/** Optional path to multimodal projector */
path_mmproj?: string;
/** Whether model is in HuggingFace cache */
in_cache: boolean;
/** Port where model instance is running (0 if not loaded) */
port?: number;
/** Current status of the model */
status: ApiModelStatus;
/** Error message if status is FAILED */
error?: string;
}
/**
* Request to load a model
*/
export interface ApiRouterModelsLoadRequest {
model: string;
}
/**
* Response from loading a model
*/
export interface ApiRouterModelsLoadResponse {
export interface ApiModelsLoadResponse {
success: boolean;
error?: string;
}
/**
* Request to check model status
* Response with list of all models from /v1/models and /models endpoints
* (same structure regardless of server mode)
*/
export interface ApiRouterModelsStatusRequest {
model: string;
}
/**
* Response with model status
*/
export interface ApiRouterModelsStatusResponse {
model: string;
status: ModelStatus;
port?: number;
error?: string;
}
/**
* Response with list of all models from /models endpoint
* Note: This is the same as ApiModelListResponse - the endpoint returns the same structure
* regardless of server mode (MODEL or ROUTER)
*/
export interface ApiRouterModelsListResponse {
export interface ApiModelsListResponse {
object: string;
data: ApiModelDataEntry[];
models?: ApiModelDetails[];
}
/**
* Request to unload a model
* Response from POST /models/unload
*/
export interface ApiRouterModelsUnloadRequest {
model: string;
}
/**
* Response from unloading a model
*/
export interface ApiRouterModelsUnloadResponse {
export interface ApiModelsUnloadResponse {
success: boolean;
error?: string;
}
+3 -9
View File
@@ -16,7 +16,6 @@ export type {
ApiModelsSseData,
ApiModelsSseEvent,
ApiModelDetails,
ApiModelListResponse,
ApiLlamaCppServerProps,
ApiChatCompletionRequest,
ApiChatCompletionToolCallFunctionDelta,
@@ -26,14 +25,9 @@ export type {
ApiChatCompletionResponse,
ApiSlotData,
ApiProcessingState,
ApiRouterModelMeta,
ApiRouterModelsLoadRequest,
ApiRouterModelsLoadResponse,
ApiRouterModelsStatusRequest,
ApiRouterModelsStatusResponse,
ApiRouterModelsListResponse,
ApiRouterModelsUnloadRequest,
ApiRouterModelsUnloadResponse,
ApiModelsLoadResponse,
ApiModelsListResponse,
ApiModelsUnloadResponse,
AudioInputFormat,
ApiStreamSession
} from './api';
+1 -1
View File
@@ -49,7 +49,7 @@ export interface ApiFetchOptions extends Omit<RequestInit, 'headers'> {
* @example
* ```typescript
* // GET request
* const models = await apiFetch<ApiModelListResponse>('/v1/models');
* const models = await apiFetch<ApiModelsListResponse>('/v1/models');
*
* // POST request
* const result = await apiFetch<ApiResponse>('/models/load', {