refactor: Server store
This commit is contained in:
parent
456828b365
commit
d6ee3d133a
|
|
@ -16,7 +16,7 @@
|
|||
supportsVision,
|
||||
fetchModelProps,
|
||||
getModelProps
|
||||
} from '$lib/stores/props.svelte';
|
||||
} from '$lib/stores/server.svelte';
|
||||
import { getConversationModel } from '$lib/stores/chat.svelte';
|
||||
import { activeMessages } from '$lib/stores/conversations.svelte';
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
isRouterMode,
|
||||
fetchModelProps,
|
||||
getModelProps
|
||||
} from '$lib/stores/props.svelte';
|
||||
} from '$lib/stores/server.svelte';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
import { modelOptions, selectedModelId, selectModelByName } from '$lib/stores/models.svelte';
|
||||
import { getConversationModel } from '$lib/stores/chat.svelte';
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
import { INPUT_CLASSES } from '$lib/constants/input-classes';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
import { isRouterMode } from '$lib/stores/props.svelte';
|
||||
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||
import { selectModel } from '$lib/stores/models.svelte';
|
||||
import { copyToClipboard } from '$lib/utils/copy';
|
||||
import type { ApiChatCompletionToolCall } from '$lib/types/api';
|
||||
|
|
|
|||
|
|
@ -32,13 +32,13 @@
|
|||
import {
|
||||
supportsVision,
|
||||
supportsAudio,
|
||||
propsLoading,
|
||||
propsError,
|
||||
propsStore,
|
||||
serverLoading,
|
||||
serverError,
|
||||
serverStore,
|
||||
isRouterMode,
|
||||
fetchModelProps,
|
||||
getModelProps
|
||||
} from '$lib/stores/props.svelte';
|
||||
} from '$lib/stores/server.svelte';
|
||||
import { modelOptions, selectedModelId } from '$lib/stores/models.svelte';
|
||||
import { getConversationModel } from '$lib/stores/chat.svelte';
|
||||
import { parseFilesToMessageExtras } from '$lib/utils/convert-files-to-extra';
|
||||
|
|
@ -87,8 +87,8 @@
|
|||
);
|
||||
|
||||
let activeErrorDialog = $derived(errorDialog());
|
||||
let isServerLoading = $derived(propsLoading());
|
||||
let hasPropsError = $derived(!!propsError());
|
||||
let isServerLoading = $derived(serverLoading());
|
||||
let hasPropsError = $derived(!!serverError());
|
||||
|
||||
let isCurrentConversationLoading = $derived(isLoading());
|
||||
|
||||
|
|
@ -407,10 +407,10 @@
|
|||
<div class="flex items-center gap-2">
|
||||
<AlertTriangle class="h-4 w-4 text-destructive" />
|
||||
<span class="text-sm font-medium text-destructive">Server unavailable</span>
|
||||
<span class="text-sm text-muted-foreground">— {propsError()}</span>
|
||||
<span class="text-sm text-muted-foreground">— {serverError()}</span>
|
||||
</div>
|
||||
<button
|
||||
onclick={() => propsStore.fetch()}
|
||||
onclick={() => serverStore.fetch()}
|
||||
disabled={isServerLoading}
|
||||
class="flex items-center gap-1.5 rounded-lg bg-destructive/20 px-3 py-1.5 text-xs font-medium text-destructive hover:bg-destructive/30 disabled:opacity-50"
|
||||
>
|
||||
|
|
@ -454,7 +454,7 @@
|
|||
<h1 class="mb-4 text-3xl font-semibold tracking-tight">llama.cpp</h1>
|
||||
|
||||
<p class="text-lg text-muted-foreground">
|
||||
{propsStore.serverProps?.modalities?.audio
|
||||
{serverStore.props?.modalities?.audio
|
||||
? 'Record audio, type a message '
|
||||
: 'Type a message'} or upload files to get started
|
||||
</p>
|
||||
|
|
@ -467,10 +467,10 @@
|
|||
<div class="flex items-center gap-2">
|
||||
<AlertTriangle class="h-4 w-4 text-destructive" />
|
||||
<span class="text-sm font-medium text-destructive">Server unavailable</span>
|
||||
<span class="text-sm text-muted-foreground">— {propsError()}</span>
|
||||
<span class="text-sm text-muted-foreground">— {serverError()}</span>
|
||||
</div>
|
||||
<button
|
||||
onclick={() => propsStore.fetch()}
|
||||
onclick={() => serverStore.fetch()}
|
||||
disabled={isServerLoading}
|
||||
class="flex items-center gap-1.5 rounded-lg bg-destructive/20 px-3 py-1.5 text-xs font-medium text-destructive hover:bg-destructive/30 disabled:opacity-50"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import * as Select from '$lib/components/ui/select';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config';
|
||||
import { supportsVision } from '$lib/stores/props.svelte';
|
||||
import { supportsVision } from '$lib/stores/server.svelte';
|
||||
import { getParameterInfo, resetParameterToServerDefault } from '$lib/stores/settings.svelte';
|
||||
import { ParameterSyncService } from '$lib/services/parameter-sync';
|
||||
import { ChatSettingsParameterSourceIndicator } from '$lib/components/app';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { BadgeModality, CopyToClipboardIcon } from '$lib/components/app';
|
||||
import { propsStore } from '$lib/stores/props.svelte';
|
||||
import { serverStore } from '$lib/stores/server.svelte';
|
||||
import { ChatService } from '$lib/services/chat';
|
||||
import type { ApiModelListResponse } from '$lib/types/api';
|
||||
import { formatFileSize, formatParameters, formatNumber } from '$lib/utils/formatters';
|
||||
|
|
@ -14,8 +14,8 @@
|
|||
|
||||
let { open = $bindable(), onOpenChange }: Props = $props();
|
||||
|
||||
let serverProps = $derived(propsStore.serverProps);
|
||||
let modalities = $derived(propsStore.supportedModalities);
|
||||
let serverProps = $derived(serverStore.props);
|
||||
let modalities = $derived(serverStore.supportedModalities);
|
||||
|
||||
let modelsData = $state<ApiModelListResponse | null>(null);
|
||||
let isLoadingModels = $state(false);
|
||||
|
|
@ -77,12 +77,12 @@
|
|||
class="resizable-text-container min-w-0 flex-1 truncate"
|
||||
style:--threshold="12rem"
|
||||
>
|
||||
{propsStore.modelName}
|
||||
{serverStore.modelName}
|
||||
</span>
|
||||
|
||||
<CopyToClipboardIcon
|
||||
text={propsStore.modelName || ''}
|
||||
canCopy={!!propsStore.modelName}
|
||||
text={serverStore.modelName || ''}
|
||||
canCopy={!!serverStore.modelName}
|
||||
ariaLabel="Copy model name to clipboard"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Package } from '@lucide/svelte';
|
||||
import { BadgeInfo, CopyToClipboardIcon } from '$lib/components/app';
|
||||
import { propsStore } from '$lib/stores/props.svelte';
|
||||
import { serverStore } from '$lib/stores/server.svelte';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { TOOLTIP_DELAY_DURATION } from '$lib/constants/tooltip-config';
|
||||
|
||||
|
|
@ -21,8 +21,8 @@
|
|||
showTooltip = false
|
||||
}: Props = $props();
|
||||
|
||||
let model = $derived(modelProp || propsStore.modelName);
|
||||
let isModelMode = $derived(propsStore.isModelMode);
|
||||
let model = $derived(modelProp || serverStore.modelName);
|
||||
let isModelMode = $derived(serverStore.isModelMode);
|
||||
</script>
|
||||
|
||||
{#snippet badgeContent()}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
loadModel
|
||||
} from '$lib/stores/models.svelte';
|
||||
import { ServerModelStatus } from '$lib/enums';
|
||||
import { isRouterMode, propsStore } from '$lib/stores/props.svelte';
|
||||
import { isRouterMode, serverStore } from '$lib/stores/server.svelte';
|
||||
import { DialogModelInformation } from '$lib/components/app';
|
||||
import type { ModelOption } from '$lib/types/models';
|
||||
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
let updating = $derived(modelsUpdating());
|
||||
let activeId = $derived(selectedModelId());
|
||||
let isRouter = $derived(isRouterMode());
|
||||
let serverModel = $derived(propsStore.modelName);
|
||||
let serverModel = $derived(serverStore.modelName);
|
||||
|
||||
// Reactive router models state - needed for proper reactivity of status checks
|
||||
let currentRouterModels = $derived(routerModels());
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import { propsStore, propsLoading } from '$lib/stores/props.svelte';
|
||||
import { serverStore, serverLoading } from '$lib/stores/server.svelte';
|
||||
import { config, updateConfig } from '$lib/stores/settings.svelte';
|
||||
import { fade, fly, scale } from 'svelte/transition';
|
||||
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
showTroubleshooting = false
|
||||
}: Props = $props();
|
||||
|
||||
let isServerLoading = $derived(propsLoading());
|
||||
let isServerLoading = $derived(serverLoading());
|
||||
let isAccessDeniedError = $derived(
|
||||
error.toLowerCase().includes('access denied') ||
|
||||
error.toLowerCase().includes('invalid api key') ||
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
if (onRetry) {
|
||||
onRetry();
|
||||
} else {
|
||||
propsStore.fetch();
|
||||
serverStore.fetch();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { AlertTriangle, Server } from '@lucide/svelte';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { serverProps, propsLoading, propsError, modelName } from '$lib/stores/props.svelte';
|
||||
import { serverProps, serverLoading, serverError, modelName } from '$lib/stores/server.svelte';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
let { class: className = '', showActions = false }: Props = $props();
|
||||
|
||||
let error = $derived(propsError());
|
||||
let loading = $derived(propsLoading());
|
||||
let error = $derived(serverError());
|
||||
let loading = $derived(serverLoading());
|
||||
let model = $derived(modelName());
|
||||
let serverData = $derived(serverProps());
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { config } from '$lib/stores/settings.svelte';
|
||||
import { getJsonHeaders } from '$lib/utils/api-headers';
|
||||
import { selectedModelName } from '$lib/stores/models.svelte';
|
||||
import { isRouterMode, propsStore } from '$lib/stores/props.svelte';
|
||||
import { isRouterMode, serverStore } from '$lib/stores/server.svelte';
|
||||
import type {
|
||||
ApiChatCompletionRequest,
|
||||
ApiChatCompletionResponse,
|
||||
|
|
@ -792,7 +792,7 @@ export class ChatService {
|
|||
* Handles various response formats including streaming chunks and final responses.
|
||||
*
|
||||
* WORKAROUND: In single model mode, llama-server returns a default/incorrect model name
|
||||
* in the response. We override it with the actual model name from propsStore.
|
||||
* in the response. We override it with the actual model name from serverStore.
|
||||
*
|
||||
* @param data - Raw response data from the Chat Completions API
|
||||
* @returns Model name string if found, undefined otherwise
|
||||
|
|
@ -803,7 +803,7 @@ export class ChatService {
|
|||
// because llama-server returns `gpt-3.5-turbo` value in the `model` field
|
||||
const isRouter = isRouterMode();
|
||||
if (!isRouter) {
|
||||
const propsModelName = propsStore.modelName;
|
||||
const propsModelName = serverStore.modelName;
|
||||
if (propsModelName) {
|
||||
return propsModelName;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { DatabaseService } from '$lib/services/database';
|
|||
import { chatService } from '$lib/services';
|
||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { config } from '$lib/stores/settings.svelte';
|
||||
import { contextSize } from '$lib/stores/props.svelte';
|
||||
import { contextSize } from '$lib/stores/server.svelte';
|
||||
import { normalizeModelName } from '$lib/utils/model-names';
|
||||
import { filterByLeafNodeId, findDescendantMessages, findLeafNode } from '$lib/utils/branching';
|
||||
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import { ModelsService } from '$lib/services/models';
|
||||
import { ServerModelStatus } from '$lib/enums';
|
||||
import { propsStore } from '$lib/stores/props.svelte';
|
||||
import { serverStore } from '$lib/stores/server.svelte';
|
||||
import type { ModelOption, ModelModalities } from '$lib/types/models';
|
||||
import type { ApiModelDataEntry } from '$lib/types/api';
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ import type { ApiModelDataEntry } from '$lib/types/api';
|
|||
* **Architecture & Relationships:**
|
||||
* - **ModelsService**: Stateless service for API communication
|
||||
* - **ModelsStore** (this class): Reactive store for model state
|
||||
* - **PropsStore**: Provides server mode detection
|
||||
* - **ServerStore**: Provides server mode detection
|
||||
* - **ConversationsStore**: Tracks which conversations use which models
|
||||
*
|
||||
* **Key Features:**
|
||||
|
|
@ -32,121 +32,62 @@ class ModelsStore {
|
|||
// State
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private _models = $state<ModelOption[]>([]);
|
||||
private _routerModels = $state<ApiModelDataEntry[]>([]);
|
||||
private _loading = $state(false);
|
||||
private _updating = $state(false);
|
||||
private _error = $state<string | null>(null);
|
||||
private _selectedModelId = $state<string | null>(null);
|
||||
private _selectedModelName = $state<string | null>(null);
|
||||
models = $state<ModelOption[]>([]);
|
||||
routerModels = $state<ApiModelDataEntry[]>([]);
|
||||
loading = $state(false);
|
||||
updating = $state(false);
|
||||
error = $state<string | null>(null);
|
||||
selectedModelId = $state<string | null>(null);
|
||||
selectedModelName = $state<string | null>(null);
|
||||
|
||||
/** Maps modelId -> Set of conversationIds that use this model */
|
||||
private _modelUsage = $state<Map<string, SvelteSet<string>>>(new Map());
|
||||
|
||||
/** Maps modelId -> loading state for load/unload operations */
|
||||
private _modelLoadingStates = $state<Map<string, boolean>>(new Map());
|
||||
private modelUsage = $state<Map<string, SvelteSet<string>>>(new Map());
|
||||
private modelLoadingStates = $state<Map<string, boolean>>(new Map());
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Getters - Basic
|
||||
// Computed Getters
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
get models(): ModelOption[] {
|
||||
return this._models;
|
||||
}
|
||||
|
||||
get routerModels(): ApiModelDataEntry[] {
|
||||
return this._routerModels;
|
||||
}
|
||||
|
||||
get loading(): boolean {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
get updating(): boolean {
|
||||
return this._updating;
|
||||
}
|
||||
|
||||
get error(): string | null {
|
||||
return this._error;
|
||||
}
|
||||
|
||||
get selectedModelId(): string | null {
|
||||
return this._selectedModelId;
|
||||
}
|
||||
|
||||
get selectedModelName(): string | null {
|
||||
return this._selectedModelName;
|
||||
}
|
||||
|
||||
get selectedModel(): ModelOption | null {
|
||||
if (!this._selectedModelId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._models.find((model) => model.id === this._selectedModelId) ?? null;
|
||||
if (!this.selectedModelId) return null;
|
||||
return this.models.find((model) => model.id === this.selectedModelId) ?? null;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Getters - Loaded Models (ROUTER mode)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get list of currently loaded model IDs
|
||||
*/
|
||||
get loadedModelIds(): string[] {
|
||||
return this._routerModels
|
||||
return this.routerModels
|
||||
.filter((m) => m.status.value === ServerModelStatus.LOADED)
|
||||
.map((m) => m.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of models currently being loaded/unloaded
|
||||
*/
|
||||
get loadingModelIds(): string[] {
|
||||
return Array.from(this._modelLoadingStates.entries())
|
||||
return Array.from(this.modelLoadingStates.entries())
|
||||
.filter(([, loading]) => loading)
|
||||
.map(([id]) => id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific model is loaded
|
||||
*/
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Methods - Model Status
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
isModelLoaded(modelId: string): boolean {
|
||||
const model = this._routerModels.find((m) => m.name === modelId);
|
||||
const model = this.routerModels.find((m) => m.name === modelId);
|
||||
return model?.status.value === ServerModelStatus.LOADED || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific model is currently loading/unloading
|
||||
*/
|
||||
isModelOperationInProgress(modelId: string): boolean {
|
||||
return this._modelLoadingStates.get(modelId) ?? false;
|
||||
return this.modelLoadingStates.get(modelId) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of a specific model
|
||||
*/
|
||||
getModelStatus(modelId: string): ServerModelStatus | null {
|
||||
const model = this._routerModels.find((m) => m.name === modelId);
|
||||
const model = this.routerModels.find((m) => m.name === modelId);
|
||||
return model?.status.value ?? null;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Getters - Model Usage
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get set of conversation IDs using a specific model
|
||||
*/
|
||||
getModelUsage(modelId: string): SvelteSet<string> {
|
||||
return this._modelUsage.get(modelId) ?? new SvelteSet<string>();
|
||||
return this.modelUsage.get(modelId) ?? new SvelteSet<string>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a model is used by any conversation
|
||||
*/
|
||||
isModelInUse(modelId: string): boolean {
|
||||
const usage = this._modelUsage.get(modelId);
|
||||
const usage = this.modelUsage.get(modelId);
|
||||
return usage !== undefined && usage.size > 0;
|
||||
}
|
||||
|
||||
|
|
@ -158,11 +99,11 @@ class ModelsStore {
|
|||
* Fetch list of models from server
|
||||
*/
|
||||
async fetch(force = false): Promise<void> {
|
||||
if (this._loading) return;
|
||||
if (this._models.length > 0 && !force) return;
|
||||
if (this.loading) return;
|
||||
if (this.models.length > 0 && !force) return;
|
||||
|
||||
this._loading = true;
|
||||
this._error = null;
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await ModelsService.list();
|
||||
|
|
@ -185,18 +126,13 @@ class ModelsStore {
|
|||
} satisfies ModelOption;
|
||||
});
|
||||
|
||||
this._models = models;
|
||||
|
||||
// Don't auto-select any model - selection should come from:
|
||||
// 1. User explicitly selecting a model in the UI
|
||||
// 2. Conversation model (synced via ChatFormActions effect)
|
||||
this.models = models;
|
||||
} catch (error) {
|
||||
this._models = [];
|
||||
this._error = error instanceof Error ? error.message : 'Failed to load models';
|
||||
|
||||
this.models = [];
|
||||
this.error = error instanceof Error ? error.message : 'Failed to load models';
|
||||
throw error;
|
||||
} finally {
|
||||
this._loading = false;
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,13 +143,11 @@ class ModelsStore {
|
|||
async fetchRouterModels(): Promise<void> {
|
||||
try {
|
||||
const response = await ModelsService.listRouter();
|
||||
this._routerModels = response.data;
|
||||
|
||||
// Fetch modalities for loaded models
|
||||
this.routerModels = response.data;
|
||||
await this.fetchModalitiesForLoadedModels();
|
||||
} catch (error) {
|
||||
console.warn('Failed to fetch router models:', error);
|
||||
this._routerModels = [];
|
||||
this.routerModels = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -226,13 +160,13 @@ class ModelsStore {
|
|||
if (loadedModelIds.length === 0) return;
|
||||
|
||||
// Fetch props for each loaded model in parallel
|
||||
const propsPromises = loadedModelIds.map((modelId) => propsStore.fetchModelProps(modelId));
|
||||
const propsPromises = loadedModelIds.map((modelId) => serverStore.fetchModelProps(modelId));
|
||||
|
||||
try {
|
||||
const results = await Promise.all(propsPromises);
|
||||
|
||||
// Update models with modalities
|
||||
this._models = this._models.map((model) => {
|
||||
this.models = this.models.map((model) => {
|
||||
const modelIndex = loadedModelIds.indexOf(model.model);
|
||||
if (modelIndex === -1) return model;
|
||||
|
||||
|
|
@ -257,7 +191,7 @@ class ModelsStore {
|
|||
*/
|
||||
async updateModelModalities(modelId: string): Promise<void> {
|
||||
try {
|
||||
const props = await propsStore.fetchModelProps(modelId);
|
||||
const props = await serverStore.fetchModelProps(modelId);
|
||||
if (!props?.modalities) return;
|
||||
|
||||
const modalities: ModelModalities = {
|
||||
|
|
@ -265,7 +199,7 @@ class ModelsStore {
|
|||
audio: props.modalities.audio ?? false
|
||||
};
|
||||
|
||||
this._models = this._models.map((model) =>
|
||||
this.models = this.models.map((model) =>
|
||||
model.model === modelId ? { ...model, modalities } : model
|
||||
);
|
||||
} catch (error) {
|
||||
|
|
@ -281,27 +215,20 @@ class ModelsStore {
|
|||
* Select a model for new conversations
|
||||
*/
|
||||
async select(modelId: string): Promise<void> {
|
||||
if (!modelId || this._updating) {
|
||||
return;
|
||||
}
|
||||
if (!modelId || this.updating) return;
|
||||
if (this.selectedModelId === modelId) return;
|
||||
|
||||
if (this._selectedModelId === modelId) {
|
||||
return;
|
||||
}
|
||||
const option = this.models.find((model) => model.id === modelId);
|
||||
if (!option) throw new Error('Selected model is not available');
|
||||
|
||||
const option = this._models.find((model) => model.id === modelId);
|
||||
if (!option) {
|
||||
throw new Error('Selected model is not available');
|
||||
}
|
||||
|
||||
this._updating = true;
|
||||
this._error = null;
|
||||
this.updating = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
this._selectedModelId = option.id;
|
||||
this._selectedModelName = option.model;
|
||||
this.selectedModelId = option.id;
|
||||
this.selectedModelName = option.model;
|
||||
} finally {
|
||||
this._updating = false;
|
||||
this.updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -310,47 +237,28 @@ class ModelsStore {
|
|||
* @param modelName - Model name to select (e.g., "unsloth/gemma-3-12b-it-GGUF:latest")
|
||||
*/
|
||||
selectModelByName(modelName: string): void {
|
||||
const option = this._models.find((model) => model.model === modelName);
|
||||
const option = this.models.find((model) => model.model === modelName);
|
||||
if (option) {
|
||||
this._selectedModelId = option.id;
|
||||
this._selectedModelName = option.model;
|
||||
// Don't persist - this is just for syncing with conversation
|
||||
this.selectedModelId = option.id;
|
||||
this.selectedModelName = option.model;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the current model selection
|
||||
*/
|
||||
clearSelection(): void {
|
||||
this._selectedModelId = null;
|
||||
this._selectedModelName = null;
|
||||
this.selectedModelId = null;
|
||||
this.selectedModelName = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a model by its model name
|
||||
* @param modelName - Model name to search for (e.g., "unsloth/gemma-3-12b-it-GGUF:latest")
|
||||
* @returns ModelOption if found, null otherwise
|
||||
*/
|
||||
findModelByName(modelName: string): ModelOption | null {
|
||||
return this._models.find((model) => model.model === modelName) ?? null;
|
||||
return this.models.find((model) => model.model === modelName) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a model by its display ID
|
||||
* @param modelId - Model ID to search for
|
||||
* @returns ModelOption if found, null otherwise
|
||||
*/
|
||||
findModelById(modelId: string): ModelOption | null {
|
||||
return this._models.find((model) => model.id === modelId) ?? null;
|
||||
return this.models.find((model) => model.id === modelId) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a model exists by name
|
||||
* @param modelName - Model name to check
|
||||
* @returns true if model exists
|
||||
*/
|
||||
hasModel(modelName: string): boolean {
|
||||
return this._models.some((model) => model.model === modelName);
|
||||
return this.models.some((model) => model.model === modelName);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -414,12 +322,10 @@ class ModelsStore {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this._modelLoadingStates.get(modelId)) {
|
||||
return; // Already loading
|
||||
}
|
||||
if (this.modelLoadingStates.get(modelId)) return;
|
||||
|
||||
this._modelLoadingStates.set(modelId, true);
|
||||
this._error = null;
|
||||
this.modelLoadingStates.set(modelId, true);
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
await ModelsService.load(modelId);
|
||||
|
|
@ -427,13 +333,12 @@ class ModelsStore {
|
|||
// Poll until model is loaded
|
||||
await this.pollForModelStatus(modelId, ServerModelStatus.LOADED);
|
||||
|
||||
// Update modalities for this specific model
|
||||
await this.updateModelModalities(modelId);
|
||||
} catch (error) {
|
||||
this._error = error instanceof Error ? error.message : 'Failed to load model';
|
||||
this.error = error instanceof Error ? error.message : 'Failed to load model';
|
||||
throw error;
|
||||
} finally {
|
||||
this._modelLoadingStates.set(modelId, false);
|
||||
this.modelLoadingStates.set(modelId, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -446,23 +351,20 @@ class ModelsStore {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this._modelLoadingStates.get(modelId)) {
|
||||
return; // Already unloading
|
||||
}
|
||||
if (this.modelLoadingStates.get(modelId)) return;
|
||||
|
||||
this._modelLoadingStates.set(modelId, true);
|
||||
this._error = null;
|
||||
this.modelLoadingStates.set(modelId, true);
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
await ModelsService.unload(modelId);
|
||||
|
||||
// Poll until model is unloaded
|
||||
await this.pollForModelStatus(modelId, ServerModelStatus.UNLOADED);
|
||||
} catch (error) {
|
||||
this._error = error instanceof Error ? error.message : 'Failed to unload model';
|
||||
this.error = error instanceof Error ? error.message : 'Failed to unload model';
|
||||
throw error;
|
||||
} finally {
|
||||
this._modelLoadingStates.set(modelId, false);
|
||||
this.modelLoadingStates.set(modelId, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -486,9 +388,9 @@ class ModelsStore {
|
|||
* Register that a conversation is using a model
|
||||
*/
|
||||
registerModelUsage(modelId: string, conversationId: string): void {
|
||||
const usage = this._modelUsage.get(modelId) ?? new SvelteSet<string>();
|
||||
const usage = this.modelUsage.get(modelId) ?? new SvelteSet<string>();
|
||||
usage.add(conversationId);
|
||||
this._modelUsage.set(modelId, usage);
|
||||
this.modelUsage.set(modelId, usage);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -502,14 +404,11 @@ class ModelsStore {
|
|||
conversationId: string,
|
||||
autoUnload = true
|
||||
): Promise<void> {
|
||||
const usage = this._modelUsage.get(modelId);
|
||||
const usage = this.modelUsage.get(modelId);
|
||||
if (usage) {
|
||||
usage.delete(conversationId);
|
||||
|
||||
if (usage.size === 0) {
|
||||
this._modelUsage.delete(modelId);
|
||||
|
||||
// Auto-unload if model is not used by any conversation
|
||||
this.modelUsage.delete(modelId);
|
||||
if (autoUnload && this.isModelLoaded(modelId)) {
|
||||
await this.unloadModel(modelId);
|
||||
}
|
||||
|
|
@ -521,10 +420,8 @@ class ModelsStore {
|
|||
* Clear all usage for a conversation (when conversation is deleted)
|
||||
*/
|
||||
async clearConversationUsage(conversationId: string): Promise<void> {
|
||||
for (const [modelId, usage] of this._modelUsage.entries()) {
|
||||
if (usage.has(conversationId)) {
|
||||
await this.unregisterModelUsage(modelId, conversationId);
|
||||
}
|
||||
for (const [modelId, usage] of this.modelUsage.entries()) {
|
||||
if (usage.has(conversationId)) await this.unregisterModelUsage(modelId, conversationId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -544,15 +441,15 @@ class ModelsStore {
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
clear(): void {
|
||||
this._models = [];
|
||||
this._routerModels = [];
|
||||
this._loading = false;
|
||||
this._updating = false;
|
||||
this._error = null;
|
||||
this._selectedModelId = null;
|
||||
this._selectedModelName = null;
|
||||
this._modelUsage.clear();
|
||||
this._modelLoadingStates.clear();
|
||||
this.models = [];
|
||||
this.routerModels = [];
|
||||
this.loading = false;
|
||||
this.updating = false;
|
||||
this.error = null;
|
||||
this.selectedModelId = null;
|
||||
this.selectedModelName = null;
|
||||
this.modelUsage.clear();
|
||||
this.modelLoadingStates.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,143 +2,93 @@ import { PropsService } from '$lib/services/props';
|
|||
import { ServerRole, ModelModality } from '$lib/enums';
|
||||
|
||||
/**
|
||||
* PropsStore - Server properties management and mode detection
|
||||
* ServerStore - Server state, capabilities, and mode detection
|
||||
*
|
||||
* This store manages the server properties fetched from the `/props` endpoint.
|
||||
* It provides reactive state for server configuration, capabilities, and mode detection.
|
||||
* This store manages the server connection state and properties fetched from `/props`.
|
||||
* It provides reactive state for server configuration, capabilities, and role detection.
|
||||
*
|
||||
* **Architecture & Relationships:**
|
||||
* - **PropsService**: Stateless service for fetching `/props` data
|
||||
* - **PropsStore** (this class): Reactive store for server properties
|
||||
* - **ModelsStore**: Uses server mode for model management strategy
|
||||
* - **ServerStore** (this class): Reactive store for server state
|
||||
* - **ModelsStore**: Uses server role for model management strategy
|
||||
*
|
||||
* **Key Features:**
|
||||
* - **Server Properties**: Model info, context size, build information
|
||||
* - **Mode Detection**: MODEL (single model) vs ROUTER (multi-model)
|
||||
* - **Server State**: Connection status, loading, error handling
|
||||
* - **Role Detection**: MODEL (single model) vs ROUTER (multi-model)
|
||||
* - **Capability Detection**: Vision and audio modality support
|
||||
* - **Error Handling**: Clear error states when server unavailable
|
||||
* - **Props Cache**: Per-model props caching for ROUTER mode
|
||||
*/
|
||||
class PropsStore {
|
||||
private _serverProps = $state<ApiLlamaCppServerProps | null>(null);
|
||||
private _loading = $state(false);
|
||||
private _error = $state<string | null>(null);
|
||||
private _serverRole = $state<ServerRole | null>(null);
|
||||
class ServerStore {
|
||||
props = $state<ApiLlamaCppServerProps | null>(null);
|
||||
loading = $state(false);
|
||||
error = $state<string | null>(null);
|
||||
role = $state<ServerRole | null>(null);
|
||||
private fetchPromise: Promise<void> | null = null;
|
||||
|
||||
// Model-specific props cache (ROUTER mode)
|
||||
private _modelPropsCache = $state<Map<string, ApiLlamaCppServerProps>>(new Map());
|
||||
private _modelPropsFetching = $state<Set<string>>(new Set());
|
||||
private modelPropsCache = $state<Map<string, ApiLlamaCppServerProps>>(new Map());
|
||||
private modelPropsFetching = $state<Set<string>>(new Set());
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Getters - Server Properties
|
||||
// Computed Getters
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
get serverProps(): ApiLlamaCppServerProps | null {
|
||||
return this._serverProps;
|
||||
}
|
||||
|
||||
get loading(): boolean {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
get error(): string | null {
|
||||
return this._error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get model name from server props.
|
||||
* In MODEL mode: extracts from model_path or model_alias
|
||||
* In ROUTER mode: returns null (model is per-conversation)
|
||||
*/
|
||||
get modelName(): string | null {
|
||||
if (this._serverRole === ServerRole.ROUTER) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this._serverProps?.model_alias) {
|
||||
return this._serverProps.model_alias;
|
||||
}
|
||||
|
||||
if (!this._serverProps?.model_path) return null;
|
||||
return this._serverProps.model_path.split(/(\\|\/)/).pop() || null;
|
||||
if (this.role === ServerRole.ROUTER) return null;
|
||||
if (this.props?.model_alias) return this.props.model_alias;
|
||||
if (!this.props?.model_path) return null;
|
||||
return this.props.model_path.split(/(\\|\/)/).pop() || null;
|
||||
}
|
||||
|
||||
get supportedModalities(): ModelModality[] {
|
||||
const modalities: ModelModality[] = [];
|
||||
if (this._serverProps?.modalities?.audio) {
|
||||
modalities.push(ModelModality.AUDIO);
|
||||
}
|
||||
if (this._serverProps?.modalities?.vision) {
|
||||
modalities.push(ModelModality.VISION);
|
||||
}
|
||||
if (this.props?.modalities?.audio) modalities.push(ModelModality.AUDIO);
|
||||
if (this.props?.modalities?.vision) modalities.push(ModelModality.VISION);
|
||||
return modalities;
|
||||
}
|
||||
|
||||
get supportsVision(): boolean {
|
||||
return this._serverProps?.modalities?.vision ?? false;
|
||||
return this.props?.modalities?.vision ?? false;
|
||||
}
|
||||
|
||||
get supportsAudio(): boolean {
|
||||
return this._serverProps?.modalities?.audio ?? false;
|
||||
return this.props?.modalities?.audio ?? false;
|
||||
}
|
||||
|
||||
get defaultParams(): ApiLlamaCppServerProps['default_generation_settings']['params'] | null {
|
||||
return this._serverProps?.default_generation_settings?.params || null;
|
||||
return this.props?.default_generation_settings?.params || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get context size (n_ctx) from server props
|
||||
*/
|
||||
get contextSize(): number | null {
|
||||
return this._serverProps?.default_generation_settings?.n_ctx ?? null;
|
||||
return this.props?.default_generation_settings?.n_ctx ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if slots endpoint is available (set by --slots flag on server)
|
||||
*/
|
||||
get slotsEndpointAvailable(): boolean {
|
||||
return this._serverProps?.endpoint_slots ?? false;
|
||||
return this.props?.endpoint_slots ?? false;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Getters - Server Mode
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get current server mode
|
||||
*/
|
||||
get serverRole(): ServerRole | null {
|
||||
return this._serverRole;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if server is running in router mode (multi-model management)
|
||||
*/
|
||||
get isRouterMode(): boolean {
|
||||
return this._serverRole === ServerRole.ROUTER;
|
||||
return this.role === ServerRole.ROUTER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if server is running in model mode (single model loaded)
|
||||
*/
|
||||
get isModelMode(): boolean {
|
||||
return this._serverRole === ServerRole.MODEL;
|
||||
return this.role === ServerRole.MODEL;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Server Mode Detection
|
||||
// Server Role Detection
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private detectServerRole(props: ApiLlamaCppServerProps): void {
|
||||
console.log('Server props role:', props?.role);
|
||||
const newMode =
|
||||
// todo - `role` attribute should always be available on the `/props` endpoint
|
||||
props?.role === ServerRole.ROUTER ? ServerRole.ROUTER : ServerRole.MODEL;
|
||||
|
||||
// Only log when mode changes
|
||||
if (this._serverRole !== newMode) {
|
||||
this._serverRole = newMode;
|
||||
console.info(`Server running in ${newMode === ServerRole.ROUTER ? 'ROUTER' : 'MODEL'} mode`);
|
||||
private detectRole(props: ApiLlamaCppServerProps): void {
|
||||
const newRole = props?.role === ServerRole.ROUTER ? ServerRole.ROUTER : ServerRole.MODEL;
|
||||
if (this.role !== newRole) {
|
||||
this.role = newRole;
|
||||
console.info(`Server running in ${newRole === ServerRole.ROUTER ? 'ROUTER' : 'MODEL'} mode`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,18 +96,13 @@ class PropsStore {
|
|||
// Fetch Server Properties
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Fetches server properties from the server
|
||||
*/
|
||||
async fetch(): Promise<void> {
|
||||
if (this.fetchPromise) {
|
||||
return this.fetchPromise;
|
||||
}
|
||||
if (this.fetchPromise) return this.fetchPromise;
|
||||
|
||||
this._loading = true;
|
||||
this._error = null;
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
const previousBuildInfo = this._serverProps?.build_info;
|
||||
const previousBuildInfo = this.props?.build_info;
|
||||
|
||||
const fetchPromise = (async () => {
|
||||
try {
|
||||
|
|
@ -165,18 +110,18 @@ class PropsStore {
|
|||
|
||||
// Clear model-specific props cache if server was restarted
|
||||
if (previousBuildInfo && previousBuildInfo !== props.build_info) {
|
||||
this._modelPropsCache.clear();
|
||||
this.modelPropsCache.clear();
|
||||
console.info('Cleared model props cache due to server restart');
|
||||
}
|
||||
|
||||
this._serverProps = props;
|
||||
this._error = null;
|
||||
this.detectServerRole(props);
|
||||
this.props = props;
|
||||
this.error = null;
|
||||
this.detectRole(props);
|
||||
} catch (error) {
|
||||
this._error = this.getErrorMessage(error);
|
||||
this.error = this.getErrorMessage(error);
|
||||
console.error('Error fetching server properties:', error);
|
||||
} finally {
|
||||
this._loading = false;
|
||||
this.loading = false;
|
||||
this.fetchPromise = null;
|
||||
}
|
||||
})();
|
||||
|
|
@ -189,45 +134,31 @@ class PropsStore {
|
|||
// Fetch Model-Specific Properties (ROUTER mode)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get cached props for a specific model
|
||||
*/
|
||||
getModelProps(modelId: string): ApiLlamaCppServerProps | null {
|
||||
return this._modelPropsCache.get(modelId) ?? null;
|
||||
return this.modelPropsCache.get(modelId) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if model props are being fetched
|
||||
*/
|
||||
isModelPropsFetching(modelId: string): boolean {
|
||||
return this._modelPropsFetching.has(modelId);
|
||||
return this.modelPropsFetching.has(modelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches properties for a specific model (ROUTER mode)
|
||||
* Results are cached for subsequent calls
|
||||
*/
|
||||
async fetchModelProps(modelId: string): Promise<ApiLlamaCppServerProps | null> {
|
||||
// Return cached if available
|
||||
const cached = this._modelPropsCache.get(modelId);
|
||||
const cached = this.modelPropsCache.get(modelId);
|
||||
if (cached) return cached;
|
||||
|
||||
// Don't fetch if already fetching
|
||||
if (this._modelPropsFetching.has(modelId)) {
|
||||
return null;
|
||||
}
|
||||
if (this.modelPropsFetching.has(modelId)) return null;
|
||||
|
||||
this._modelPropsFetching.add(modelId);
|
||||
this.modelPropsFetching.add(modelId);
|
||||
|
||||
try {
|
||||
const props = await PropsService.fetchForModel(modelId);
|
||||
this._modelPropsCache.set(modelId, props);
|
||||
this.modelPropsCache.set(modelId, props);
|
||||
return props;
|
||||
} catch (error) {
|
||||
console.warn(`Failed to fetch props for model ${modelId}:`, error);
|
||||
return null;
|
||||
} finally {
|
||||
this._modelPropsFetching.delete(modelId);
|
||||
this.modelPropsFetching.delete(modelId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -265,42 +196,37 @@ class PropsStore {
|
|||
// Clear State
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Clears all server state
|
||||
*/
|
||||
clear(): void {
|
||||
this._serverProps = null;
|
||||
this._error = null;
|
||||
this._loading = false;
|
||||
this._serverRole = null;
|
||||
this.props = null;
|
||||
this.error = null;
|
||||
this.loading = false;
|
||||
this.role = null;
|
||||
this.fetchPromise = null;
|
||||
this._modelPropsCache.clear();
|
||||
this.modelPropsCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export const propsStore = new PropsStore();
|
||||
export const serverStore = new ServerStore();
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Reactive Getters (for use in components)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const serverProps = () => propsStore.serverProps;
|
||||
export const propsLoading = () => propsStore.loading;
|
||||
export const propsError = () => propsStore.error;
|
||||
export const modelName = () => propsStore.modelName;
|
||||
export const supportedModalities = () => propsStore.supportedModalities;
|
||||
export const supportsVision = () => propsStore.supportsVision;
|
||||
export const supportsAudio = () => propsStore.supportsAudio;
|
||||
export const slotsEndpointAvailable = () => propsStore.slotsEndpointAvailable;
|
||||
export const defaultParams = () => propsStore.defaultParams;
|
||||
export const contextSize = () => propsStore.contextSize;
|
||||
|
||||
// Server mode exports
|
||||
export const serverRole = () => propsStore.serverRole;
|
||||
export const isRouterMode = () => propsStore.isRouterMode;
|
||||
export const isModelMode = () => propsStore.isModelMode;
|
||||
export const serverProps = () => serverStore.props;
|
||||
export const serverLoading = () => serverStore.loading;
|
||||
export const serverError = () => serverStore.error;
|
||||
export const serverRole = () => serverStore.role;
|
||||
export const modelName = () => serverStore.modelName;
|
||||
export const supportedModalities = () => serverStore.supportedModalities;
|
||||
export const supportsVision = () => serverStore.supportsVision;
|
||||
export const supportsAudio = () => serverStore.supportsAudio;
|
||||
export const slotsEndpointAvailable = () => serverStore.slotsEndpointAvailable;
|
||||
export const defaultParams = () => serverStore.defaultParams;
|
||||
export const contextSize = () => serverStore.contextSize;
|
||||
export const isRouterMode = () => serverStore.isRouterMode;
|
||||
export const isModelMode = () => serverStore.isModelMode;
|
||||
|
||||
// Actions
|
||||
export const fetchProps = propsStore.fetch.bind(propsStore);
|
||||
export const fetchModelProps = propsStore.fetchModelProps.bind(propsStore);
|
||||
export const getModelProps = propsStore.getModelProps.bind(propsStore);
|
||||
export const fetchServerProps = serverStore.fetch.bind(serverStore);
|
||||
export const fetchModelProps = serverStore.fetchModelProps.bind(serverStore);
|
||||
export const getModelProps = serverStore.getModelProps.bind(serverStore);
|
||||
|
|
@ -35,7 +35,7 @@ import { browser } from '$app/environment';
|
|||
import { SETTING_CONFIG_DEFAULT } from '$lib/constants/settings-config';
|
||||
import { normalizeFloatingPoint } from '$lib/utils/precision';
|
||||
import { ParameterSyncService } from '$lib/services/parameter-sync';
|
||||
import { propsStore } from '$lib/stores/props.svelte';
|
||||
import { serverStore } from '$lib/stores/server.svelte';
|
||||
import { setConfigValue, getConfigValue, configToParameterRecord } from '$lib/utils/config-helpers';
|
||||
import {
|
||||
CONFIG_LOCALSTORAGE_KEY,
|
||||
|
|
@ -53,7 +53,7 @@ class SettingsStore {
|
|||
* Centralizes the pattern of getting and extracting server defaults
|
||||
*/
|
||||
private getServerDefaults(): Record<string, string | number | boolean> {
|
||||
const serverParams = propsStore.defaultParams;
|
||||
const serverParams = serverStore.defaultParams;
|
||||
return serverParams ? ParameterSyncService.extractServerDefaults(serverParams) : {};
|
||||
}
|
||||
|
||||
|
|
@ -259,7 +259,7 @@ class SettingsStore {
|
|||
* This sets up the default values from /props endpoint
|
||||
*/
|
||||
syncWithServerDefaults(): void {
|
||||
const serverParams = propsStore.defaultParams;
|
||||
const serverParams = serverStore.defaultParams;
|
||||
if (!serverParams) {
|
||||
console.warn('No server parameters available for initialization');
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { isSvgMimeType, svgBase64UrlToPngDataURL } from './svg-to-png';
|
|||
import { isWebpMimeType, webpBase64UrlToPngDataURL } from './webp-to-png';
|
||||
import { FileTypeCategory, AttachmentType } from '$lib/enums';
|
||||
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||
import { supportsVision } from '$lib/stores/props.svelte';
|
||||
import { supportsVision } from '$lib/stores/server.svelte';
|
||||
import { getFileTypeCategory } from '$lib/utils/file-type';
|
||||
import { readFileAsText, isLikelyTextFile } from './text-files';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { isTextFileByName } from './text-files';
|
|||
import { isWebpMimeType, webpBase64UrlToPngDataURL } from './webp-to-png';
|
||||
import { FileTypeCategory } from '$lib/enums';
|
||||
import { getFileTypeCategory } from '$lib/utils/file-type';
|
||||
import { supportsVision } from '$lib/stores/props.svelte';
|
||||
import { supportsVision } from '$lib/stores/server.svelte';
|
||||
import { settingsStore } from '$lib/stores/settings.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
setTitleUpdateConfirmationCallback
|
||||
} from '$lib/stores/conversations.svelte';
|
||||
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
|
||||
import { isRouterMode, propsStore } from '$lib/stores/props.svelte';
|
||||
import { isRouterMode, serverStore } from '$lib/stores/server.svelte';
|
||||
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||
import { ModeWatcher } from 'mode-watcher';
|
||||
import { Toaster } from 'svelte-sonner';
|
||||
|
|
@ -95,16 +95,16 @@
|
|||
// Initialize server properties on app load (run once)
|
||||
$effect(() => {
|
||||
// Only fetch if we don't already have props
|
||||
if (!propsStore.serverProps) {
|
||||
if (!serverStore.props) {
|
||||
untrack(() => {
|
||||
propsStore.fetch();
|
||||
serverStore.fetch();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Sync settings when server props are loaded
|
||||
$effect(() => {
|
||||
const serverProps = propsStore.serverProps;
|
||||
const serverProps = serverStore.props;
|
||||
|
||||
if (serverProps?.default_generation_settings?.params) {
|
||||
settingsStore.syncWithServerDefaults();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { propsStore } from '$lib/stores/props.svelte';
|
||||
import { serverStore } from '$lib/stores/server.svelte';
|
||||
|
||||
/**
|
||||
* Mock server properties for Storybook testing
|
||||
* This utility allows setting mock server configurations without polluting production code
|
||||
*/
|
||||
export function mockServerProps(props: Partial<ApiLlamaCppServerProps>): void {
|
||||
// Directly set the private _serverProps for testing purposes
|
||||
(propsStore as unknown as { _serverProps: ApiLlamaCppServerProps })._serverProps = {
|
||||
// Directly set the props for testing purposes
|
||||
(serverStore as unknown as { props: ApiLlamaCppServerProps }).props = {
|
||||
model_path: props.model_path || 'test-model',
|
||||
modalities: {
|
||||
vision: props.modalities?.vision ?? false,
|
||||
|
|
@ -17,18 +17,18 @@ export function mockServerProps(props: Partial<ApiLlamaCppServerProps>): void {
|
|||
}
|
||||
|
||||
/**
|
||||
* Reset props store to clean state for testing
|
||||
* Reset server store to clean state for testing
|
||||
*/
|
||||
export function resetPropsStore(): void {
|
||||
(propsStore as unknown as { _serverProps: ApiLlamaCppServerProps })._serverProps = {
|
||||
export function resetServerStore(): void {
|
||||
(serverStore as unknown as { props: ApiLlamaCppServerProps }).props = {
|
||||
model_path: '',
|
||||
modalities: {
|
||||
vision: false,
|
||||
audio: false
|
||||
}
|
||||
} as ApiLlamaCppServerProps;
|
||||
(propsStore as unknown as { _error: string })._error = '';
|
||||
(propsStore as unknown as { _loading: boolean })._loading = false;
|
||||
(serverStore as unknown as { error: string }).error = '';
|
||||
(serverStore as unknown as { loading: boolean }).loading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue