Use OpenAI-compatible `/v1/models` endpoint by default (#17689)
* 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
This commit is contained in:
parent
41c5e02f42
commit
e9f9483464
Binary file not shown.
|
|
@ -15,7 +15,7 @@ sequenceDiagram
|
||||||
Stores->>DB: load conversations
|
Stores->>DB: load conversations
|
||||||
Stores->>API: GET /props
|
Stores->>API: GET /props
|
||||||
API-->>Stores: {role: "router"}
|
API-->>Stores: {role: "router"}
|
||||||
Stores->>API: GET /models
|
Stores->>API: GET /v1/models
|
||||||
API-->>Stores: models[] with status (loaded/available)
|
API-->>Stores: models[] with status (loaded/available)
|
||||||
loop each loaded model
|
loop each loaded model
|
||||||
Stores->>API: GET /props?model=X
|
Stores->>API: GET /props?model=X
|
||||||
|
|
@ -28,7 +28,7 @@ sequenceDiagram
|
||||||
alt model not loaded
|
alt model not loaded
|
||||||
Stores->>API: POST /models/load
|
Stores->>API: POST /models/load
|
||||||
loop poll status
|
loop poll status
|
||||||
Stores->>API: GET /models
|
Stores->>API: GET /v1/models
|
||||||
API-->>Stores: check if loaded
|
API-->>Stores: check if loaded
|
||||||
end
|
end
|
||||||
Stores->>API: GET /props?model=X
|
Stores->>API: GET /props?model=X
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ sequenceDiagram
|
||||||
UI->>modelsStore: fetchRouterModels()
|
UI->>modelsStore: fetchRouterModels()
|
||||||
activate modelsStore
|
activate modelsStore
|
||||||
modelsStore->>ModelsSvc: listRouter()
|
modelsStore->>ModelsSvc: listRouter()
|
||||||
ModelsSvc->>API: GET /models
|
ModelsSvc->>API: GET /v1/models
|
||||||
API-->>ModelsSvc: ApiRouterModelsListResponse
|
API-->>ModelsSvc: ApiRouterModelsListResponse
|
||||||
Note right of API: {data: [{id, status, path, in_cache}]}
|
Note right of API: {data: [{id, status, path, in_cache}]}
|
||||||
modelsStore->>modelsStore: routerModels = $state(data)
|
modelsStore->>modelsStore: routerModels = $state(data)
|
||||||
|
|
@ -132,7 +132,7 @@ sequenceDiagram
|
||||||
loop poll every 500ms (max 60 attempts)
|
loop poll every 500ms (max 60 attempts)
|
||||||
modelsStore->>modelsStore: fetchRouterModels()
|
modelsStore->>modelsStore: fetchRouterModels()
|
||||||
modelsStore->>ModelsSvc: listRouter()
|
modelsStore->>ModelsSvc: listRouter()
|
||||||
ModelsSvc->>API: GET /models
|
ModelsSvc->>API: GET /v1/models
|
||||||
API-->>ModelsSvc: models[]
|
API-->>ModelsSvc: models[]
|
||||||
modelsStore->>modelsStore: getModelStatus(modelId)
|
modelsStore->>modelsStore: getModelStatus(modelId)
|
||||||
alt status === LOADED
|
alt status === LOADED
|
||||||
|
|
@ -165,7 +165,7 @@ sequenceDiagram
|
||||||
modelsStore->>modelsStore: pollForModelStatus(modelId, UNLOADED)
|
modelsStore->>modelsStore: pollForModelStatus(modelId, UNLOADED)
|
||||||
loop poll until unloaded
|
loop poll until unloaded
|
||||||
modelsStore->>ModelsSvc: listRouter()
|
modelsStore->>ModelsSvc: listRouter()
|
||||||
ModelsSvc->>API: GET /models
|
ModelsSvc->>API: GET /v1/models
|
||||||
end
|
end
|
||||||
|
|
||||||
modelsStore->>modelsStore: modelLoadingStates.set(modelId, false)
|
modelsStore->>modelsStore: modelLoadingStates.set(modelId, false)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ChatMessage } from '$lib/components/app';
|
import { ChatMessage } from '$lib/components/app';
|
||||||
import { DatabaseService } from '$lib/services/database';
|
|
||||||
import { chatStore } from '$lib/stores/chat.svelte';
|
import { chatStore } from '$lib/stores/chat.svelte';
|
||||||
import { conversationsStore, activeConversation } from '$lib/stores/conversations.svelte';
|
import { conversationsStore, activeConversation } from '$lib/stores/conversations.svelte';
|
||||||
import { getMessageSiblings } from '$lib/utils';
|
import { getMessageSiblings } from '$lib/utils';
|
||||||
|
|
@ -19,7 +18,7 @@
|
||||||
const conversation = activeConversation();
|
const conversation = activeConversation();
|
||||||
|
|
||||||
if (conversation) {
|
if (conversation) {
|
||||||
DatabaseService.getConversationMessages(conversation.id).then((messages) => {
|
conversationsStore.getConversationMessages(conversation.id).then((messages) => {
|
||||||
allConversationMessages = messages;
|
allConversationMessages = messages;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
import { Textarea } from '$lib/components/ui/textarea';
|
import { Textarea } from '$lib/components/ui/textarea';
|
||||||
import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config';
|
import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config';
|
||||||
import { settingsStore } from '$lib/stores/settings.svelte';
|
import { settingsStore } from '$lib/stores/settings.svelte';
|
||||||
import { ParameterSyncService } from '$lib/services/parameter-sync';
|
|
||||||
import { ChatSettingsParameterSourceIndicator } from '$lib/components/app';
|
import { ChatSettingsParameterSourceIndicator } from '$lib/components/app';
|
||||||
import type { Component } from 'svelte';
|
import type { Component } from 'svelte';
|
||||||
|
|
||||||
|
|
@ -22,7 +21,7 @@
|
||||||
|
|
||||||
// Helper function to get parameter source info for syncable parameters
|
// Helper function to get parameter source info for syncable parameters
|
||||||
function getParameterSourceInfo(key: string) {
|
function getParameterSourceInfo(key: string) {
|
||||||
if (!ParameterSyncService.canSyncParameter(key)) {
|
if (!settingsStore.canSyncParameter(key)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@
|
||||||
import { Download, Upload } from '@lucide/svelte';
|
import { Download, Upload } from '@lucide/svelte';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { DialogConversationSelection } from '$lib/components/app';
|
import { DialogConversationSelection } from '$lib/components/app';
|
||||||
import { DatabaseService } from '$lib/services/database';
|
|
||||||
import { createMessageCountMap } from '$lib/utils';
|
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 exportedConversations = $state<DatabaseConversation[]>([]);
|
||||||
let importedConversations = $state<DatabaseConversation[]>([]);
|
let importedConversations = $state<DatabaseConversation[]>([]);
|
||||||
|
|
@ -21,15 +20,15 @@
|
||||||
|
|
||||||
async function handleExportClick() {
|
async function handleExportClick() {
|
||||||
try {
|
try {
|
||||||
const allConversations = await DatabaseService.getAllConversations();
|
const allConversations = conversations();
|
||||||
if (allConversations.length === 0) {
|
if (allConversations.length === 0) {
|
||||||
alert('No conversations to export');
|
alert('No conversations to export');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversationsWithMessages = await Promise.all(
|
const conversationsWithMessages = await Promise.all(
|
||||||
allConversations.map(async (conv) => {
|
allConversations.map(async (conv: DatabaseConversation) => {
|
||||||
const messages = await DatabaseService.getConversationMessages(conv.id);
|
const messages = await conversationsStore.getConversationMessages(conv.id);
|
||||||
return { conv, messages };
|
return { conv, messages };
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -47,7 +46,7 @@
|
||||||
try {
|
try {
|
||||||
const allData: ExportedConversations = await Promise.all(
|
const allData: ExportedConversations = await Promise.all(
|
||||||
selectedConversations.map(async (conv) => {
|
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) };
|
return { conv: $state.snapshot(conv), messages: $state.snapshot(messages) };
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -135,9 +134,7 @@
|
||||||
.snapshot(fullImportData)
|
.snapshot(fullImportData)
|
||||||
.filter((item) => selectedIds.has(item.conv.id));
|
.filter((item) => selectedIds.has(item.conv.id));
|
||||||
|
|
||||||
await DatabaseService.importConversations(selectedData);
|
await conversationsStore.importConversationsData(selectedData);
|
||||||
|
|
||||||
await conversationsStore.loadConversations();
|
|
||||||
|
|
||||||
importedConversations = selectedConversations;
|
importedConversations = selectedConversations;
|
||||||
showImportSummary = true;
|
showImportSummary = true;
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@
|
||||||
import * as Table from '$lib/components/ui/table';
|
import * as Table from '$lib/components/ui/table';
|
||||||
import { BadgeModality, CopyToClipboardIcon } from '$lib/components/app';
|
import { BadgeModality, CopyToClipboardIcon } from '$lib/components/app';
|
||||||
import { serverStore } from '$lib/stores/server.svelte';
|
import { serverStore } from '$lib/stores/server.svelte';
|
||||||
import { modelsStore } from '$lib/stores/models.svelte';
|
import { modelsStore, modelOptions, modelsLoading } from '$lib/stores/models.svelte';
|
||||||
import { ChatService } from '$lib/services/chat';
|
|
||||||
import { formatFileSize, formatParameters, formatNumber } from '$lib/utils';
|
import { formatFileSize, formatParameters, formatNumber } from '$lib/utils';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -16,38 +15,24 @@
|
||||||
|
|
||||||
let serverProps = $derived(serverStore.props);
|
let serverProps = $derived(serverStore.props);
|
||||||
let modelName = $derived(modelsStore.singleModelName);
|
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
|
// 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(() => {
|
let modalities = $derived.by(() => {
|
||||||
if (!modelsData?.data?.[0]?.id) return [];
|
if (!firstModel?.id) return [];
|
||||||
|
return modelsStore.getModelModalitiesArray(firstModel.id);
|
||||||
return modelsStore.getModelModalitiesArray(modelsData.data[0].id);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let modelsData = $state<ApiModelListResponse | null>(null);
|
// Ensure models are fetched when dialog opens
|
||||||
let isLoadingModels = $state(false);
|
|
||||||
|
|
||||||
// Fetch models data when dialog opens
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (open && !modelsData) {
|
if (open && models.length === 0) {
|
||||||
loadModelsData();
|
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>
|
</script>
|
||||||
|
|
||||||
<Dialog.Root bind:open {onOpenChange}>
|
<Dialog.Root bind:open {onOpenChange}>
|
||||||
|
|
@ -70,8 +55,8 @@
|
||||||
<div class="flex items-center justify-center py-8">
|
<div class="flex items-center justify-center py-8">
|
||||||
<div class="text-sm text-muted-foreground">Loading model information...</div>
|
<div class="text-sm text-muted-foreground">Loading model information...</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if modelsData && modelsData.data.length > 0}
|
{:else if firstModel}
|
||||||
{@const modelMeta = modelsData.data[0].meta}
|
{@const modelMeta = firstModel.meta}
|
||||||
|
|
||||||
{#if serverProps}
|
{#if serverProps}
|
||||||
<Table.Root>
|
<Table.Root>
|
||||||
|
|
|
||||||
|
|
@ -677,48 +677,6 @@ export class ChatService {
|
||||||
// Utilities
|
// 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.
|
* Injects a system message at the beginning of the conversation if provided.
|
||||||
* Checks for existing system messages to avoid duplication.
|
* Checks for existing system messages to avoid duplication.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { getJsonHeaders } from '$lib/utils';
|
||||||
*
|
*
|
||||||
* This service handles communication with model-related endpoints:
|
* This service handles communication with model-related endpoints:
|
||||||
* - `/v1/models` - OpenAI-compatible model list (MODEL + ROUTER mode)
|
* - `/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:**
|
* **Responsibilities:**
|
||||||
* - List available models
|
* - List available models
|
||||||
|
|
@ -43,7 +43,7 @@ export class ModelsService {
|
||||||
* Returns models with load status, paths, and other metadata
|
* Returns models with load status, paths, and other metadata
|
||||||
*/
|
*/
|
||||||
static async listRouter(): Promise<ApiRouterModelsListResponse> {
|
static async listRouter(): Promise<ApiRouterModelsListResponse> {
|
||||||
const response = await fetch(`${base}/models`, {
|
const response = await fetch(`${base}/v1/models`, {
|
||||||
headers: getJsonHeaders()
|
headers: getJsonHeaders()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -519,6 +519,19 @@ class ConversationsStore {
|
||||||
return await DatabaseService.getConversationMessages(convId);
|
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
|
* Adds a message to the active messages array
|
||||||
* Used by chatStore when creating new messages
|
* Used by chatStore when creating new messages
|
||||||
|
|
|
||||||
|
|
@ -370,6 +370,10 @@ class SettingsStore {
|
||||||
return { ...this.config };
|
return { ...this.config };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canSyncParameter(key: string): boolean {
|
||||||
|
return ParameterSyncService.canSyncParameter(key);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get parameter information including source for a specific parameter
|
* Get parameter information including source for a specific parameter
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue