```mermaid 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 Note over chatStore: State:
isLoading, currentResponse
errorDialogState, activeProcessingState
chatLoadingStates (Map)
chatStreamingStates (Map)
abortControllers (Map)
processingStates (Map) %% ═══════════════════════════════════════════════════════════════════════════ Note over UI,API: đŸ’Ŧ SEND MESSAGE %% ═══════════════════════════════════════════════════════════════════════════ UI->>chatStore: sendMessage(content, extras) activate chatStore chatStore->>chatStore: setChatLoading(convId, true) chatStore->>chatStore: clearChatStreaming(convId) alt no active conversation chatStore->>convStore: createConversation() 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) chatStore->>convStore: updateCurrentNode(userMsg.id) chatStore->>chatStore: createAssistantMessage(userMsg.id) chatStore->>DbSvc: createMessageBranch(assistantMsg, userMsg.id) chatStore->>convStore: addMessageToActive(assistantMsg) chatStore->>chatStore: streamChatCompletion(messages, assistantMsg) deactivate chatStore %% ═══════════════════════════════════════════════════════════════════════════ Note over UI,API: 🌊 STREAMING (with agentic flow detection) %% ═══════════════════════════════════════════════════════════════════════════ activate chatStore chatStore->>chatStore: startStreaming() Note right of chatStore: isStreamingActive = true chatStore->>chatStore: setActiveProcessingConversation(convId) chatStore->>chatStore: getOrCreateAbortController(convId) Note right of chatStore: abortControllers.set(convId, new AbortController()) chatStore->>chatStore: getApiOptions() Note right of chatStore: Merge from settingsStore.config:
temperature, max_tokens, top_p, etc. 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: convertDbMessageToApiChatMessageData(messages) Note right of ChatSvc: DatabaseMessage[] → ApiChatMessageData[]
Process attachments (images, PDFs, audio) ChatSvc->>API: POST /v1/chat/completions Note right of API: {messages, model?, stream: true, ...params} loop SSE chunks API-->>ChatSvc: data: {"choices":[{"delta":{...}}]} ChatSvc->>ChatSvc: handleStreamResponse(response) alt content chunk ChatSvc-->>chatStore: onChunk(content) chatStore->>chatStore: setChatStreaming(convId, response, msgId) Note right of chatStore: currentResponse = $state(accumulated) chatStore->>convStore: updateMessageAtIndex(idx, {content}) end alt reasoning chunk ChatSvc-->>chatStore: onReasoningChunk(reasoning) chatStore->>convStore: updateMessageAtIndex(idx, {thinking}) end alt tool_calls chunk ChatSvc-->>chatStore: onToolCallChunk(toolCalls) chatStore->>convStore: updateMessageAtIndex(idx, {toolCalls}) end alt model info ChatSvc-->>chatStore: onModel(modelName) chatStore->>chatStore: recordModel(modelName) chatStore->>DbSvc: updateMessage(msgId, {model}) end alt timings (during stream) ChatSvc-->>chatStore: onTimings(timings, promptProgress) chatStore->>chatStore: updateProcessingStateFromTimings() end chatStore-->>UI: reactive $state update end API-->>ChatSvc: data: [DONE] ChatSvc-->>chatStore: onComplete(content, reasoning, timings, toolCalls) deactivate ChatSvc chatStore->>chatStore: stopStreaming() chatStore->>DbSvc: updateMessage(msgId, {content, timings, model}) chatStore->>convStore: updateCurrentNode(msgId) chatStore->>chatStore: setChatLoading(convId, false) chatStore->>chatStore: clearChatStreaming(convId) chatStore->>chatStore: clearProcessingState(convId) deactivate chatStore %% ═══════════════════════════════════════════════════════════════════════════ Note over UI,API: âšī¸ STOP GENERATION %% ═══════════════════════════════════════════════════════════════════════════ UI->>chatStore: stopGeneration() activate chatStore chatStore->>chatStore: savePartialResponseIfNeeded(convId) Note right of chatStore: Save currentResponse to DB if non-empty chatStore->>chatStore: abortControllers.get(convId).abort() Note right of chatStore: fetch throws AbortError → caught by isAbortError() chatStore->>chatStore: stopStreaming() chatStore->>chatStore: setChatLoading(convId, false) chatStore->>chatStore: clearChatStreaming(convId) chatStore->>chatStore: clearProcessingState(convId) deactivate chatStore %% ═══════════════════════════════════════════════════════════════════════════ Note over UI,API: 🔁 REGENERATE %% ═══════════════════════════════════════════════════════════════════════════ UI->>chatStore: regenerateMessageWithBranching(msgId, model?) activate chatStore chatStore->>convStore: findMessageIndex(msgId) chatStore->>chatStore: Get parent of target message chatStore->>chatStore: createAssistantMessage(parentId) chatStore->>DbSvc: createMessageBranch(newAssistantMsg, parentId) chatStore->>convStore: refreshActiveMessages() Note right of chatStore: Same streaming flow chatStore->>chatStore: streamChatCompletion(...) deactivate chatStore %% ═══════════════════════════════════════════════════════════════════════════ Note over UI,API: âžĄī¸ CONTINUE %% ═══════════════════════════════════════════════════════════════════════════ UI->>chatStore: continueAssistantMessage(msgId) activate chatStore chatStore->>chatStore: Get existing content from message chatStore->>chatStore: streamChatCompletion(..., existingContent) Note right of chatStore: Appends to existing message content deactivate chatStore %% ═══════════════════════════════════════════════════════════════════════════ Note over UI,API: âœī¸ EDIT USER MESSAGE %% ═══════════════════════════════════════════════════════════════════════════ 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 %% ═══════════════════════════════════════════════════════════════════════════ Note over UI,API: ❌ ERROR HANDLING %% ═══════════════════════════════════════════════════════════════════════════ Note over chatStore: On stream error (non-abort): chatStore->>chatStore: showErrorDialog(type, message) 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 ```