diff --git a/tools/server/webui/docs/architecture/high-level-architecture-simplified.md b/tools/server/webui/docs/architecture/high-level-architecture-simplified.md index dde630be37..500f477c9a 100644 --- a/tools/server/webui/docs/architecture/high-level-architecture-simplified.md +++ b/tools/server/webui/docs/architecture/high-level-architecture-simplified.md @@ -12,11 +12,13 @@ flowchart TB C_Form["ChatForm"] C_Messages["ChatMessages"] C_Message["ChatMessage"] - C_AgenticContent["AgenticContent"] + C_ChatMessageAgenticContent["ChatMessageAgenticContent"] C_MessageEditForm["ChatMessageEditForm"] C_ModelsSelector["ModelsSelector"] C_Settings["ChatSettings"] - C_McpSettings["McpSettingsSection"] + C_McpSettings["McpServersSettings"] + C_McpResourceBrowser["McpResourceBrowser"] + C_McpServersSelector["McpServersSelector"] end subgraph Hooks["🪝 Hooks"] @@ -26,24 +28,22 @@ flowchart TB subgraph Stores["🗄️ Stores"] S1["chatStore
Chat interactions & streaming"] - S2["conversationsStore
Conversation data & messages"] + SA["agenticStore
Multi-turn agentic loop orchestration"] + S2["conversationsStore
Conversation data, messages & MCP overrides"] S3["modelsStore
Model selection & loading"] S4["serverStore
Server props & role detection"] S5["settingsStore
User configuration incl. MCP"] + S6["mcpStore
MCP servers, tools, prompts"] + S7["mcpResourceStore
MCP resources & attachments"] end subgraph Services["⚙️ Services"] - SV1["ChatService
incl. agentic loop"] + SV1["ChatService"] SV2["ModelsService"] SV3["PropsService"] SV4["DatabaseService"] SV5["ParameterSyncService"] - end - - subgraph MCP["🔧 MCP (Model Context Protocol)"] - MCP1["MCPClient
@modelcontextprotocol/sdk"] - MCP2["mcpStore
reactive state"] - MCP3["OpenAISseClient"] + SV6["MCPService
protocol operations"] end subgraph Storage["💾 Storage"] @@ -59,7 +59,7 @@ flowchart TB end subgraph ExternalMCP["🔌 External MCP Servers"] - EXT1["MCP Server 1"] + EXT1["MCP Server 1
WebSocket/HTTP/SSE"] EXT2["MCP Server N"] end @@ -67,13 +67,18 @@ flowchart TB R1 & R2 --> C_Screen RL --> C_Sidebar + %% Layout runs MCP health checks + RL --> S6 + %% Component hierarchy C_Screen --> C_Form & C_Messages & C_Settings C_Messages --> C_Message - C_Message --> C_AgenticContent + C_Message --> C_ChatMessageAgenticContent C_Message --> C_MessageEditForm C_Form & C_MessageEditForm --> C_ModelsSelector + C_Form --> C_McpServersSelector C_Settings --> C_McpSettings + C_McpSettings --> C_McpResourceBrowser %% Components → Hooks → Stores C_Form & C_Messages --> H1 & H2 @@ -85,7 +90,15 @@ flowchart TB C_Sidebar --> S2 C_ModelsSelector --> S3 & S4 C_Settings --> S5 - C_McpSettings --> S5 + C_McpSettings --> S6 + C_McpResourceBrowser --> S6 & S7 + C_McpServersSelector --> S6 + C_Form --> S6 + + %% chatStore → agenticStore → mcpStore (agentic loop) + S1 --> SA + SA --> SV1 + SA --> S6 %% Stores → Services S1 --> SV1 & SV4 @@ -93,12 +106,8 @@ flowchart TB S3 --> SV2 & SV3 S4 --> SV3 S5 --> SV5 - - %% ChatService → MCP (Agentic Mode) - SV1 --> MCP2 - MCP2 --> MCP1 - SV1 --> MCP3 - MCP3 --> API1 + S6 --> SV6 + S7 --> SV6 %% Services → Storage SV4 --> ST1 @@ -110,7 +119,7 @@ flowchart TB SV3 --> API2 %% MCP → External Servers - MCP1 --> EXT1 & EXT2 + SV6 --> EXT1 & EXT2 %% Styling classDef routeStyle fill:#e1f5fe,stroke:#01579b,stroke-width:2px @@ -121,15 +130,16 @@ flowchart TB classDef storageStyle fill:#fce4ec,stroke:#c2185b,stroke-width:2px classDef apiStyle fill:#e3f2fd,stroke:#1565c0,stroke-width:2px classDef mcpStyle fill:#e0f2f1,stroke:#00695c,stroke-width:2px + classDef agenticStyle fill:#e8eaf6,stroke:#283593,stroke-width:2px classDef externalStyle fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px,stroke-dasharray: 5 5 class R1,R2,RL routeStyle - class C_Sidebar,C_Screen,C_Form,C_Messages,C_Message,C_AgenticContent,C_MessageEditForm,C_ModelsSelector,C_Settings,C_McpSettings componentStyle + class C_Sidebar,C_Screen,C_Form,C_Messages,C_Message,C_ChatMessageAgenticContent,C_MessageEditForm,C_ModelsSelector,C_Settings componentStyle + class C_McpSettings,C_McpResourceBrowser,C_McpServersSelector componentStyle class H1,H2 hookStyle - class S1,S2,S3,S4,S5 storeStyle - class SV1,SV2,SV3,SV4,SV5 serviceStyle + class S1,S2,S3,S4,S5,SA,S6,S7 storeStyle + class SV1,SV2,SV3,SV4,SV5,SV6 serviceStyle class ST1,ST2 storageStyle class API1,API2,API3,API4 apiStyle - class MCP1,MCP2,MCP3 mcpStyle class EXT1,EXT2 externalStyle ``` diff --git a/tools/server/webui/docs/architecture/high-level-architecture.md b/tools/server/webui/docs/architecture/high-level-architecture.md index 0a1e5a745e..42ddb3f4f5 100644 --- a/tools/server/webui/docs/architecture/high-level-architecture.md +++ b/tools/server/webui/docs/architecture/high-level-architecture.md @@ -22,6 +22,13 @@ end C_ModelsSelector["ModelsSelector"] C_Settings["ChatSettings"] end + subgraph MCPComponents["MCP UI"] + C_McpSettings["McpServersSettings"] + C_McpServerCard["McpServerCard"] + C_McpResourceBrowser["McpResourceBrowser"] + C_McpResourcePreview["McpResourcePreview"] + C_McpServersSelector["McpServersSelector"] + end end subgraph Hooks["🪝 Hooks"] @@ -43,14 +50,20 @@ end S1Edit["Editing:
editAssistantMessage()
editUserMessagePreserveResponses()
editMessageWithBranching()
clearEditMode()
isEditModeActive()
getAddFilesHandler()
setEditModeActive()"] S1Utils["Utilities:
getApiOptions()
parseTimingData()
getOrCreateAbortController()
getConversationModel()"] end + subgraph SA["agenticStore"] + SAState["State:
sessions (Map)
isAnyRunning"] + SASession["Session Management:
getSession()
updateSession()
clearSession()
getActiveSessions()
isRunning()
currentTurn()
totalToolCalls()
lastError()
streamingToolCall()"] + SAConfig["Configuration:
getConfig()
maxTurns, maxToolPreviewLines"] + SAFlow["Agentic Loop:
runAgenticFlow()
executeAgenticLoop()
normalizeToolCalls()
emitToolCallResult()
extractBase64Attachments()"] + end subgraph S2["conversationsStore"] - S2State["State:
conversations
activeConversation
activeMessages
usedModalities
isInitialized
titleUpdateConfirmationCallback"] - S2Modal["Modalities:
getModalitiesUpToMessage()
calculateModalitiesFromMessages()"] + S2State["State:
conversations
activeConversation
activeMessages
isInitialized
pendingMcpServerOverrides
titleUpdateConfirmationCallback"] S2Lifecycle["Lifecycle:
initialize()
loadConversations()
clearActiveConversation()"] - S2ConvCRUD["Conversation CRUD:
createConversation()
loadConversation()
deleteConversation()
updateConversationName()
updateConversationTitleWithConfirmation()"] + S2ConvCRUD["Conversation CRUD:
createConversation()
loadConversation()
deleteConversation()
deleteAll()
updateConversationName()
updateConversationTitleWithConfirmation()"] S2MsgMgmt["Message Management:
refreshActiveMessages()
addMessageToActive()
updateMessageAtIndex()
findMessageIndex()
sliceActiveMessages()
removeMessageAtIndex()
getConversationMessages()"] S2Nav["Navigation:
navigateToSibling()
updateCurrentNode()
updateConversationTimestamp()"] - S2Export["Import/Export:
downloadConversation()
exportAllConversations()
importConversations()
triggerDownload()"] + S2McpOverrides["MCP Per-Chat Overrides:
getMcpServerOverride()
getAllMcpServerOverrides()
setMcpServerOverride()
toggleMcpServerForChat()
removeMcpServerOverride()
isMcpServerEnabledForChat()
clearPendingMcpServerOverrides()"] + S2Export["Import/Export:
downloadConversation()
exportAllConversations()
importConversations()
importConversationsData()
triggerDownload()"] S2Utils["Utilities:
setTitleUpdateConfirmationCallback()"] end subgraph S3["modelsStore"] @@ -77,6 +90,21 @@ end S5Sync["Server Sync:
syncWithServerDefaults()
forceSyncWithServerDefaults()"] S5Utils["Utilities:
getConfig()
getAllConfig()
getParameterInfo()
getParameterDiff()
getServerDefaults()
clearAllUserOverrides()"] end + subgraph S6["mcpStore"] + S6State["State:
isInitializing, error
toolCount, connectedServers
healthChecks (Map)
connections (Map)
toolsIndex (Map)"] + S6Lifecycle["Lifecycle:
ensureInitialized()
initialize()
shutdown()
acquireConnection()
releaseConnection()"] + S6Health["Health Checks:
runHealthCheck()
runHealthChecksForServers()
updateHealthCheck()
getHealthCheckState()
clearHealthCheck()"] + S6Servers["Server Management:
getServers()
addServer()
updateServer()
removeServer()
getServerById()
getServerDisplayName()"] + S6Tools["Tool Operations:
getToolDefinitionsForLLM()
getToolNames()
hasTool()
getToolServer()
executeTool()
executeToolByName()"] + S6Prompts["Prompt Operations:
getAllPrompts()
getPrompt()
hasPromptsCapability()
getPromptCompletions()"] + end + subgraph S7["mcpResourceStore"] + S7State["State:
serverResources (Map)
cachedResources (Map)
subscriptions (Map)
attachments[]
isLoading"] + S7Resources["Resource Discovery:
setServerResources()
getServerResources()
getAllResourceInfos()
getAllTemplateInfos()
clearServerResources()"] + S7Cache["Caching:
cacheResourceContent()
getCachedContent()
invalidateCache()
clearCache()"] + S7Subs["Subscriptions:
addSubscription()
removeSubscription()
isSubscribed()
handleResourceUpdate()"] + S7Attach["Attachments:
addAttachment()
updateAttachmentContent()
removeAttachment()
clearAttachments()
toMessageExtras()"] + end subgraph ReactiveExports["⚡ Reactive Exports"] direction LR @@ -95,12 +123,19 @@ end RE9c["setEditModeActive()"] RE9d["clearEditMode()"] end + subgraph AgenticExports["agenticStore"] + REA1["agenticIsRunning()"] + REA2["agenticCurrentTurn()"] + REA3["agenticTotalToolCalls()"] + REA4["agenticLastError()"] + REA5["agenticStreamingToolCall()"] + REA6["agenticIsAnyRunning()"] + end subgraph ConvExports["conversationsStore"] RE10["conversations()"] RE11["activeConversation()"] RE12["activeMessages()"] RE13["isConversationsInitialized()"] - RE14["usedModalities()"] end subgraph ModelsExports["modelsStore"] RE15["modelOptions()"] @@ -131,6 +166,13 @@ end RE36["theme()"] RE37["isInitialized()"] end + subgraph MCPExports["mcpStore / mcpResourceStore"] + RE38["mcpResources()"] + RE39["mcpResourceAttachments()"] + RE40["mcpHasResourceAttachments()"] + RE41["mcpTotalResourceCount()"] + RE42["mcpResourcesLoading()"] + end end end @@ -138,9 +180,9 @@ end direction TB subgraph SV1["ChatService"] SV1Msg["Messaging:
sendMessage()"] - SV1Stream["Streaming:
handleStreamResponse()
parseSSEChunk()"] - SV1Convert["Conversion:
convertMessageToChatData()
convertExtraToApiFormat()"] - SV1Utils["Utilities:
extractReasoningContent()
getServerProps()
getModels()"] + SV1Stream["Streaming:
handleStreamResponse()
handleNonStreamResponse()"] + SV1Convert["Conversion:
convertDbMessageToApiChatMessageData()
mergeToolCallDeltas()"] + SV1Utils["Utilities:
stripReasoningContent()
extractModelName()
parseErrorResponse()"] end subgraph SV2["ModelsService"] SV2List["Listing:
list()
listRouter()"] @@ -152,7 +194,7 @@ end end subgraph SV4["DatabaseService"] SV4Conv["Conversations:
createConversation()
getConversation()
getAllConversations()
updateConversation()
deleteConversation()"] - SV4Msg["Messages:
createMessageBranch()
createRootMessage()
getConversationMessages()
updateMessage()
deleteMessage()
deleteMessageCascading()"] + SV4Msg["Messages:
createMessageBranch()
createRootMessage()
createSystemMessage()
getConversationMessages()
updateMessage()
deleteMessage()
deleteMessageCascading()"] SV4Node["Navigation:
updateCurrentNode()"] SV4Import["Import:
importConversations()"] end @@ -162,31 +204,18 @@ end SV5Info["Info:
getParameterInfo()
canSyncParameter()
getSyncableParameterKeys()
validateServerParameter()"] SV5Diff["Diff:
createParameterDiff()"] end - end - - subgraph MCP["🔧 MCP (Model Context Protocol)"] - direction TB - subgraph MCPStoreBox["mcpStore"] - MCPStoreState["State:
client, isInitializing
error, availableTools"] - MCPStoreLifecycle["Lifecycle:
ensureClient()
shutdown()"] - MCPStoreExec["Execution:
execute()"] - end - subgraph MCPClient["MCPClient"] - MCP1Init["Lifecycle:
initialize()
shutdown()"] - MCP1Tools["Tools:
listTools()
getToolsDefinition()
execute()"] - MCP1Transport["Transport:
StreamableHTTPClientTransport
SSEClientTransport (fallback)"] - end - subgraph MCPSse["OpenAISseClient"] - MCP3Stream["Streaming:
streamChatCompletion()"] - MCP3Parse["Parsing:
tool call delta merging"] - end - subgraph MCPConfig["config/mcp"] - MCP4Parse["Parsing:
parseServersFromSettings()"] + subgraph SV6["MCPService"] + SV6Transport["Transport:
createTransport()
WebSocket / StreamableHTTP / SSE"] + SV6Conn["Connection:
connect()
disconnect()"] + SV6Tools["Tools:
listTools()
callTool()"] + SV6Prompts["Prompts:
listPrompts()
getPrompt()"] + SV6Resources["Resources:
listResources()
listResourceTemplates()
readResource()
subscribeResource()
unsubscribeResource()"] + SV6Complete["Completions:
complete()"] end end subgraph ExternalMCP["🔌 External MCP Servers"] - EXT1["MCP Server 1
(StreamableHTTP/SSE)"] + EXT1["MCP Server 1
(WebSocket/StreamableHTTP/SSE)"] EXT2["MCP Server N"] end @@ -197,6 +226,7 @@ end ST5["LocalStorage"] ST6["config"] ST7["userOverrides"] + ST8["mcpServers"] end subgraph APIs["🌐 llama-server API"] @@ -211,6 +241,9 @@ end R2 --> C_Screen RL --> C_Sidebar + %% Layout runs MCP health checks on startup + RL --> S6 + %% Component hierarchy C_Screen --> C_Form & C_Messages & C_Settings C_Messages --> C_Message @@ -220,8 +253,15 @@ end C_MessageEditForm --> C_Attach C_Form --> C_ModelsSelector C_Form --> C_Attach + C_Form --> C_McpServersSelector C_Message --> C_Attach + %% MCP Components hierarchy + C_Settings --> C_McpSettings + C_McpSettings --> C_McpServerCard + C_McpServerCard --> C_McpResourceBrowser + C_McpResourceBrowser --> C_McpResourcePreview + %% Components use Hooks C_Form --> H1 C_Message --> H1 & H2 @@ -236,17 +276,29 @@ end C_Screen --> S1 & S2 C_Messages --> S2 C_Message --> S1 & S2 & S3 - C_Form --> S1 & S3 + C_Form --> S1 & S3 & S6 C_Sidebar --> S2 C_ModelsSelector --> S3 & S4 C_Settings --> S5 + C_McpSettings --> S6 + C_McpServerCard --> S6 + C_McpResourceBrowser --> S6 & S7 + C_McpServersSelector --> S6 %% Stores export Reactive State S1 -. exports .-> ChatExports + SA -. exports .-> AgenticExports S2 -. exports .-> ConvExports S3 -. exports .-> ModelsExports S4 -. exports .-> ServerExports S5 -. exports .-> SettingsExports + S6 -. exports .-> MCPExports + S7 -. exports .-> MCPExports + + %% chatStore → agenticStore (agentic loop orchestration) + S1 --> SA + SA --> SV1 + SA --> S6 %% Stores use Services S1 --> SV1 & SV4 @@ -254,40 +306,34 @@ end S3 --> SV2 & SV3 S4 --> SV3 S5 --> SV5 + S6 --> SV6 + S7 --> SV6 %% Services to Storage SV4 --> ST1 ST1 --> ST2 & ST3 SV5 --> ST5 - ST5 --> ST6 & ST7 + ST5 --> ST6 & ST7 & ST8 %% Services to APIs SV1 --> API1 SV2 --> API3 & API4 SV3 --> API2 - %% ChatService → MCP (Agentic Mode) - SV1 --> MCPStoreBox - MCPStoreBox --> MCPClient - SV1 --> MCPSse - MCPSse --> API1 - MCPConfig --> MCPStoreBox - %% MCP → External Servers - MCPClient --> EXT1 & EXT2 + SV6 --> EXT1 & EXT2 %% 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 hookStyle fill:#fff8e1,stroke:#ff8f00,stroke-width:2px 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 mcpStyle fill:#e0f2f1,stroke:#00695c,stroke-width:2px - classDef mcpMethodStyle fill:#b2dfdb,stroke:#00695c,stroke-width:1px classDef externalStyle fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px,stroke-dasharray: 5 5 classDef storageStyle fill:#fce4ec,stroke:#c2185b,stroke-width:2px classDef apiStyle fill:#e3f2fd,stroke:#1565c0,stroke-width:2px @@ -296,26 +342,32 @@ end class C_Sidebar,C_Screen,C_Form,C_Messages,C_Message,C_MessageUser,C_MessageEditForm componentStyle class C_ModelsSelector,C_Settings componentStyle class C_Attach componentStyle - class H1,H2,H3 methodStyle - class LayoutComponents,ChatUIComponents componentGroupStyle - class Hooks storeStyle - class S1,S2,S3,S4,S5 storeStyle - class S1State,S2State,S3State,S4State,S5State stateStyle + class C_McpSettings,C_McpServerCard,C_McpResourceBrowser,C_McpResourcePreview,C_McpServersSelector componentStyle + class H1,H2,H3 hookStyle + class LayoutComponents,ChatUIComponents,MCPComponents componentGroupStyle + class Hooks hookStyle + classDef agenticStyle fill:#e8eaf6,stroke:#283593,stroke-width:2px + classDef agenticMethodStyle fill:#c5cae9,stroke:#283593,stroke-width:1px + + class S1,S2,S3,S4,S5,SA,S6,S7 storeStyle + class S1State,S2State,S3State,S4State,S5State,SAState,S6State,S7State stateStyle class S1Msg,S1Regen,S1Edit,S1Stream,S1LoadState,S1ProcState,S1Error,S1Utils methodStyle - class S2Lifecycle,S2ConvCRUD,S2MsgMgmt,S2Nav,S2Modal,S2Export,S2Utils methodStyle + class SASession,SAConfig,SAFlow methodStyle + class S2Lifecycle,S2ConvCRUD,S2MsgMgmt,S2Nav,S2McpOverrides,S2Export,S2Utils methodStyle class S3Getters,S3Modal,S3Status,S3Fetch,S3Select,S3LoadUnload,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 MCPStoreBox,MCPClient,MCPSse,MCPConfig mcpStyle - class MCPStoreState,MCPStoreLifecycle,MCPStoreExec,MCP1Init,MCP1Tools,MCP1Transport,MCP3Stream,MCP3Parse,MCP4Build,MCP4Parse mcpMethodStyle + class S6Lifecycle,S6Health,S6Servers,S6Tools,S6Prompts methodStyle + class S7Resources,S7Cache,S7Subs,S7Attach methodStyle + class ChatExports,AgenticExports,ConvExports,ModelsExports,ServerExports,SettingsExports,MCPExports reactiveStyle + class SV1,SV2,SV3,SV4,SV5,SV6 serviceStyle + class SV6Transport,SV6Conn,SV6Tools,SV6Prompts,SV6Resources,SV6Complete serviceMStyle class EXT1,EXT2 externalStyle 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 ST1,ST2,ST3,ST5,ST6,ST7,ST8 storageStyle class API1,API2,API3,API4 apiStyle ``` diff --git a/tools/server/webui/docs/flows/chat-flow.md b/tools/server/webui/docs/flows/chat-flow.md index 05e1df385a..296693c6a5 100644 --- a/tools/server/webui/docs/flows/chat-flow.md +++ b/tools/server/webui/docs/flows/chat-flow.md @@ -2,8 +2,10 @@ sequenceDiagram participant UI as 🧩 ChatForm / ChatMessage participant chatStore as 🗄️ chatStore + participant agenticStore as 🗄️ agenticStore participant convStore as 🗄️ conversationsStore participant settingsStore as 🗄️ settingsStore + participant mcpStore as 🗄️ mcpStore participant ChatSvc as ⚙️ ChatService participant DbSvc as ⚙️ DatabaseService participant API as 🌐 /v1/chat/completions @@ -25,6 +27,9 @@ sequenceDiagram Note over convStore: → see conversations-flow.mmd end + chatStore->>mcpStore: consumeResourceAttachmentsAsExtras() + Note right of mcpStore: Converts pending MCP resource
attachments into message extras + chatStore->>chatStore: addMessage("user", content, extras) chatStore->>DbSvc: createMessageBranch(userMsg, parentId) chatStore->>convStore: addMessageToActive(userMsg) @@ -38,7 +43,7 @@ sequenceDiagram deactivate chatStore %% ═══════════════════════════════════════════════════════════════════════════ - Note over UI,API: 🌊 STREAMING + Note over UI,API: 🌊 STREAMING (with agentic flow detection) %% ═══════════════════════════════════════════════════════════════════════════ activate chatStore @@ -52,10 +57,17 @@ sequenceDiagram chatStore->>chatStore: getApiOptions() Note right of chatStore: Merge from settingsStore.config:
temperature, max_tokens, top_p, etc. - chatStore->>ChatSvc: sendMessage(messages, options, signal) + alt agenticConfig.enabled && mcpStore has connected servers + chatStore->>agenticStore: runAgenticFlow(convId, messages, assistantMsg, options, signal) + Note over agenticStore: Multi-turn agentic loop:
1. Call ChatService.sendMessage()
2. If response has tool_calls → execute via mcpStore
3. Append tool results as messages
4. Loop until no more tool_calls or maxTurns
→ see agentic flow details below + agenticStore-->>chatStore: final response with timings + else standard (non-agentic) flow + chatStore->>ChatSvc: sendMessage(messages, options, signal) + end + activate ChatSvc - ChatSvc->>ChatSvc: convertMessageToChatData(messages) + ChatSvc->>ChatSvc: convertDbMessageToApiChatMessageData(messages) Note right of ChatSvc: DatabaseMessage[] → ApiChatMessageData[]
Process attachments (images, PDFs, audio) ChatSvc->>API: POST /v1/chat/completions @@ -63,7 +75,7 @@ sequenceDiagram loop SSE chunks API-->>ChatSvc: data: {"choices":[{"delta":{...}}]} - ChatSvc->>ChatSvc: parseSSEChunk(line) + ChatSvc->>ChatSvc: handleStreamResponse(response) alt content chunk ChatSvc-->>chatStore: onChunk(content) @@ -154,12 +166,15 @@ sequenceDiagram Note over UI,API: ✏️ EDIT USER MESSAGE %% ═══════════════════════════════════════════════════════════════════════════ - UI->>chatStore: editUserMessagePreserveResponses(msgId, newContent) + UI->>chatStore: editMessageWithBranching(msgId, newContent, extras) activate chatStore chatStore->>chatStore: Get parent of target message chatStore->>DbSvc: createMessageBranch(editedMsg, parentId) chatStore->>convStore: refreshActiveMessages() Note right of chatStore: Creates new branch, original preserved + chatStore->>chatStore: createAssistantMessage(editedMsg.id) + chatStore->>chatStore: streamChatCompletion(...) + Note right of chatStore: Automatically regenerates response deactivate chatStore %% ═══════════════════════════════════════════════════════════════════════════ @@ -171,4 +186,43 @@ sequenceDiagram Note right of chatStore: errorDialogState = {type: 'timeout'|'server', message} chatStore->>convStore: removeMessageAtIndex(failedMsgIdx) chatStore->>DbSvc: deleteMessage(failedMsgId) + + %% ═══════════════════════════════════════════════════════════════════════════ + Note over UI,API: 🤖 AGENTIC LOOP (when agenticConfig.enabled) + %% ═══════════════════════════════════════════════════════════════════════════ + + Note over agenticStore: agenticStore.runAgenticFlow(convId, messages, assistantMsg, options, signal) + activate agenticStore + agenticStore->>agenticStore: getSession(convId) or create new + agenticStore->>agenticStore: updateSession(turn: 0, running: true) + + loop executeAgenticLoop (until no tool_calls or maxTurns) + agenticStore->>agenticStore: turn++ + agenticStore->>ChatSvc: sendMessage(messages, options, signal) + ChatSvc->>API: POST /v1/chat/completions + API-->>ChatSvc: response with potential tool_calls + ChatSvc-->>agenticStore: onComplete(content, reasoning, timings, toolCalls) + + alt response has tool_calls + agenticStore->>agenticStore: normalizeToolCalls(toolCalls) + loop for each tool_call + agenticStore->>agenticStore: updateSession(streamingToolCall) + agenticStore->>mcpStore: executeTool(mcpCall, signal) + mcpStore-->>agenticStore: tool result + agenticStore->>agenticStore: extractBase64Attachments(result) + agenticStore->>agenticStore: emitToolCallResult(convId, ...) + agenticStore->>convStore: addMessageToActive(toolResultMsg) + agenticStore->>DbSvc: createMessageBranch(toolResultMsg) + end + agenticStore->>agenticStore: Create new assistantMsg for next turn + Note right of agenticStore: Continue loop with updated messages + else no tool_calls (final response) + agenticStore->>agenticStore: buildFinalTimings(allTurns) + Note right of agenticStore: Break loop, return final response + end + end + + agenticStore->>agenticStore: updateSession(running: false) + agenticStore-->>chatStore: final content, timings, model + deactivate agenticStore ``` diff --git a/tools/server/webui/docs/flows/conversations-flow.md b/tools/server/webui/docs/flows/conversations-flow.md index 185ed16e0c..bd2309bc03 100644 --- a/tools/server/webui/docs/flows/conversations-flow.md +++ b/tools/server/webui/docs/flows/conversations-flow.md @@ -6,7 +6,7 @@ sequenceDiagram participant DbSvc as ⚙️ DatabaseService participant IDB as 💾 IndexedDB - Note over convStore: State:
conversations: DatabaseConversation[]
activeConversation: DatabaseConversation | null
activeMessages: DatabaseMessage[]
isInitialized: boolean
usedModalities: $derived({vision, audio}) + Note over convStore: State:
conversations: DatabaseConversation[]
activeConversation: DatabaseConversation | null
activeMessages: DatabaseMessage[]
isInitialized: boolean
pendingMcpServerOverrides: Map<string, McpServerOverride> %% ═══════════════════════════════════════════════════════════════════════════ Note over UI,IDB: 🚀 INITIALIZATION @@ -37,6 +37,13 @@ sequenceDiagram convStore->>convStore: conversations.unshift(conversation) convStore->>convStore: activeConversation = $state(conversation) convStore->>convStore: activeMessages = $state([]) + + alt pendingMcpServerOverrides has entries + loop each pending override + convStore->>DbSvc: Store MCP server override for new conversation + end + convStore->>convStore: clearPendingMcpServerOverrides() + end deactivate convStore %% ═══════════════════════════════════════════════════════════════════════════ @@ -58,8 +65,7 @@ sequenceDiagram Note right of convStore: Filter to show only current branch path convStore->>convStore: activeMessages = $state(filtered) - convStore->>chatStore: syncLoadingStateForChat(convId) - Note right of chatStore: Sync isLoading/currentResponse if streaming + Note right of convStore: Route (+page.svelte) then calls:
chatStore.syncLoadingStateForChat(convId) deactivate convStore %% ═══════════════════════════════════════════════════════════════════════════ @@ -121,16 +127,36 @@ sequenceDiagram end deactivate convStore + UI->>convStore: deleteAll() + activate convStore + convStore->>DbSvc: Delete all conversations and messages + convStore->>convStore: conversations = [] + convStore->>convStore: clearActiveConversation() + deactivate convStore + %% ═══════════════════════════════════════════════════════════════════════════ - Note over UI,IDB: 📊 MODALITY TRACKING + Note over UI,IDB: � MCP SERVER PER-CHAT OVERRIDES %% ═══════════════════════════════════════════════════════════════════════════ - Note over convStore: usedModalities = $derived.by(() => {
calculateModalitiesFromMessages(activeMessages)
}) + Note over convStore: Conversations can override which MCP servers are enabled. + Note over convStore: Uses pendingMcpServerOverrides before conversation
is created, then persists to conversation metadata. - Note over convStore: Scans activeMessages for attachments:
- IMAGE → vision: true
- PDF (processedAsImages) → vision: true
- AUDIO → audio: true + UI->>convStore: setMcpServerOverride(convId, serverName, override) + Note right of convStore: override = {enabled: boolean} - UI->>convStore: getModalitiesUpToMessage(msgId) - Note right of convStore: Used for regeneration validation
Only checks messages BEFORE target + UI->>convStore: toggleMcpServerForChat(convId, serverName, enabled) + activate convStore + convStore->>convStore: setMcpServerOverride(convId, serverName, {enabled}) + deactivate convStore + + UI->>convStore: isMcpServerEnabledForChat(convId, serverName) + Note right of convStore: Check override → fall back to global MCP config + + UI->>convStore: getAllMcpServerOverrides(convId) + Note right of convStore: Returns all overrides for a conversation + + UI->>convStore: removeMcpServerOverride(convId, serverName) + UI->>convStore: getMcpServerOverride(convId, serverName) %% ═══════════════════════════════════════════════════════════════════════════ Note over UI,IDB: 📤 EXPORT / 📥 IMPORT @@ -148,8 +174,10 @@ sequenceDiagram UI->>convStore: importConversations(file) activate convStore convStore->>convStore: Parse JSON file + convStore->>convStore: importConversationsData(parsed) convStore->>DbSvc: importConversations(parsed) - DbSvc->>IDB: Bulk INSERT conversations + messages + Note right of DbSvc: Skips duplicate conversations
(checks existing by ID) + DbSvc->>IDB: INSERT conversations + messages (skip existing) convStore->>convStore: loadConversations() deactivate convStore ``` diff --git a/tools/server/webui/docs/flows/database-flow.md b/tools/server/webui/docs/flows/database-flow.md index 50f8284e3c..38cd6941cf 100644 --- a/tools/server/webui/docs/flows/database-flow.md +++ b/tools/server/webui/docs/flows/database-flow.md @@ -66,6 +66,14 @@ sequenceDiagram DbSvc-->>Store: rootMessageId deactivate DbSvc + Store->>DbSvc: createSystemMessage(convId, content, parentId) + activate DbSvc + DbSvc->>DbSvc: Create message {role: "system", parent: parentId} + DbSvc->>Dexie: db.messages.add(systemMsg) + Dexie->>IDB: INSERT + DbSvc-->>Store: DatabaseMessage + deactivate DbSvc + Store->>DbSvc: createMessageBranch(message, parentId) activate DbSvc DbSvc->>DbSvc: Generate UUID for new message @@ -116,6 +124,13 @@ sequenceDiagram end DbSvc->>Dexie: db.messages.delete(msgId) Dexie->>IDB: DELETE target message + + alt target message has a parent + DbSvc->>Dexie: db.messages.get(parentId) + DbSvc->>DbSvc: parent.children.filter(id !== msgId) + DbSvc->>Dexie: db.messages.update(parentId, {children}) + Note right of DbSvc: Remove deleted message from parent's children[] + end deactivate DbSvc %% ═══════════════════════════════════════════════════════════════════════════ @@ -125,12 +140,16 @@ sequenceDiagram Store->>DbSvc: importConversations(data) activate DbSvc loop each conversation in data - DbSvc->>DbSvc: Generate new UUIDs (avoid conflicts) - DbSvc->>Dexie: db.conversations.add(conversation) - Dexie->>IDB: INSERT conversation - loop each message - DbSvc->>Dexie: db.messages.add(message) - Dexie->>IDB: INSERT message + DbSvc->>Dexie: db.conversations.get(conv.id) + alt conversation already exists + Note right of DbSvc: Skip duplicate (keep existing) + else conversation is new + DbSvc->>Dexie: db.conversations.add(conversation) + Dexie->>IDB: INSERT conversation + loop each message + DbSvc->>Dexie: db.messages.add(message) + Dexie->>IDB: INSERT message + end end end deactivate DbSvc diff --git a/tools/server/webui/docs/flows/mcp-flow.md b/tools/server/webui/docs/flows/mcp-flow.md new file mode 100644 index 0000000000..ee08419e7e --- /dev/null +++ b/tools/server/webui/docs/flows/mcp-flow.md @@ -0,0 +1,226 @@ +```mermaid +sequenceDiagram + participant UI as 🧩 McpServersSettings / ChatForm + participant chatStore as 🗄️ chatStore + participant mcpStore as 🗄️ mcpStore + participant mcpResStore as 🗄️ mcpResourceStore + participant convStore as 🗄️ conversationsStore + participant MCPSvc as ⚙️ MCPService + participant LS as 💾 LocalStorage + participant ExtMCP as 🔌 External MCP Server + + Note over mcpStore: State:
isInitializing, error
toolCount, connectedServers
healthChecks (Map)
connections (Map)
toolsIndex (Map)
serverConfigs (Map) + + Note over mcpResStore: State:
serverResources (Map)
cachedResources (Map)
subscriptions (Map)
attachments[] + + %% ═══════════════════════════════════════════════════════════════════════════ + Note over UI,ExtMCP: 🚀 INITIALIZATION (App Startup) + %% ═══════════════════════════════════════════════════════════════════════════ + + UI->>mcpStore: ensureInitialized() + activate mcpStore + + mcpStore->>LS: get(MCP_SERVERS_LOCALSTORAGE_KEY) + LS-->>mcpStore: MCPServerSettingsEntry[] + + mcpStore->>mcpStore: parseServerSettings(servers) + Note right of mcpStore: Filter enabled servers
Build MCPServerConfig objects
Per-chat overrides checked via convStore + + loop For each enabled server + mcpStore->>mcpStore: runHealthCheck(serverId) + mcpStore->>mcpStore: updateHealthCheck(id, CONNECTING) + + mcpStore->>MCPSvc: connect(serverName, config, clientInfo, capabilities, onPhase) + activate MCPSvc + + MCPSvc->>MCPSvc: createTransport(config) + Note right of MCPSvc: WebSocket / StreamableHTTP / SSE
with optional CORS proxy + + MCPSvc->>ExtMCP: Transport handshake + ExtMCP-->>MCPSvc: Connection established + + MCPSvc->>ExtMCP: Initialize request + Note right of ExtMCP: Exchange capabilities
Server info, protocol version + + ExtMCP-->>MCPSvc: InitializeResult (serverInfo, capabilities) + + MCPSvc->>ExtMCP: listTools() + ExtMCP-->>MCPSvc: Tool[] + + MCPSvc-->>mcpStore: MCPConnection + deactivate MCPSvc + + mcpStore->>mcpStore: connections.set(serverName, connection) + mcpStore->>mcpStore: indexTools(connection.tools, serverName) + Note right of mcpStore: toolsIndex.set(toolName, serverName)
Handle name conflicts with prefixes + + mcpStore->>mcpStore: updateHealthCheck(id, SUCCESS) + mcpStore->>mcpStore: _connectedServers.push(serverName) + + alt Server supports resources + mcpStore->>MCPSvc: listAllResources(connection) + MCPSvc->>ExtMCP: listResources() + ExtMCP-->>MCPSvc: MCPResource[] + MCPSvc-->>mcpStore: resources + + mcpStore->>MCPSvc: listAllResourceTemplates(connection) + MCPSvc->>ExtMCP: listResourceTemplates() + ExtMCP-->>MCPSvc: MCPResourceTemplate[] + MCPSvc-->>mcpStore: templates + + mcpStore->>mcpResStore: setServerResources(serverName, resources, templates) + end + end + + mcpStore->>mcpStore: _isInitializing = false + deactivate mcpStore + + %% ═══════════════════════════════════════════════════════════════════════════ + Note over UI,ExtMCP: 🔧 TOOL EXECUTION (Chat with Tools) + %% ═══════════════════════════════════════════════════════════════════════════ + + UI->>mcpStore: executeTool(mcpCall: MCPToolCall, signal?) + activate mcpStore + + mcpStore->>mcpStore: toolsIndex.get(mcpCall.function.name) + Note right of mcpStore: Resolve serverName from toolsIndex
MCPToolCall = {id, type, function: {name, arguments}} + + mcpStore->>mcpStore: acquireConnection() + Note right of mcpStore: activeFlowCount++
Prevent shutdown during execution + + mcpStore->>mcpStore: connection = connections.get(serverName) + + mcpStore->>MCPSvc: callTool(connection, {name, arguments}, signal) + activate MCPSvc + + MCPSvc->>MCPSvc: throwIfAborted(signal) + MCPSvc->>ExtMCP: callTool(name, arguments) + + alt Tool execution success + ExtMCP-->>MCPSvc: ToolCallResult (content, isError) + MCPSvc->>MCPSvc: formatToolResult(result) + Note right of MCPSvc: Handle text, image (base64),
embedded resource content + MCPSvc-->>mcpStore: ToolExecutionResult + else Tool execution error + ExtMCP-->>MCPSvc: Error + MCPSvc-->>mcpStore: throw Error + else Aborted + MCPSvc-->>mcpStore: throw AbortError + end + + deactivate MCPSvc + + mcpStore->>mcpStore: releaseConnection() + Note right of mcpStore: activeFlowCount-- + + mcpStore-->>UI: ToolExecutionResult + deactivate mcpStore + + %% ═══════════════════════════════════════════════════════════════════════════ + Note over UI,ExtMCP: � RESOURCE ATTACHMENT CONSUMPTION + %% ═══════════════════════════════════════════════════════════════════════════ + + chatStore->>mcpStore: consumeResourceAttachmentsAsExtras() + activate mcpStore + mcpStore->>mcpResStore: getAttachments() + mcpResStore-->>mcpStore: MCPResourceAttachment[] + mcpStore->>mcpStore: Convert attachments to message extras + mcpStore->>mcpResStore: clearAttachments() + mcpStore-->>chatStore: MessageExtra[] (for user message) + deactivate mcpStore + + %% ═══════════════════════════════════════════════════════════════════════════ + Note over UI,ExtMCP: �📝 PROMPT OPERATIONS + %% ═══════════════════════════════════════════════════════════════════════════ + + UI->>mcpStore: getAllPrompts() + activate mcpStore + + loop For each connected server with prompts capability + mcpStore->>MCPSvc: listPrompts(connection) + MCPSvc->>ExtMCP: listPrompts() + ExtMCP-->>MCPSvc: Prompt[] + MCPSvc-->>mcpStore: prompts + end + + mcpStore-->>UI: MCPPromptInfo[] (with serverName) + deactivate mcpStore + + UI->>mcpStore: getPrompt(serverName, promptName, args?) + activate mcpStore + + mcpStore->>MCPSvc: getPrompt(connection, name, args) + MCPSvc->>ExtMCP: getPrompt({name, arguments}) + ExtMCP-->>MCPSvc: GetPromptResult (messages) + MCPSvc-->>mcpStore: GetPromptResult + + mcpStore-->>UI: GetPromptResult + deactivate mcpStore + + %% ═══════════════════════════════════════════════════════════════════════════ + Note over UI,ExtMCP: 📁 RESOURCE OPERATIONS + %% ═══════════════════════════════════════════════════════════════════════════ + + UI->>mcpResStore: addAttachment(resourceInfo) + activate mcpResStore + mcpResStore->>mcpResStore: Create MCPResourceAttachment (loading: true) + mcpResStore-->>UI: attachment + + UI->>mcpStore: readResource(serverName, uri) + activate mcpStore + + mcpStore->>MCPSvc: readResource(connection, uri) + MCPSvc->>ExtMCP: readResource({uri}) + ExtMCP-->>MCPSvc: MCPReadResourceResult (contents) + MCPSvc-->>mcpStore: contents + + mcpStore-->>UI: MCPResourceContent[] + deactivate mcpStore + + UI->>mcpResStore: updateAttachmentContent(attachmentId, content) + mcpResStore->>mcpResStore: cacheResourceContent(resource, content) + deactivate mcpResStore + + %% ═══════════════════════════════════════════════════════════════════════════ + Note over UI,ExtMCP: 🔄 AUTO-RECONNECTION + %% ═══════════════════════════════════════════════════════════════════════════ + + Note over mcpStore: On WebSocket close or connection error: + mcpStore->>mcpStore: autoReconnect(serverName, attempt) + activate mcpStore + + mcpStore->>mcpStore: Calculate backoff delay + Note right of mcpStore: delay = min(30s, 1s * 2^attempt) + + mcpStore->>mcpStore: Wait for delay + mcpStore->>mcpStore: reconnectServer(serverName) + + alt Reconnection success + mcpStore->>mcpStore: updateHealthCheck(id, SUCCESS) + else Max attempts reached + mcpStore->>mcpStore: updateHealthCheck(id, ERROR) + end + deactivate mcpStore + + %% ═══════════════════════════════════════════════════════════════════════════ + Note over UI,ExtMCP: 🛑 SHUTDOWN + %% ═══════════════════════════════════════════════════════════════════════════ + + UI->>mcpStore: shutdown() + activate mcpStore + + mcpStore->>mcpStore: Wait for activeFlowCount == 0 + + loop For each connection + mcpStore->>MCPSvc: disconnect(connection) + MCPSvc->>MCPSvc: transport.onclose = undefined + MCPSvc->>ExtMCP: close() + end + + mcpStore->>mcpStore: connections.clear() + mcpStore->>mcpStore: toolsIndex.clear() + mcpStore->>mcpStore: _connectedServers = [] + + mcpStore->>mcpResStore: clear() + deactivate mcpStore +``` diff --git a/tools/server/webui/docs/flows/settings-flow.md b/tools/server/webui/docs/flows/settings-flow.md index 474aef01b0..40ad3bd94d 100644 --- a/tools/server/webui/docs/flows/settings-flow.md +++ b/tools/server/webui/docs/flows/settings-flow.md @@ -49,14 +49,20 @@ sequenceDiagram settingsStore->>serverStore: defaultParams serverStore-->>settingsStore: {temperature, top_p, top_k, ...} - settingsStore->>ParamSvc: extractServerDefaults(defaultParams) - ParamSvc-->>settingsStore: Record + loop each SYNCABLE_PARAMETER + alt key NOT in userOverrides + settingsStore->>settingsStore: config[key] = serverDefault[key] + Note right of settingsStore: Non-overridden params adopt server default + else key in userOverrides + Note right of settingsStore: Keep user value, skip server default + end + end - settingsStore->>ParamSvc: mergeWithServerDefaults(config, serverDefaults) - Note right of ParamSvc: For each syncable parameter:
- If NOT in userOverrides → use server default
- If in userOverrides → keep user value - ParamSvc-->>settingsStore: mergedConfig + alt serverStore.props has webuiSettings + settingsStore->>settingsStore: Apply webuiSettings from server + Note right of settingsStore: Server-provided UI settings
(e.g. showRawOutputSwitch) + end - settingsStore->>settingsStore: config = mergedConfig settingsStore->>settingsStore: saveConfig() deactivate settingsStore @@ -67,11 +73,18 @@ sequenceDiagram UI->>settingsStore: updateConfig(key, value) activate settingsStore settingsStore->>settingsStore: config[key] = value - settingsStore->>settingsStore: userOverrides.add(key) - Note right of settingsStore: Mark as user-modified (won't be overwritten by server) + + alt value matches server default for key + settingsStore->>settingsStore: userOverrides.delete(key) + Note right of settingsStore: Matches server default, remove override + else value differs from server default + settingsStore->>settingsStore: userOverrides.add(key) + Note right of settingsStore: Mark as user-modified (won't be overwritten) + end + settingsStore->>settingsStore: saveConfig() - settingsStore->>LS: set("llama-config", config) - settingsStore->>LS: set("llama-userOverrides", [...userOverrides]) + settingsStore->>LS: set(CONFIG_LOCALSTORAGE_KEY, config) + settingsStore->>LS: set(USER_OVERRIDES_LOCALSTORAGE_KEY, [...userOverrides]) deactivate settingsStore UI->>settingsStore: updateMultipleConfig({key1: val1, key2: val2}) @@ -88,10 +101,9 @@ sequenceDiagram UI->>settingsStore: resetConfig() activate settingsStore - settingsStore->>settingsStore: config = SETTING_CONFIG_DEFAULT + settingsStore->>settingsStore: config = {...SETTING_CONFIG_DEFAULT} settingsStore->>settingsStore: userOverrides.clear() - settingsStore->>settingsStore: syncWithServerDefaults() - Note right of settingsStore: Apply server defaults for syncable params + Note right of settingsStore: All params reset to defaults
Next syncWithServerDefaults will adopt server values settingsStore->>settingsStore: saveConfig() deactivate settingsStore