| ```mermaid | |
| sequenceDiagram | |
| participant UI as π§© ChatSidebar / ChatScreen | |
| participant convStore as ποΈ conversationsStore | |
| participant chatStore as ποΈ chatStore | |
| participant DbSvc as βοΈ DatabaseService | |
| participant IDB as πΎ IndexedDB | |
| Note over convStore: State:<br/>conversations: DatabaseConversation[]<br/>activeConversation: DatabaseConversation | null<br/>activeMessages: DatabaseMessage[]<br/>isInitialized: boolean<br/>usedModalities: $derived({vision, audio}) | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Note over UI,IDB: π INITIALIZATION | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Note over convStore: Auto-initialized in constructor (browser only) | |
| convStore->>convStore: initialize() | |
| activate convStore | |
| convStore->>convStore: loadConversations() | |
| convStore->>DbSvc: getAllConversations() | |
| DbSvc->>IDB: SELECT * FROM conversations ORDER BY lastModified DESC | |
| IDB-->>DbSvc: Conversation[] | |
| DbSvc-->>convStore: conversations | |
| convStore->>convStore: conversations = $state(data) | |
| convStore->>convStore: isInitialized = true | |
| deactivate convStore | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Note over UI,IDB: β CREATE CONVERSATION | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| UI->>convStore: createConversation(name?) | |
| activate convStore | |
| convStore->>DbSvc: createConversation(name || "New Chat") | |
| DbSvc->>IDB: INSERT INTO conversations | |
| IDB-->>DbSvc: conversation {id, name, lastModified, currNode: ""} | |
| DbSvc-->>convStore: conversation | |
| convStore->>convStore: conversations.unshift(conversation) | |
| convStore->>convStore: activeConversation = $state(conversation) | |
| convStore->>convStore: activeMessages = $state([]) | |
| deactivate convStore | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Note over UI,IDB: π LOAD CONVERSATION | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| UI->>convStore: loadConversation(convId) | |
| activate convStore | |
| convStore->>DbSvc: getConversation(convId) | |
| DbSvc->>IDB: SELECT * FROM conversations WHERE id = ? | |
| IDB-->>DbSvc: conversation | |
| convStore->>convStore: activeConversation = $state(conversation) | |
| convStore->>convStore: refreshActiveMessages() | |
| convStore->>DbSvc: getConversationMessages(convId) | |
| DbSvc->>IDB: SELECT * FROM messages WHERE convId = ? | |
| IDB-->>DbSvc: allMessages[] | |
| convStore->>convStore: filterByLeafNodeId(allMessages, currNode) | |
| 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 | |
| deactivate convStore | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Note over UI,IDB: π³ MESSAGE BRANCHING MODEL | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Note over IDB: Message Tree Structure:<br/>- Each message has parent (null for root)<br/>- Each message has children[] array<br/>- Conversation.currNode points to active leaf<br/>- filterByLeafNodeId() traverses from root to currNode | |
| rect rgb(240, 240, 255) | |
| Note over convStore: Example Branch Structure: | |
| Note over convStore: root β user1 β assistant1 β user2 β assistant2a (currNode)<br/> β assistant2b (alt branch) | |
| end | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Note over UI,IDB: βοΈ BRANCH NAVIGATION | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| UI->>convStore: navigateToSibling(msgId, direction) | |
| activate convStore | |
| convStore->>convStore: Find message in activeMessages | |
| convStore->>convStore: Get parent message | |
| convStore->>convStore: Find sibling in parent.children[] | |
| convStore->>convStore: findLeafNode(siblingId, allMessages) | |
| Note right of convStore: Navigate to leaf of sibling branch | |
| convStore->>convStore: updateCurrentNode(leafId) | |
| convStore->>DbSvc: updateCurrentNode(convId, leafId) | |
| DbSvc->>IDB: UPDATE conversations SET currNode = ? | |
| convStore->>convStore: refreshActiveMessages() | |
| deactivate convStore | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Note over UI,IDB: π UPDATE CONVERSATION | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| UI->>convStore: updateConversationName(convId, newName) | |
| activate convStore | |
| convStore->>DbSvc: updateConversation(convId, {name: newName}) | |
| DbSvc->>IDB: UPDATE conversations SET name = ? | |
| convStore->>convStore: Update in conversations array | |
| deactivate convStore | |
| Note over convStore: Auto-title update (after first response): | |
| convStore->>convStore: updateConversationTitleWithConfirmation() | |
| convStore->>convStore: titleUpdateConfirmationCallback?() | |
| Note right of convStore: Shows dialog if title would change | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Note over UI,IDB: ποΈ DELETE CONVERSATION | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| UI->>convStore: deleteConversation(convId) | |
| activate convStore | |
| convStore->>DbSvc: deleteConversation(convId) | |
| DbSvc->>IDB: DELETE FROM conversations WHERE id = ? | |
| DbSvc->>IDB: DELETE FROM messages WHERE convId = ? | |
| convStore->>convStore: conversations.filter(c => c.id !== convId) | |
| alt deleted active conversation | |
| convStore->>convStore: clearActiveConversation() | |
| end | |
| deactivate convStore | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Note over UI,IDB: π MODALITY TRACKING | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Note over convStore: usedModalities = $derived.by(() => {<br/> calculateModalitiesFromMessages(activeMessages)<br/>}) | |
| Note over convStore: Scans activeMessages for attachments:<br/>- IMAGE β vision: true<br/>- PDF (processedAsImages) β vision: true<br/>- AUDIO β audio: true | |
| UI->>convStore: getModalitiesUpToMessage(msgId) | |
| Note right of convStore: Used for regeneration validation<br/>Only checks messages BEFORE target | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Note over UI,IDB: π€ EXPORT / π₯ IMPORT | |
| %% βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| UI->>convStore: exportAllConversations() | |
| activate convStore | |
| convStore->>DbSvc: getAllConversations() | |
| loop each conversation | |
| convStore->>DbSvc: getConversationMessages(convId) | |
| end | |
| convStore->>convStore: triggerDownload(JSON blob) | |
| deactivate convStore | |
| UI->>convStore: importConversations(file) | |
| activate convStore | |
| convStore->>convStore: Parse JSON file | |
| convStore->>DbSvc: importConversations(parsed) | |
| DbSvc->>IDB: Bulk INSERT conversations + messages | |
| convStore->>convStore: loadConversations() | |
| deactivate convStore | |
| ``` | |