]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/commitdiff
Use OpenAI-compatible `/v1/models` endpoint by default (#17689)
authorAleksander Grygier <redacted>
Wed, 3 Dec 2025 19:49:09 +0000 (20:49 +0100)
committerGitHub <redacted>
Wed, 3 Dec 2025 19:49:09 +0000 (20:49 +0100)
* refactor: Data fetching via stores

* chore: update webui build output

* refactor: Use OpenAI compat `/v1/models` endpoint by default to list models

* chore: update webui build output

* chore: update webui build output

tools/server/public/index.html.gz
tools/server/webui/docs/flows/data-flow-simplified-router-mode.md
tools/server/webui/docs/flows/models-flow.md
tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessages.svelte
tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFields.svelte
tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsImportExportTab.svelte
tools/server/webui/src/lib/components/app/dialogs/DialogModelInformation.svelte
tools/server/webui/src/lib/services/chat.ts
tools/server/webui/src/lib/services/models.ts
tools/server/webui/src/lib/stores/conversations.svelte.ts
tools/server/webui/src/lib/stores/settings.svelte.ts

index 8e3d997f09bdfcaae7d3cbb7c5351bde523fa2de..508a4e7c7e5c12c8dba5851288d3f5a750c350aa 100644 (file)
Binary files a/tools/server/public/index.html.gz and b/tools/server/public/index.html.gz differ
index f5c4f05edf65423bd48269357738747847bd71ba..bccacf568412df8d7c1d998819e874b41a87775e 100644 (file)
@@ -15,7 +15,7 @@ sequenceDiagram
     Stores->>DB: load conversations
     Stores->>API: GET /props
     API-->>Stores: {role: "router"}
-    Stores->>API: GET /models
+    Stores->>API: GET /v1/models
     API-->>Stores: models[] with status (loaded/available)
     loop each loaded model
         Stores->>API: GET /props?model=X
@@ -28,7 +28,7 @@ sequenceDiagram
     alt model not loaded
         Stores->>API: POST /models/load
         loop poll status
-            Stores->>API: GET /models
+            Stores->>API: GET /v1/models
             API-->>Stores: check if loaded
         end
         Stores->>API: GET /props?model=X
index ce63da1b367a326da358b9909da45863efec04d8..c3031b7292355b0e04cb743f302ae85a06cd9542 100644 (file)
@@ -56,7 +56,7 @@ sequenceDiagram
     UI->>modelsStore: fetchRouterModels()
     activate modelsStore
     modelsStore->>ModelsSvc: listRouter()
-    ModelsSvc->>API: GET /models
+    ModelsSvc->>API: GET /v1/models
     API-->>ModelsSvc: ApiRouterModelsListResponse
     Note right of API: {data: [{id, status, path, in_cache}]}
     modelsStore->>modelsStore: routerModels = $state(data)
@@ -132,7 +132,7 @@ sequenceDiagram
     loop poll every 500ms (max 60 attempts)
         modelsStore->>modelsStore: fetchRouterModels()
         modelsStore->>ModelsSvc: listRouter()
-        ModelsSvc->>API: GET /models
+        ModelsSvc->>API: GET /v1/models
         API-->>ModelsSvc: models[]
         modelsStore->>modelsStore: getModelStatus(modelId)
         alt status === LOADED
@@ -165,7 +165,7 @@ sequenceDiagram
     modelsStore->>modelsStore: pollForModelStatus(modelId, UNLOADED)
     loop poll until unloaded
         modelsStore->>ModelsSvc: listRouter()
-        ModelsSvc->>API: GET /models
+        ModelsSvc->>API: GET /v1/models
     end
 
     modelsStore->>modelsStore: modelLoadingStates.set(modelId, false)
index 6024f66c8bd22352a92665b4ac8125f455a5de9a..f307f829bc6de21eaa77b1abab608615393ac430 100644 (file)
@@ -1,6 +1,5 @@
 <script lang="ts">
        import { ChatMessage } from '$lib/components/app';
-       import { DatabaseService } from '$lib/services/database';
        import { chatStore } from '$lib/stores/chat.svelte';
        import { conversationsStore, activeConversation } from '$lib/stores/conversations.svelte';
        import { getMessageSiblings } from '$lib/utils';
@@ -19,7 +18,7 @@
                const conversation = activeConversation();
 
                if (conversation) {
-                       DatabaseService.getConversationMessages(conversation.id).then((messages) => {
+                       conversationsStore.getConversationMessages(conversation.id).then((messages) => {
                                allConversationMessages = messages;
                        });
                } else {
index 305687decbdb08e4bbbb5f747e26bc2a4c670169..ef46e2d1764ba9cb3cae3c705671a6b777472f24 100644 (file)
@@ -7,7 +7,6 @@
        import { Textarea } from '$lib/components/ui/textarea';
        import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config';
        import { settingsStore } from '$lib/stores/settings.svelte';
-       import { ParameterSyncService } from '$lib/services/parameter-sync';
        import { ChatSettingsParameterSourceIndicator } from '$lib/components/app';
        import type { Component } from 'svelte';
 
@@ -22,7 +21,7 @@
 
        // Helper function to get parameter source info for syncable parameters
        function getParameterSourceInfo(key: string) {
-               if (!ParameterSyncService.canSyncParameter(key)) {
+               if (!settingsStore.canSyncParameter(key)) {
                        return null;
                }
 
index 800d9af328a7ea87ce18edbc750566652475a8a6..7edce48b9bc0b4d500956f5e8608f8ed1c8b8296 100644 (file)
@@ -2,9 +2,8 @@
        import { Download, Upload } from '@lucide/svelte';
        import { Button } from '$lib/components/ui/button';
        import { DialogConversationSelection } from '$lib/components/app';
-       import { DatabaseService } from '$lib/services/database';
        import { createMessageCountMap } from '$lib/utils';
-       import { conversationsStore } from '$lib/stores/conversations.svelte';
+       import { conversationsStore, conversations } from '$lib/stores/conversations.svelte';
 
        let exportedConversations = $state<DatabaseConversation[]>([]);
        let importedConversations = $state<DatabaseConversation[]>([]);
 
        async function handleExportClick() {
                try {
-                       const allConversations = await DatabaseService.getAllConversations();
+                       const allConversations = conversations();
                        if (allConversations.length === 0) {
                                alert('No conversations to export');
                                return;
                        }
 
                        const conversationsWithMessages = await Promise.all(
-                               allConversations.map(async (conv) => {
-                                       const messages = await DatabaseService.getConversationMessages(conv.id);
+                               allConversations.map(async (conv: DatabaseConversation) => {
+                                       const messages = await conversationsStore.getConversationMessages(conv.id);
                                        return { conv, messages };
                                })
                        );
@@ -47,7 +46,7 @@
                try {
                        const allData: ExportedConversations = await Promise.all(
                                selectedConversations.map(async (conv) => {
-                                       const messages = await DatabaseService.getConversationMessages(conv.id);
+                                       const messages = await conversationsStore.getConversationMessages(conv.id);
                                        return { conv: $state.snapshot(conv), messages: $state.snapshot(messages) };
                                })
                        );
                                .snapshot(fullImportData)
                                .filter((item) => selectedIds.has(item.conv.id));
 
-                       await DatabaseService.importConversations(selectedData);
-
-                       await conversationsStore.loadConversations();
+                       await conversationsStore.importConversationsData(selectedData);
 
                        importedConversations = selectedConversations;
                        showImportSummary = true;
index 38f3a629ce7009144293ce607761aa4cf5c20f10..dfea47cc90b5583e45e5cd9fdc1a059e67473351 100644 (file)
@@ -3,8 +3,7 @@
        import * as Table from '$lib/components/ui/table';
        import { BadgeModality, CopyToClipboardIcon } from '$lib/components/app';
        import { serverStore } from '$lib/stores/server.svelte';
-       import { modelsStore } from '$lib/stores/models.svelte';
-       import { ChatService } from '$lib/services/chat';
+       import { modelsStore, modelOptions, modelsLoading } from '$lib/stores/models.svelte';
        import { formatFileSize, formatParameters, formatNumber } from '$lib/utils';
 
        interface Props {
 
        let serverProps = $derived(serverStore.props);
        let modelName = $derived(modelsStore.singleModelName);
+       let models = $derived(modelOptions());
+       let isLoadingModels = $derived(modelsLoading());
+
+       // Get the first model for single-model mode display
+       let firstModel = $derived(models[0] ?? null);
 
        // Get modalities from modelStore using the model ID from the first model
-       // For now it supports only for single-model mode, will be extended with further improvements for multi-model functioanlities
        let modalities = $derived.by(() => {
-               if (!modelsData?.data?.[0]?.id) return [];
-
-               return modelsStore.getModelModalitiesArray(modelsData.data[0].id);
+               if (!firstModel?.id) return [];
+               return modelsStore.getModelModalitiesArray(firstModel.id);
        });
 
-       let modelsData = $state<ApiModelListResponse | null>(null);
-       let isLoadingModels = $state(false);
-
-       // Fetch models data when dialog opens
+       // Ensure models are fetched when dialog opens
        $effect(() => {
-               if (open && !modelsData) {
-                       loadModelsData();
+               if (open && models.length === 0) {
+                       modelsStore.fetch();
                }
        });
-
-       async function loadModelsData() {
-               isLoadingModels = true;
-
-               try {
-                       modelsData = await ChatService.getModels();
-               } catch (error) {
-                       console.error('Failed to load models data:', error);
-                       // Set empty data to prevent infinite loading
-                       modelsData = { object: 'list', data: [] };
-               } finally {
-                       isLoadingModels = false;
-               }
-       }
 </script>
 
 <Dialog.Root bind:open {onOpenChange}>
@@ -70,8 +55,8 @@
                                <div class="flex items-center justify-center py-8">
                                        <div class="text-sm text-muted-foreground">Loading model information...</div>
                                </div>
-                       {:else if modelsData && modelsData.data.length > 0}
-                               {@const modelMeta = modelsData.data[0].meta}
+                       {:else if firstModel}
+                               {@const modelMeta = firstModel.meta}
 
                                {#if serverProps}
                                        <Table.Root>
index 70b18c8a00c747f16a84e5340dfa64ec3f8aff74..a6a68124035637c2be38705cbe60cb0bb4e598cf 100644 (file)
@@ -677,48 +677,6 @@ export class ChatService {
        // Utilities
        // ─────────────────────────────────────────────────────────────────────────────
 
-       /**
-        * Get server properties - static method for API compatibility (to be refactored)
-        */
-       static async getServerProps(): Promise<ApiLlamaCppServerProps> {
-               try {
-                       const response = await fetch(`./props`, {
-                               headers: getJsonHeaders()
-                       });
-
-                       if (!response.ok) {
-                               throw new Error(`Failed to fetch server props: ${response.status}`);
-                       }
-
-                       const data = await response.json();
-                       return data;
-               } catch (error) {
-                       console.error('Error fetching server props:', error);
-                       throw error;
-               }
-       }
-
-       /**
-        * Get model information from /models endpoint (to be refactored)
-        */
-       static async getModels(): Promise<ApiModelListResponse> {
-               try {
-                       const response = await fetch(`./models`, {
-                               headers: getJsonHeaders()
-                       });
-
-                       if (!response.ok) {
-                               throw new Error(`Failed to fetch models: ${response.status} ${response.statusText}`);
-                       }
-
-                       const data = await response.json();
-                       return data;
-               } catch (error) {
-                       console.error('Error fetching models:', error);
-                       throw error;
-               }
-       }
-
        /**
         * Injects a system message at the beginning of the conversation if provided.
         * Checks for existing system messages to avoid duplication.
index f031bd74975f5b1f16156138e365f3e3bd41d9bc..eecb7fa26287be378a78b78cbc2b10977b0355ee 100644 (file)
@@ -7,7 +7,7 @@ import { getJsonHeaders } from '$lib/utils';
  *
  * This service handles communication with model-related endpoints:
  * - `/v1/models` - OpenAI-compatible model list (MODEL + ROUTER mode)
- * - `/models` - Router-specific model management (ROUTER mode only)
+ * - `/models/load`, `/models/unload` - Router-specific model management (ROUTER mode only)
  *
  * **Responsibilities:**
  * - List available models
@@ -43,7 +43,7 @@ export class ModelsService {
         * Returns models with load status, paths, and other metadata
         */
        static async listRouter(): Promise<ApiRouterModelsListResponse> {
-               const response = await fetch(`${base}/models`, {
+               const response = await fetch(`${base}/v1/models`, {
                        headers: getJsonHeaders()
                });
 
index 44ef36d6ee5172ebd8380251a60de3a02aa25fe1..f766561971c051e3e2162d1d46e4f6ca4606aebe 100644 (file)
@@ -519,6 +519,19 @@ class ConversationsStore {
                return await DatabaseService.getConversationMessages(convId);
        }
 
+       /**
+        * Imports conversations from provided data (without file picker)
+        * @param data - Array of conversation data with messages
+        * @returns Import result with counts
+        */
+       async importConversationsData(
+               data: ExportedConversations
+       ): Promise<{ imported: number; skipped: number }> {
+               const result = await DatabaseService.importConversations(data);
+               await this.loadConversations();
+               return result;
+       }
+
        /**
         * Adds a message to the active messages array
         * Used by chatStore when creating new messages
index 5140995eea48bb1c8392c21fb4ab9b36dd7fba9f..2b7d8db10219b958a628895715c558d84277fc24 100644 (file)
@@ -370,6 +370,10 @@ class SettingsStore {
                return { ...this.config };
        }
 
+       canSyncParameter(key: string): boolean {
+               return ParameterSyncService.canSyncParameter(key);
+       }
+
        /**
         * Get parameter information including source for a specific parameter
         */