docs: Architecture documentation
This commit is contained in:
parent
db479523ec
commit
bc577266b9
|
|
@ -0,0 +1,254 @@
|
|||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Routes["📍 Routes"]
|
||||
R1["/ (+page.svelte)"]
|
||||
R2["/chat/[id]"]
|
||||
RL["+layout.svelte"]
|
||||
end
|
||||
|
||||
subgraph Components["🧩 Components"]
|
||||
direction TB
|
||||
subgraph LayoutComponents["Layout"]
|
||||
C_Sidebar["ChatSidebar"]
|
||||
C_Screen["ChatScreen"]
|
||||
end
|
||||
subgraph ChatComponents["Chat"]
|
||||
C_Form["ChatForm"]
|
||||
C_Messages["ChatMessages"]
|
||||
C_Message["ChatMessage"]
|
||||
end
|
||||
subgraph ControlComponents["Controls"]
|
||||
C_SelectorModel["SelectorModel"]
|
||||
C_Settings["ChatSettings"]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph Stores["🗄️ Stores"]
|
||||
direction TB
|
||||
subgraph S1["chatStore"]
|
||||
S1State["<b>State:</b><br/>isLoading, currentResponse<br/>errorDialogState<br/>activeProcessingState<br/>chatLoadingStates<br/>chatStreamingStates<br/>abortControllers<br/>processingStates<br/>activeConversationId<br/>isStreamingActive"]
|
||||
S1LoadState["<b>Loading State:</b><br/>setChatLoading()<br/>isChatLoading()<br/>syncLoadingStateForChat()<br/>clearUIState()<br/>isChatLoadingPublic()<br/>getAllLoadingChats()<br/>getAllStreamingChats()"]
|
||||
S1ProcState["<b>Processing State:</b><br/>setActiveProcessingConversation()<br/>getProcessingState()<br/>clearProcessingState()<br/>getActiveProcessingState()<br/>updateProcessingStateFromTimings()<br/>getCurrentProcessingStateSync()<br/>restoreProcessingStateFromMessages()"]
|
||||
S1Stream["<b>Streaming:</b><br/>streamChatCompletion()<br/>startStreaming()<br/>stopStreaming()<br/>stopGeneration()<br/>isStreaming()"]
|
||||
S1Error["<b>Error Handling:</b><br/>showErrorDialog()<br/>dismissErrorDialog()<br/>isAbortError()"]
|
||||
S1Msg["<b>Message Operations:</b><br/>addMessage()<br/>sendMessage()<br/>updateMessage()<br/>deleteMessage()<br/>getDeletionInfo()"]
|
||||
S1Regen["<b>Regeneration:</b><br/>regenerateMessage()<br/>regenerateMessageWithBranching()<br/>continueAssistantMessage()"]
|
||||
S1Edit["<b>Editing:</b><br/>editAssistantMessage()<br/>editUserMessagePreserveResponses()<br/>editMessageWithBranching()"]
|
||||
S1Utils["<b>Utilities:</b><br/>getApiOptions()<br/>parseTimingData()<br/>getOrCreateAbortController()<br/>getConversationModel()"]
|
||||
end
|
||||
subgraph S2["conversationsStore"]
|
||||
S2State["<b>State:</b><br/>conversations<br/>activeConversation<br/>activeMessages<br/>usedModalities<br/>isInitialized<br/>titleUpdateConfirmationCallback"]
|
||||
S2Modal["<b>Modalities:</b><br/>getModalitiesUpToMessage()<br/>calculateModalitiesFromMessages()"]
|
||||
S2Lifecycle["<b>Lifecycle:</b><br/>initialize()<br/>loadConversations()<br/>clearActiveConversation()"]
|
||||
S2ConvCRUD["<b>Conversation CRUD:</b><br/>createConversation()<br/>loadConversation()<br/>deleteConversation()<br/>updateConversationName()<br/>updateConversationTitleWithConfirmation()"]
|
||||
S2MsgMgmt["<b>Message Management:</b><br/>refreshActiveMessages()<br/>addMessageToActive()<br/>updateMessageAtIndex()<br/>findMessageIndex()<br/>sliceActiveMessages()<br/>removeMessageAtIndex()<br/>getConversationMessages()"]
|
||||
S2Nav["<b>Navigation:</b><br/>navigateToSibling()<br/>updateCurrentNode()<br/>updateConversationTimestamp()"]
|
||||
S2Export["<b>Import/Export:</b><br/>downloadConversation()<br/>exportAllConversations()<br/>importConversations()<br/>triggerDownload()"]
|
||||
S2Utils["<b>Utilities:</b><br/>setTitleUpdateConfirmationCallback()"]
|
||||
end
|
||||
subgraph S3["modelsStore"]
|
||||
S3State["<b>State:</b><br/>models, routerModels<br/>selectedModelId<br/>selectedModelName<br/>loading, updating, error<br/>modelUsage<br/>modelLoadingStates<br/>modelPropsCache<br/>modelPropsFetching"]
|
||||
S3Getters["<b>Computed Getters:</b><br/>selectedModel<br/>loadedModelIds<br/>loadingModelIds"]
|
||||
S3Modal["<b>Modalities:</b><br/>getModelModalities()<br/>modelSupportsVision()<br/>modelSupportsAudio()<br/>getModelProps()<br/>updateModelModalities()"]
|
||||
S3Status["<b>Status Queries:</b><br/>isModelLoaded()<br/>isModelOperationInProgress()<br/>getModelStatus()<br/>isModelPropsFetching()"]
|
||||
S3Fetch["<b>Data Fetching:</b><br/>fetch()<br/>fetchRouterModels()<br/>fetchModelProps()<br/>fetchModalitiesForLoadedModels()"]
|
||||
S3Select["<b>Model Selection:</b><br/>selectModelById()<br/>selectModelByName()<br/>clearSelection()<br/>findModelByName()<br/>findModelById()<br/>hasModel()"]
|
||||
S3LoadUnload["<b>Loading/Unloading Models:</b><br/>loadModel()<br/>unloadModel()<br/>ensureModelLoaded()<br/>waitForModelStatus()<br/>pollForModelStatus()"]
|
||||
S3Usage["<b>Usage Tracking:</b><br/>registerModelUsage()<br/>unregisterModelUsage()<br/>clearConversationUsage()<br/>getModelUsage()<br/>isModelInUse()"]
|
||||
S3Utils["<b>Utilities:</b><br/>toDisplayName()<br/>clear()"]
|
||||
end
|
||||
subgraph S4["serverStore"]
|
||||
S4State["<b>State:</b><br/>props<br/>loading, error<br/>role<br/>fetchPromise"]
|
||||
S4Getters["<b>Getters:</b><br/>modelName<br/>supportedModalities<br/>supportsVision<br/>supportsAudio<br/>defaultParams<br/>contextSize<br/>slotsEndpointAvailable<br/>isRouterMode<br/>isModelMode"]
|
||||
S4Data["<b>Data Handling:</b><br/>fetch()<br/>getErrorMessage()<br/>clear()"]
|
||||
S4Utils["<b>Utilities:</b><br/>detectRole()"]
|
||||
end
|
||||
subgraph S5["settingsStore"]
|
||||
S5State["<b>State:</b><br/>config<br/>theme<br/>isInitialized<br/>userOverrides"]
|
||||
S5Lifecycle["<b>Lifecycle:</b><br/>initialize()<br/>loadConfig()<br/>saveConfig()<br/>loadTheme()<br/>saveTheme()"]
|
||||
S5Update["<b>Config Updates:</b><br/>updateConfig()<br/>updateMultipleConfig()<br/>updateTheme()"]
|
||||
S5Reset["<b>Reset:</b><br/>resetConfig()<br/>resetTheme()<br/>resetAll()<br/>resetParameterToServerDefault()"]
|
||||
S5Sync["<b>Server Sync:</b><br/>syncWithServerDefaults()<br/>forceSyncWithServerDefaults()"]
|
||||
S5Utils["<b>Utilities:</b><br/>getConfig()<br/>getAllConfig()<br/>getParameterInfo()<br/>getParameterDiff()<br/>getServerDefaults()<br/>clearAllUserOverrides()"]
|
||||
end
|
||||
|
||||
subgraph ReactiveExports["⚡ Reactive Exports"]
|
||||
direction LR
|
||||
subgraph ChatExports["chatStore"]
|
||||
RE1["isLoading()"]
|
||||
RE2["currentResponse()"]
|
||||
RE3["errorDialog()"]
|
||||
RE4["activeProcessingState()"]
|
||||
RE5["isChatStreaming()"]
|
||||
RE6["isChatLoading()"]
|
||||
RE7["getChatStreaming()"]
|
||||
RE8["getAllLoadingChats()"]
|
||||
RE9["getAllStreamingChats()"]
|
||||
end
|
||||
subgraph ConvExports["conversationsStore"]
|
||||
RE10["conversations()"]
|
||||
RE11["activeConversation()"]
|
||||
RE12["activeMessages()"]
|
||||
RE13["isConversationsInitialized()"]
|
||||
RE14["usedModalities()"]
|
||||
end
|
||||
subgraph ModelsExports["modelsStore"]
|
||||
RE15["modelOptions()"]
|
||||
RE16["routerModels()"]
|
||||
RE17["modelsLoading()"]
|
||||
RE18["modelsUpdating()"]
|
||||
RE19["modelsError()"]
|
||||
RE20["selectedModelId()"]
|
||||
RE21["selectedModelName()"]
|
||||
RE22["selectedModelOption()"]
|
||||
RE23["loadedModelIds()"]
|
||||
RE24["loadingModelIds()"]
|
||||
end
|
||||
subgraph ServerExports["serverStore"]
|
||||
RE25["serverProps()"]
|
||||
RE26["serverLoading()"]
|
||||
RE27["serverError()"]
|
||||
RE28["serverRole()"]
|
||||
RE29["modelName()"]
|
||||
RE30["supportedModalities()"]
|
||||
RE31["supportsVision()"]
|
||||
RE32["supportsAudio()"]
|
||||
RE33["slotsEndpointAvailable()"]
|
||||
RE34["defaultParams()"]
|
||||
RE35["contextSize()"]
|
||||
RE36["isRouterMode()"]
|
||||
RE37["isModelMode()"]
|
||||
end
|
||||
subgraph SettingsExports["settingsStore"]
|
||||
RE38["config()"]
|
||||
RE39["theme()"]
|
||||
RE40["isInitialized()"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
subgraph Services["⚙️ Services"]
|
||||
direction TB
|
||||
subgraph SV1["ChatService"]
|
||||
SV1Msg["<b>Messaging:</b><br/>sendMessage()"]
|
||||
SV1Stream["<b>Streaming:</b><br/>handleStreamResponse()<br/>parseSSEChunk()"]
|
||||
SV1Convert["<b>Conversion:</b><br/>convertMessageToChatData()<br/>convertExtraToApiFormat()"]
|
||||
SV1Utils["<b>Utilities:</b><br/>extractReasoningContent()<br/>getServerProps()<br/>getModels()"]
|
||||
end
|
||||
subgraph SV2["ModelsService"]
|
||||
SV2List["<b>Listing:</b><br/>list()<br/>listRouter()"]
|
||||
SV2LoadUnload["<b>Load/Unload:</b><br/>load()<br/>unload()"]
|
||||
SV2Status["<b>Status:</b><br/>getStatus()<br/>isModelLoaded()<br/>isModelLoading()"]
|
||||
end
|
||||
subgraph SV3["PropsService"]
|
||||
SV3Fetch["<b>Fetching:</b><br/>fetch()<br/>fetchForModel()"]
|
||||
end
|
||||
subgraph SV4["DatabaseService"]
|
||||
SV4Conv["<b>Conversations:</b><br/>createConversation()<br/>getConversation()<br/>getAllConversations()<br/>updateConversation()<br/>deleteConversation()"]
|
||||
SV4Msg["<b>Messages:</b><br/>createMessageBranch()<br/>createRootMessage()<br/>getConversationMessages()<br/>updateMessage()<br/>deleteMessage()<br/>deleteMessageCascading()"]
|
||||
SV4Node["<b>Navigation:</b><br/>updateCurrentNode()"]
|
||||
SV4Import["<b>Import:</b><br/>importConversations()"]
|
||||
end
|
||||
subgraph SV5["ParameterSyncService"]
|
||||
SV5Extract["<b>Extraction:</b><br/>extractServerDefaults()"]
|
||||
SV5Merge["<b>Merging:</b><br/>mergeWithServerDefaults()"]
|
||||
SV5Info["<b>Info:</b><br/>getParameterInfo()<br/>canSyncParameter()<br/>getSyncableParameterKeys()<br/>validateServerParameter()"]
|
||||
SV5Diff["<b>Diff:</b><br/>createParameterDiff()"]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph Storage["💾 Storage"]
|
||||
ST1["IndexedDB"]
|
||||
ST2["conversations"]
|
||||
ST3["messages"]
|
||||
ST5["LocalStorage"]
|
||||
ST6["config"]
|
||||
ST7["userOverrides"]
|
||||
end
|
||||
|
||||
subgraph APIs["🌐 llama-server API"]
|
||||
API1["/v1/chat/completions"]
|
||||
API2["/props<br/>/props?model="]
|
||||
API3["/models<br/>/models/load<br/>/models/unload<br/>/models/status"]
|
||||
API4["/v1/models"]
|
||||
end
|
||||
|
||||
%% Routes render Components
|
||||
R1 --> C_Screen
|
||||
R2 --> C_Screen
|
||||
RL --> C_Sidebar
|
||||
|
||||
%% Component hierarchy
|
||||
C_Screen --> C_Form & C_Messages & C_Settings
|
||||
C_Messages --> C_Message
|
||||
C_Message --> C_SelectorModel
|
||||
C_Form --> C_SelectorModel
|
||||
|
||||
%% Components use Stores
|
||||
C_Screen --> S1 & S2
|
||||
C_Messages --> S2
|
||||
C_Message --> S1 & S2 & S3
|
||||
C_Form --> S1 & S3
|
||||
C_Sidebar --> S2
|
||||
C_SelectorModel --> S3 & S4
|
||||
C_Settings --> S5
|
||||
|
||||
%% Stores export Reactive State
|
||||
S1 -. exports .-> ChatExports
|
||||
S2 -. exports .-> ConvExports
|
||||
S3 -. exports .-> ModelsExports
|
||||
S4 -. exports .-> ServerExports
|
||||
S5 -. exports .-> SettingsExports
|
||||
|
||||
%% Stores use Services
|
||||
S1 --> SV1 & SV4
|
||||
S2 --> SV4
|
||||
S3 --> SV2 & SV3
|
||||
S4 --> SV3
|
||||
S5 --> SV5
|
||||
|
||||
%% Services to Storage
|
||||
SV4 --> ST1
|
||||
ST1 --> ST2 & ST3
|
||||
SV5 --> ST5
|
||||
ST5 --> ST6 & ST7
|
||||
|
||||
%% Services to APIs
|
||||
SV1 --> API1
|
||||
SV2 --> API3 & API4
|
||||
SV3 --> API2
|
||||
|
||||
%% Styling
|
||||
classDef routeStyle fill:#e1f5fe,stroke:#01579b,stroke-width:2px
|
||||
classDef componentStyle fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
|
||||
classDef componentGroupStyle fill:#e1bee7,stroke:#7b1fa2,stroke-width:1px
|
||||
classDef storeStyle fill:#fff3e0,stroke:#e65100,stroke-width:2px
|
||||
classDef stateStyle fill:#ffe0b2,stroke:#e65100,stroke-width:1px
|
||||
classDef methodStyle fill:#ffecb3,stroke:#e65100,stroke-width:1px
|
||||
classDef reactiveStyle fill:#fffde7,stroke:#f9a825,stroke-width:1px
|
||||
classDef serviceStyle fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
|
||||
classDef serviceMStyle fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px
|
||||
classDef storageStyle fill:#fce4ec,stroke:#c2185b,stroke-width:2px
|
||||
classDef apiStyle fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
|
||||
|
||||
class R1,R2,RL routeStyle
|
||||
class C_Sidebar,C_Screen,C_Form,C_Messages,C_Message componentStyle
|
||||
class C_SelectorModel,C_Settings componentStyle
|
||||
class LayoutComponents,ChatComponents,ControlComponents componentGroupStyle
|
||||
class S1,S2,S3,S4,S5 storeStyle
|
||||
class S1State,S2State,S3State,S4State,S5State stateStyle
|
||||
class S1Msg,S1Regen,S1Edit,S1Stream,S1LoadState,S1ProcState,S1Error,S1Utils methodStyle
|
||||
class S2Lifecycle,S2ConvCRUD,S2MsgMgmt,S2Nav,S2Modal,S2Export,S2Utils methodStyle
|
||||
class S3Getters,S3Modal,S3Status,S3Fetch,S3Select,S3LoadUnload,S3Usage,S3Utils methodStyle
|
||||
class S4Getters,S4Data,S4Utils methodStyle
|
||||
class S5Lifecycle,S5Update,S5Reset,S5Sync,S5Utils methodStyle
|
||||
class ChatExports,ConvExports,ModelsExports,ServerExports,SettingsExports reactiveStyle
|
||||
class SV1,SV2,SV3,SV4,SV5 serviceStyle
|
||||
class SV1Msg,SV1Stream,SV1Convert,SV1Utils serviceMStyle
|
||||
class SV2List,SV2LoadUnload,SV2Status serviceMStyle
|
||||
class SV3Fetch serviceMStyle
|
||||
class SV4Conv,SV4Msg,SV4Node,SV4Import serviceMStyle
|
||||
class SV5Extract,SV5Merge,SV5Info,SV5Diff serviceMStyle
|
||||
class ST1,ST2,ST3,ST5,ST6,ST7 storageStyle
|
||||
class API1,API2,API3,API4 apiStyle
|
||||
```
|
||||
|
|
@ -54,6 +54,10 @@ import type { SettingsChatServiceOptions } from '$lib/types/settings';
|
|||
* - Request lifecycle management (abort via AbortSignal)
|
||||
*/
|
||||
export class ChatService {
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Messaging
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Sends a chat completion request to the llama.cpp server.
|
||||
* Supports both streaming and non-streaming responses with comprehensive parameter configuration.
|
||||
|
|
@ -261,6 +265,10 @@ export class ChatService {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Streaming
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Handles streaming response from the chat completion API
|
||||
* @param response - The Response object from the fetch request
|
||||
|
|
@ -571,6 +579,10 @@ export class ChatService {
|
|||
return result;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Conversion
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Converts a database message with attachments to API chat message format.
|
||||
* Processes various attachment types (images, text files, PDFs) and formats them
|
||||
|
|
@ -681,6 +693,10 @@ export class ChatService {
|
|||
};
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Utilities
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get server properties - static method for API compatibility (to be refactored)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -66,6 +66,10 @@ import { v4 as uuid } from 'uuid';
|
|||
* `currNode` tracks the currently active branch endpoint.
|
||||
*/
|
||||
export class DatabaseService {
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Conversations
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Creates a new conversation.
|
||||
*
|
||||
|
|
@ -84,6 +88,10 @@ export class DatabaseService {
|
|||
return conversation;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Messages
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Creates a new message branch by adding a message and updating parent/child relationships.
|
||||
* Also updates the conversation's currNode to point to the new message.
|
||||
|
|
@ -277,6 +285,10 @@ export class DatabaseService {
|
|||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Navigation
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Updates the conversation's current node (active branch).
|
||||
* This determines which conversation path is currently being viewed.
|
||||
|
|
@ -304,6 +316,10 @@ export class DatabaseService {
|
|||
await db.messages.update(id, updates);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Import
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Imports multiple conversations and their messages.
|
||||
* Skips conversations that already exist.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import type {
|
|||
*/
|
||||
export class ModelsService {
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// MODEL + ROUTER mode - OpenAI-compatible API
|
||||
// Listing
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
|
@ -46,10 +46,6 @@ export class ModelsService {
|
|||
return response.json() as Promise<ApiModelListResponse>;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// ROUTER mode only - Model management API
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Fetch list of all models with detailed metadata (ROUTER mode)
|
||||
* Returns models with load status, paths, and other metadata
|
||||
|
|
@ -66,6 +62,10 @@ export class ModelsService {
|
|||
return response.json() as Promise<ApiRouterModelsListResponse>;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Load/Unload
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Load a model (ROUTER mode)
|
||||
* POST /models/load
|
||||
|
|
@ -112,6 +112,10 @@ export class ModelsService {
|
|||
return response.json() as Promise<ApiRouterModelsUnloadResponse>;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Status
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get status of a specific model (ROUTER mode)
|
||||
* @param modelId - Model identifier to check
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ export const SYNCABLE_PARAMETERS: SyncableParameter[] = [
|
|||
];
|
||||
|
||||
export class ParameterSyncService {
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Extraction
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Round floating-point numbers to avoid JavaScript precision issues
|
||||
*/
|
||||
|
|
@ -95,6 +99,10 @@ export class ParameterSyncService {
|
|||
return extracted;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Merging
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Merge server defaults with current user settings
|
||||
* Returns updated settings that respect user overrides while using server defaults
|
||||
|
|
@ -116,6 +124,10 @@ export class ParameterSyncService {
|
|||
return merged;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Info
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get parameter information including source and values
|
||||
*/
|
||||
|
|
@ -172,6 +184,10 @@ export class ParameterSyncService {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Diff
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Create a diff between current settings and server defaults
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ import { getAuthHeaders } from '$lib/utils/api-headers';
|
|||
* - serverStore: Primary consumer for server state management
|
||||
*/
|
||||
export class PropsService {
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Fetching
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Fetches server properties from the /props endpoint
|
||||
*
|
||||
|
|
|
|||
|
|
@ -55,78 +55,24 @@ import type { DatabaseMessage, DatabaseMessageExtra } from '$lib/types/database'
|
|||
* - Automatic state sync when switching between conversations
|
||||
*/
|
||||
class ChatStore {
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// State
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
activeProcessingState = $state<ApiProcessingState | null>(null);
|
||||
currentResponse = $state('');
|
||||
errorDialogState = $state<{ type: 'timeout' | 'server'; message: string } | null>(null);
|
||||
isLoading = $state(false);
|
||||
chatLoadingStates = new SvelteMap<string, boolean>();
|
||||
chatStreamingStates = new SvelteMap<string, { response: string; messageId: string }>();
|
||||
|
||||
// Abort controllers for per-conversation request cancellation
|
||||
private abortControllers = new SvelteMap<string, AbortController>();
|
||||
|
||||
// Processing state tracking - per-conversation timing/context info
|
||||
private processingStates = new SvelteMap<string, ApiProcessingState | null>();
|
||||
private activeConversationId = $state<string | null>(null);
|
||||
private isStreamingActive = $state(false);
|
||||
|
||||
// ============ API Options ============
|
||||
|
||||
private getApiOptions(): Record<string, unknown> {
|
||||
const currentConfig = config();
|
||||
const hasValue = (value: unknown): boolean =>
|
||||
value !== undefined && value !== null && value !== '';
|
||||
|
||||
const apiOptions: Record<string, unknown> = { stream: true, timings_per_token: true };
|
||||
|
||||
// Model selection (required in ROUTER mode)
|
||||
if (isRouterMode()) {
|
||||
const modelName = selectedModelName();
|
||||
if (modelName) apiOptions.model = modelName;
|
||||
}
|
||||
|
||||
// Config options needed by ChatService
|
||||
if (currentConfig.systemMessage) apiOptions.systemMessage = currentConfig.systemMessage;
|
||||
if (currentConfig.disableReasoningFormat) apiOptions.disableReasoningFormat = true;
|
||||
|
||||
if (hasValue(currentConfig.temperature))
|
||||
apiOptions.temperature = Number(currentConfig.temperature);
|
||||
if (hasValue(currentConfig.max_tokens))
|
||||
apiOptions.max_tokens = Number(currentConfig.max_tokens);
|
||||
if (hasValue(currentConfig.dynatemp_range))
|
||||
apiOptions.dynatemp_range = Number(currentConfig.dynatemp_range);
|
||||
if (hasValue(currentConfig.dynatemp_exponent))
|
||||
apiOptions.dynatemp_exponent = Number(currentConfig.dynatemp_exponent);
|
||||
if (hasValue(currentConfig.top_k)) apiOptions.top_k = Number(currentConfig.top_k);
|
||||
if (hasValue(currentConfig.top_p)) apiOptions.top_p = Number(currentConfig.top_p);
|
||||
if (hasValue(currentConfig.min_p)) apiOptions.min_p = Number(currentConfig.min_p);
|
||||
if (hasValue(currentConfig.xtc_probability))
|
||||
apiOptions.xtc_probability = Number(currentConfig.xtc_probability);
|
||||
if (hasValue(currentConfig.xtc_threshold))
|
||||
apiOptions.xtc_threshold = Number(currentConfig.xtc_threshold);
|
||||
if (hasValue(currentConfig.typ_p)) apiOptions.typ_p = Number(currentConfig.typ_p);
|
||||
if (hasValue(currentConfig.repeat_last_n))
|
||||
apiOptions.repeat_last_n = Number(currentConfig.repeat_last_n);
|
||||
if (hasValue(currentConfig.repeat_penalty))
|
||||
apiOptions.repeat_penalty = Number(currentConfig.repeat_penalty);
|
||||
if (hasValue(currentConfig.presence_penalty))
|
||||
apiOptions.presence_penalty = Number(currentConfig.presence_penalty);
|
||||
if (hasValue(currentConfig.frequency_penalty))
|
||||
apiOptions.frequency_penalty = Number(currentConfig.frequency_penalty);
|
||||
if (hasValue(currentConfig.dry_multiplier))
|
||||
apiOptions.dry_multiplier = Number(currentConfig.dry_multiplier);
|
||||
if (hasValue(currentConfig.dry_base)) apiOptions.dry_base = Number(currentConfig.dry_base);
|
||||
if (hasValue(currentConfig.dry_allowed_length))
|
||||
apiOptions.dry_allowed_length = Number(currentConfig.dry_allowed_length);
|
||||
if (hasValue(currentConfig.dry_penalty_last_n))
|
||||
apiOptions.dry_penalty_last_n = Number(currentConfig.dry_penalty_last_n);
|
||||
if (currentConfig.samplers) apiOptions.samplers = currentConfig.samplers;
|
||||
if (currentConfig.custom) apiOptions.custom = currentConfig.custom;
|
||||
|
||||
return apiOptions;
|
||||
}
|
||||
|
||||
// ============ Loading State Management ============
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Loading State
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private setChatLoading(convId: string, loading: boolean): void {
|
||||
if (loading) {
|
||||
|
|
@ -171,28 +117,9 @@ class ChatStore {
|
|||
this.currentResponse = '';
|
||||
}
|
||||
|
||||
// ============ Processing State Management ============
|
||||
|
||||
/**
|
||||
* Start streaming session tracking
|
||||
*/
|
||||
startStreaming(): void {
|
||||
this.isStreamingActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop streaming session tracking
|
||||
*/
|
||||
stopStreaming(): void {
|
||||
this.isStreamingActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if currently in a streaming session
|
||||
*/
|
||||
isStreaming(): boolean {
|
||||
return this.isStreamingActive;
|
||||
}
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Processing State
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Set the active conversation for statistics display
|
||||
|
|
@ -301,6 +228,31 @@ class ChatStore {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Streaming
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Start streaming session tracking
|
||||
*/
|
||||
startStreaming(): void {
|
||||
this.isStreamingActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop streaming session tracking
|
||||
*/
|
||||
stopStreaming(): void {
|
||||
this.isStreamingActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if currently in a streaming session
|
||||
*/
|
||||
isStreaming(): boolean {
|
||||
return this.isStreamingActive;
|
||||
}
|
||||
|
||||
private getContextTotal(): number {
|
||||
const activeState = this.getActiveProcessingState();
|
||||
|
||||
|
|
@ -360,8 +312,6 @@ class ChatStore {
|
|||
};
|
||||
}
|
||||
|
||||
// ============ Model Detection ============
|
||||
|
||||
/**
|
||||
* Gets the model used in a conversation based on the latest assistant message.
|
||||
* Returns the model from the most recent assistant message that has a model field set.
|
||||
|
|
@ -380,7 +330,9 @@ class ChatStore {
|
|||
return null;
|
||||
}
|
||||
|
||||
// ============ Error Handling ============
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Error Handling
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private isAbortError(error: unknown): boolean {
|
||||
return error instanceof Error && (error.name === 'AbortError' || error instanceof DOMException);
|
||||
|
|
@ -394,7 +346,9 @@ class ChatStore {
|
|||
this.errorDialogState = null;
|
||||
}
|
||||
|
||||
// ============ Message Creation ============
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Message Operations
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
async addMessage(
|
||||
role: ChatRole,
|
||||
|
|
@ -475,8 +429,6 @@ class ChatStore {
|
|||
);
|
||||
}
|
||||
|
||||
// ============ Streaming ============
|
||||
|
||||
private async streamChatCompletion(
|
||||
allMessages: DatabaseMessage[],
|
||||
assistantMessage: DatabaseMessage,
|
||||
|
|
@ -614,8 +566,6 @@ class ChatStore {
|
|||
);
|
||||
}
|
||||
|
||||
// ============ Send Message ============
|
||||
|
||||
async sendMessage(content: string, extras?: DatabaseMessageExtra[]): Promise<void> {
|
||||
if (!content.trim() && (!extras || extras.length === 0)) return;
|
||||
const activeConv = conversationsStore.activeConversation;
|
||||
|
|
@ -661,8 +611,6 @@ class ChatStore {
|
|||
}
|
||||
}
|
||||
|
||||
// ============ Graceful Stop ============
|
||||
|
||||
async stopGeneration(): Promise<void> {
|
||||
const activeConv = conversationsStore.activeConversation;
|
||||
if (!activeConv) return;
|
||||
|
|
@ -746,8 +694,6 @@ class ChatStore {
|
|||
}
|
||||
}
|
||||
|
||||
// ============ Message Updates ============
|
||||
|
||||
async updateMessage(messageId: string, newContent: string): Promise<void> {
|
||||
const activeConv = conversationsStore.activeConversation;
|
||||
if (!activeConv) return;
|
||||
|
|
@ -803,6 +749,10 @@ class ChatStore {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Regeneration
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
async regenerateMessage(messageId: string): Promise<void> {
|
||||
const activeConv = conversationsStore.activeConversation;
|
||||
if (!activeConv || this.isLoading) return;
|
||||
|
|
@ -900,7 +850,9 @@ class ChatStore {
|
|||
}
|
||||
}
|
||||
|
||||
// ============ Branching Operations ============
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Editing
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
async editAssistantMessage(
|
||||
messageId: string,
|
||||
|
|
@ -1268,6 +1220,64 @@ class ChatStore {
|
|||
public getAllStreamingChats(): string[] {
|
||||
return Array.from(this.chatStreamingStates.keys());
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Utilities
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private getApiOptions(): Record<string, unknown> {
|
||||
const currentConfig = config();
|
||||
const hasValue = (value: unknown): boolean =>
|
||||
value !== undefined && value !== null && value !== '';
|
||||
|
||||
const apiOptions: Record<string, unknown> = { stream: true, timings_per_token: true };
|
||||
|
||||
// Model selection (required in ROUTER mode)
|
||||
if (isRouterMode()) {
|
||||
const modelName = selectedModelName();
|
||||
if (modelName) apiOptions.model = modelName;
|
||||
}
|
||||
|
||||
// Config options needed by ChatService
|
||||
if (currentConfig.systemMessage) apiOptions.systemMessage = currentConfig.systemMessage;
|
||||
if (currentConfig.disableReasoningFormat) apiOptions.disableReasoningFormat = true;
|
||||
|
||||
if (hasValue(currentConfig.temperature))
|
||||
apiOptions.temperature = Number(currentConfig.temperature);
|
||||
if (hasValue(currentConfig.max_tokens))
|
||||
apiOptions.max_tokens = Number(currentConfig.max_tokens);
|
||||
if (hasValue(currentConfig.dynatemp_range))
|
||||
apiOptions.dynatemp_range = Number(currentConfig.dynatemp_range);
|
||||
if (hasValue(currentConfig.dynatemp_exponent))
|
||||
apiOptions.dynatemp_exponent = Number(currentConfig.dynatemp_exponent);
|
||||
if (hasValue(currentConfig.top_k)) apiOptions.top_k = Number(currentConfig.top_k);
|
||||
if (hasValue(currentConfig.top_p)) apiOptions.top_p = Number(currentConfig.top_p);
|
||||
if (hasValue(currentConfig.min_p)) apiOptions.min_p = Number(currentConfig.min_p);
|
||||
if (hasValue(currentConfig.xtc_probability))
|
||||
apiOptions.xtc_probability = Number(currentConfig.xtc_probability);
|
||||
if (hasValue(currentConfig.xtc_threshold))
|
||||
apiOptions.xtc_threshold = Number(currentConfig.xtc_threshold);
|
||||
if (hasValue(currentConfig.typ_p)) apiOptions.typ_p = Number(currentConfig.typ_p);
|
||||
if (hasValue(currentConfig.repeat_last_n))
|
||||
apiOptions.repeat_last_n = Number(currentConfig.repeat_last_n);
|
||||
if (hasValue(currentConfig.repeat_penalty))
|
||||
apiOptions.repeat_penalty = Number(currentConfig.repeat_penalty);
|
||||
if (hasValue(currentConfig.presence_penalty))
|
||||
apiOptions.presence_penalty = Number(currentConfig.presence_penalty);
|
||||
if (hasValue(currentConfig.frequency_penalty))
|
||||
apiOptions.frequency_penalty = Number(currentConfig.frequency_penalty);
|
||||
if (hasValue(currentConfig.dry_multiplier))
|
||||
apiOptions.dry_multiplier = Number(currentConfig.dry_multiplier);
|
||||
if (hasValue(currentConfig.dry_base)) apiOptions.dry_base = Number(currentConfig.dry_base);
|
||||
if (hasValue(currentConfig.dry_allowed_length))
|
||||
apiOptions.dry_allowed_length = Number(currentConfig.dry_allowed_length);
|
||||
if (hasValue(currentConfig.dry_penalty_last_n))
|
||||
apiOptions.dry_penalty_last_n = Number(currentConfig.dry_penalty_last_n);
|
||||
if (currentConfig.samplers) apiOptions.samplers = currentConfig.samplers;
|
||||
if (currentConfig.custom) apiOptions.custom = currentConfig.custom;
|
||||
|
||||
return apiOptions;
|
||||
}
|
||||
}
|
||||
|
||||
export const chatStore = new ChatStore();
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ import type { ModelModalities } from '$lib/types/models';
|
|||
* - `isInitialized`: Store initialization status
|
||||
*/
|
||||
class ConversationsStore {
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// State
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/** List of all conversations */
|
||||
conversations = $state<DatabaseConversation[]>([]);
|
||||
|
||||
|
|
@ -64,6 +68,13 @@ class ConversationsStore {
|
|||
/** Whether the store has been initialized */
|
||||
isInitialized = $state(false);
|
||||
|
||||
/** Callback for title update confirmation dialog */
|
||||
titleUpdateConfirmationCallback?: (currentTitle: string, newTitle: string) => Promise<boolean>;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Modalities
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Modalities used in the active conversation.
|
||||
* Computed from attachments in activeMessages.
|
||||
|
|
@ -113,15 +124,16 @@ class ConversationsStore {
|
|||
return this.calculateModalitiesFromMessages(messagesBefore);
|
||||
}
|
||||
|
||||
/** Callback for title update confirmation dialog */
|
||||
titleUpdateConfirmationCallback?: (currentTitle: string, newTitle: string) => Promise<boolean>;
|
||||
|
||||
constructor() {
|
||||
if (browser) {
|
||||
this.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Lifecycle
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Initializes the conversations store by loading conversations from the database
|
||||
*/
|
||||
|
|
@ -141,6 +153,10 @@ class ConversationsStore {
|
|||
this.conversations = await DatabaseService.getAllConversations();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Conversation CRUD
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Creates a new conversation and navigates to it
|
||||
* @param name - Optional name for the conversation
|
||||
|
|
@ -202,6 +218,10 @@ class ConversationsStore {
|
|||
// Active processing conversation is now managed by chatStore
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Message Management
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Refreshes active messages based on currNode after branch navigation
|
||||
*/
|
||||
|
|
@ -248,16 +268,6 @@ class ConversationsStore {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the callback function for title update confirmations
|
||||
* @param callback - Function to call when confirmation is needed
|
||||
*/
|
||||
setTitleUpdateConfirmationCallback(
|
||||
callback: (currentTitle: string, newTitle: string) => Promise<boolean>
|
||||
): void {
|
||||
this.titleUpdateConfirmationCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates conversation title with optional confirmation dialog based on settings
|
||||
* @param convId - The conversation ID to update
|
||||
|
|
@ -289,6 +299,10 @@ class ConversationsStore {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Navigation
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Updates the current node of the active conversation
|
||||
* @param nodeId - The new current node ID
|
||||
|
|
@ -376,6 +390,10 @@ class ConversationsStore {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Import/Export
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Downloads a conversation as JSON file
|
||||
* @param convId - The conversation ID to download
|
||||
|
|
@ -580,6 +598,20 @@ class ConversationsStore {
|
|||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Utilities
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Sets the callback function for title update confirmations
|
||||
* @param callback - Function to call when confirmation is needed
|
||||
*/
|
||||
setTitleUpdateConfirmationCallback(
|
||||
callback: (currentTitle: string, newTitle: string) => Promise<boolean>
|
||||
): void {
|
||||
this.titleUpdateConfirmationCallback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
export const conversationsStore = new ConversationsStore();
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class ModelsStore {
|
|||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Methods - Model Modalities
|
||||
// Modalities
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
|
@ -133,7 +133,7 @@ class ModelsStore {
|
|||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Methods - Model Status
|
||||
// Status Queries
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
isModelLoaded(modelId: string): boolean {
|
||||
|
|
@ -160,7 +160,7 @@ class ModelsStore {
|
|||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Fetch Models
|
||||
// Data Fetching
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
|
@ -328,7 +328,7 @@ class ModelsStore {
|
|||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Select Model
|
||||
// Model Selection
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
|
@ -382,7 +382,7 @@ class ModelsStore {
|
|||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Load/Unload Models (ROUTER mode)
|
||||
// Loading/Unloading Models
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
|
@ -501,7 +501,7 @@ class ModelsStore {
|
|||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Model Usage Tracking
|
||||
// Usage Tracking
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
|
@ -546,7 +546,7 @@ class ModelsStore {
|
|||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Private Helpers
|
||||
// Utilities
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private toDisplayName(id: string): string {
|
||||
|
|
@ -556,10 +556,6 @@ class ModelsStore {
|
|||
return candidate && candidate.trim().length > 0 ? candidate : id;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Clear State
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
clear(): void {
|
||||
this.models = [];
|
||||
this.routerModels = [];
|
||||
|
|
|
|||
|
|
@ -24,12 +24,20 @@ import { ServerRole, ModelModality } from '$lib/enums';
|
|||
* apply to MODEL mode (single model).
|
||||
*/
|
||||
class ServerStore {
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// State
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
props = $state<ApiLlamaCppServerProps | null>(null);
|
||||
loading = $state(false);
|
||||
error = $state<string | null>(null);
|
||||
role = $state<ServerRole | null>(null);
|
||||
private fetchPromise: Promise<void> | null = null;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Getters
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get model name from server props.
|
||||
* In MODEL mode: extracts from model_path or model_alias
|
||||
|
|
@ -89,13 +97,9 @@ class ServerStore {
|
|||
return this.role === ServerRole.MODEL;
|
||||
}
|
||||
|
||||
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`);
|
||||
}
|
||||
}
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Data Handling
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
async fetch(): Promise<void> {
|
||||
if (this.fetchPromise) return this.fetchPromise;
|
||||
|
|
@ -147,6 +151,7 @@ class ServerStore {
|
|||
|
||||
return 'Failed to connect to server';
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.props = null;
|
||||
this.error = null;
|
||||
|
|
@ -154,6 +159,18 @@ class ServerStore {
|
|||
this.role = null;
|
||||
this.fetchPromise = null;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Utilities
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const serverStore = new ServerStore();
|
||||
|
|
|
|||
|
|
@ -43,11 +43,19 @@ import {
|
|||
} from '$lib/constants/localstorage-keys';
|
||||
|
||||
class SettingsStore {
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// State
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
config = $state<SettingsConfigType>({ ...SETTING_CONFIG_DEFAULT });
|
||||
theme = $state<string>('auto');
|
||||
isInitialized = $state(false);
|
||||
userOverrides = $state<Set<string>>(new Set());
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Utilities (private helpers)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Helper method to get server defaults with null safety
|
||||
* Centralizes the pattern of getting and extracting server defaults
|
||||
|
|
@ -63,6 +71,10 @@ class SettingsStore {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Lifecycle
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Initialize the settings store by loading from localStorage
|
||||
*/
|
||||
|
|
@ -113,6 +125,10 @@ class SettingsStore {
|
|||
|
||||
this.theme = localStorage.getItem('theme') || 'auto';
|
||||
}
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Config Updates
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Update a specific configuration setting
|
||||
* @param key - The configuration key to update
|
||||
|
|
@ -213,6 +229,10 @@ class SettingsStore {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Reset
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Reset configuration to defaults
|
||||
*/
|
||||
|
|
@ -238,22 +258,32 @@ class SettingsStore {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get a specific configuration value
|
||||
* @param key - The configuration key to get
|
||||
* @returns The configuration value
|
||||
* Reset a parameter to server default (or webui default if no server default)
|
||||
*/
|
||||
getConfig<K extends keyof SettingsConfigType>(key: K): SettingsConfigType[K] {
|
||||
return this.config[key];
|
||||
resetParameterToServerDefault(key: string): void {
|
||||
const serverDefaults = this.getServerDefaults();
|
||||
|
||||
if (serverDefaults[key] !== undefined) {
|
||||
const value = normalizeFloatingPoint(serverDefaults[key]);
|
||||
|
||||
this.config[key as keyof SettingsConfigType] =
|
||||
value as SettingsConfigType[keyof SettingsConfigType];
|
||||
} else {
|
||||
if (key in SETTING_CONFIG_DEFAULT) {
|
||||
const defaultValue = getConfigValue(SETTING_CONFIG_DEFAULT, key);
|
||||
|
||||
setConfigValue(this.config, key, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entire configuration object
|
||||
* @returns The complete configuration object
|
||||
*/
|
||||
getAllConfig(): SettingsConfigType {
|
||||
return { ...this.config };
|
||||
this.userOverrides.delete(key);
|
||||
this.saveConfig();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Server Sync
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Initialize settings with props defaults when server properties are first loaded
|
||||
* This sets up the default values from /props endpoint
|
||||
|
|
@ -287,15 +317,6 @@ class SettingsStore {
|
|||
console.log('Current user overrides after sync:', Array.from(this.userOverrides));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all user overrides (for debugging)
|
||||
*/
|
||||
clearAllUserOverrides(): void {
|
||||
this.userOverrides.clear();
|
||||
this.saveConfig();
|
||||
console.log('Cleared all user overrides');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all parameters to their default values (from props)
|
||||
* This is used by the "Reset to Default" functionality
|
||||
|
|
@ -324,6 +345,27 @@ class SettingsStore {
|
|||
this.saveConfig();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Utilities
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get a specific configuration value
|
||||
* @param key - The configuration key to get
|
||||
* @returns The configuration value
|
||||
*/
|
||||
getConfig<K extends keyof SettingsConfigType>(key: K): SettingsConfigType[K] {
|
||||
return this.config[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entire configuration object
|
||||
* @returns The complete configuration object
|
||||
*/
|
||||
getAllConfig(): SettingsConfigType {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parameter information including source for a specific parameter
|
||||
*/
|
||||
|
|
@ -339,29 +381,6 @@ class SettingsStore {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset a parameter to server default (or webui default if no server default)
|
||||
*/
|
||||
resetParameterToServerDefault(key: string): void {
|
||||
const serverDefaults = this.getServerDefaults();
|
||||
|
||||
if (serverDefaults[key] !== undefined) {
|
||||
const value = normalizeFloatingPoint(serverDefaults[key]);
|
||||
|
||||
this.config[key as keyof SettingsConfigType] =
|
||||
value as SettingsConfigType[keyof SettingsConfigType];
|
||||
} else {
|
||||
if (key in SETTING_CONFIG_DEFAULT) {
|
||||
const defaultValue = getConfigValue(SETTING_CONFIG_DEFAULT, key);
|
||||
|
||||
setConfigValue(this.config, key, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
this.userOverrides.delete(key);
|
||||
this.saveConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get diff between current settings and server defaults
|
||||
*/
|
||||
|
|
@ -376,6 +395,15 @@ class SettingsStore {
|
|||
|
||||
return ParameterSyncService.createParameterDiff(configAsRecord, serverDefaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all user overrides (for debugging)
|
||||
*/
|
||||
clearAllUserOverrides(): void {
|
||||
this.userOverrides.clear();
|
||||
this.saveConfig();
|
||||
console.log('Cleared all user overrides');
|
||||
}
|
||||
}
|
||||
|
||||
export const settingsStore = new SettingsStore();
|
||||
|
|
|
|||
Loading…
Reference in New Issue