refactor: DatabaseStore -> DatabaseService

This commit is contained in:
Aleksander Grygier 2025-11-25 08:08:32 +01:00
parent 7db3d87434
commit ccd6c27183
5 changed files with 84 additions and 82 deletions

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { ChatMessage } from '$lib/components/app'; import { ChatMessage } from '$lib/components/app';
import { DatabaseStore } from '$lib/stores/database'; import { DatabaseService } from '$lib/services/database';
import { import {
activeConversation, activeConversation,
continueAssistantMessage, continueAssistantMessage,
@ -27,7 +27,7 @@
const conversation = activeConversation(); const conversation = activeConversation();
if (conversation) { if (conversation) {
DatabaseStore.getConversationMessages(conversation.id).then((messages) => { DatabaseService.getConversationMessages(conversation.id).then((messages) => {
allConversationMessages = messages; allConversationMessages = messages;
}); });
} else { } else {

View File

@ -2,7 +2,7 @@
import { Download, Upload } from '@lucide/svelte'; import { Download, Upload } from '@lucide/svelte';
import { Button } from '$lib/components/ui/button'; import { Button } from '$lib/components/ui/button';
import { DialogConversationSelection } from '$lib/components/app'; import { DialogConversationSelection } from '$lib/components/app';
import { DatabaseStore } from '$lib/stores/database'; import { DatabaseService } from '$lib/services/database';
import type { ExportedConversations } from '$lib/types/database'; import type { ExportedConversations } from '$lib/types/database';
import { createMessageCountMap } from '$lib/utils/conversation-utils'; import { createMessageCountMap } from '$lib/utils/conversation-utils';
import { chatStore } from '$lib/stores/chat.svelte'; import { chatStore } from '$lib/stores/chat.svelte';
@ -22,7 +22,7 @@
async function handleExportClick() { async function handleExportClick() {
try { try {
const allConversations = await DatabaseStore.getAllConversations(); const allConversations = await DatabaseService.getAllConversations();
if (allConversations.length === 0) { if (allConversations.length === 0) {
alert('No conversations to export'); alert('No conversations to export');
return; return;
@ -30,7 +30,7 @@
const conversationsWithMessages = await Promise.all( const conversationsWithMessages = await Promise.all(
allConversations.map(async (conv) => { allConversations.map(async (conv) => {
const messages = await DatabaseStore.getConversationMessages(conv.id); const messages = await DatabaseService.getConversationMessages(conv.id);
return { conv, messages }; return { conv, messages };
}) })
); );
@ -48,7 +48,7 @@
try { try {
const allData: ExportedConversations = await Promise.all( const allData: ExportedConversations = await Promise.all(
selectedConversations.map(async (conv) => { selectedConversations.map(async (conv) => {
const messages = await DatabaseStore.getConversationMessages(conv.id); const messages = await DatabaseService.getConversationMessages(conv.id);
return { conv: $state.snapshot(conv), messages: $state.snapshot(messages) }; return { conv: $state.snapshot(conv), messages: $state.snapshot(messages) };
}) })
); );
@ -136,7 +136,7 @@
.snapshot(fullImportData) .snapshot(fullImportData)
.filter((item) => selectedIds.has(item.conv.id)); .filter((item) => selectedIds.has(item.conv.id));
await DatabaseStore.importConversations(selectedData); await DatabaseService.importConversations(selectedData);
await chatStore.loadConversations(); await chatStore.loadConversations();

View File

@ -40,7 +40,7 @@ import type { SettingsChatServiceOptions } from '$lib/types/settings';
* - **ChatStore**: Stateful orchestration and UI state management * - **ChatStore**: Stateful orchestration and UI state management
* - Uses ChatService for all AI model communication * - Uses ChatService for all AI model communication
* - Manages conversation state, message history, and UI reactivity * - Manages conversation state, message history, and UI reactivity
* - Coordinates with DatabaseStore for persistence * - Coordinates with DatabaseService for persistence
* - Handles complex workflows like branching and regeneration * - Handles complex workflows like branching and regeneration
* *
* **Key Responsibilities:** * **Key Responsibilities:**

View File

@ -18,23 +18,23 @@ class LlamacppDatabase extends Dexie {
const db = new LlamacppDatabase(); const db = new LlamacppDatabase();
/** /**
* DatabaseStore - Persistent data layer for conversation and message management * DatabaseService - Persistent data layer for conversation and message management
* *
* This service provides a comprehensive data access layer built on IndexedDB using Dexie. * This service provides a comprehensive data access layer built on IndexedDB using Dexie.
* It handles all persistent storage operations for conversations, messages, and application settings * It handles all persistent storage operations for conversations, messages, and application settings
* with support for complex conversation branching and message threading. * with support for complex conversation branching and message threading.
* *
* **Architecture & Relationships:** * **Architecture & Relationships:**
* - **DatabaseStore** (this class): Stateless data persistence layer * - **DatabaseService** (this class): Stateless data persistence layer
* - Manages IndexedDB operations through Dexie ORM * - Manages IndexedDB operations through Dexie ORM
* - Handles conversation and message CRUD operations * - Handles conversation and message CRUD operations
* - Supports complex branching with parent-child relationships * - Supports complex branching with parent-child relationships
* - Provides transaction safety for multi-table operations * - Provides transaction safety for multi-table operations
* *
* - **ChatStore**: Primary consumer for conversation state management * - **ChatStore & ConversationsStore**: Primary consumers for state management
* - Uses DatabaseStore for all persistence operations * - Use DatabaseService for all persistence operations
* - Coordinates UI state with database state * - Coordinate UI state with database state
* - Handles conversation lifecycle and message branching * - Handle conversation lifecycle and message branching
* *
* **Key Features:** * **Key Features:**
* - **Conversation Management**: Create, read, update, delete conversations * - **Conversation Management**: Create, read, update, delete conversations
@ -54,7 +54,7 @@ const db = new LlamacppDatabase();
*/ */
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
export class DatabaseStore { export class DatabaseService {
/** /**
* Adds a new message to the database. * Adds a new message to the database.
* *

View File

@ -1,4 +1,4 @@
import { DatabaseStore } from '$lib/stores/database'; import { DatabaseService } from '$lib/services/database';
import { chatService, slotsService } from '$lib/services'; import { chatService, slotsService } from '$lib/services';
import { config } from '$lib/stores/settings.svelte'; import { config } from '$lib/stores/settings.svelte';
import { normalizeModelName } from '$lib/utils/model-names'; import { normalizeModelName } from '$lib/utils/model-names';
@ -10,15 +10,15 @@ import { SvelteMap } from 'svelte/reactivity';
import type { ExportedConversations } from '$lib/types/database'; import type { ExportedConversations } from '$lib/types/database';
/** /**
* ChatStore - Central state management for chat conversations and AI interactions * ChatStore - State management for chat messages and AI interactions
* *
* This store manages the complete chat experience including: * This store manages message-level operations and AI streaming:
* - Conversation lifecycle (create, load, delete, update)
* - Message management with branching support for conversation trees * - Message management with branching support for conversation trees
* - Real-time AI response streaming with reasoning content support * - Real-time AI response streaming with reasoning content support
* - File attachment handling and processing * - File attachment handling and processing
* - Context error management and recovery * - Context error management and recovery
* - Database persistence through DatabaseStore integration * - Per-conversation loading and streaming states
* - Database persistence through DatabaseService integration
* *
* **Architecture & Relationships:** * **Architecture & Relationships:**
* - **ChatService**: Handles low-level API communication with AI models * - **ChatService**: Handles low-level API communication with AI models
@ -26,10 +26,11 @@ import type { ExportedConversations } from '$lib/types/database';
* - ChatService provides abort capabilities and error handling * - ChatService provides abort capabilities and error handling
* - ChatStore manages the UI state while ChatService handles network layer * - ChatStore manages the UI state while ChatService handles network layer
* *
* - **DatabaseStore**: Provides persistent storage for conversations and messages * - **ConversationsStore**: Manages conversation lifecycle and list
* - ChatStore uses DatabaseStore for all CRUD operations * - To be added
* - Maintains referential integrity for conversation trees *
* - Handles message branching and parent-child relationships * - **DatabaseService**: Provides persistent storage for messages
* - To be added
* *
* - **SlotsService**: Monitors server resource usage during AI generation * - **SlotsService**: Monitors server resource usage during AI generation
* - ChatStore coordinates slots polling during streaming * - ChatStore coordinates slots polling during streaming
@ -37,11 +38,12 @@ import type { ExportedConversations } from '$lib/types/database';
* *
* **Key Features:** * **Key Features:**
* - Reactive state management using Svelte 5 runes ($state) * - Reactive state management using Svelte 5 runes ($state)
* - Conversation branching for exploring different response paths * - Message branching for exploring different response paths
* - Streaming AI responses with real-time content updates * - Streaming AI responses with real-time content updates
* - File attachment support (images, PDFs, text files, audio) * - File attachment support (images, PDFs, text files, audio)
* - Partial response saving when generation is interrupted * - Partial response saving when generation is interrupted
* - Message editing with automatic response regeneration * - Message editing with automatic response regeneration
* - Per-conversation loading and streaming state tracking
*/ */
class ChatStore { class ChatStore {
activeConversation = $state<DatabaseConversation | null>(null); activeConversation = $state<DatabaseConversation | null>(null);
@ -80,7 +82,7 @@ class ChatStore {
* Refreshes the conversations list from persistent storage * Refreshes the conversations list from persistent storage
*/ */
async loadConversations(): Promise<void> { async loadConversations(): Promise<void> {
this.conversations = await DatabaseStore.getAllConversations(); this.conversations = await DatabaseService.getAllConversations();
} }
/** /**
@ -90,7 +92,7 @@ class ChatStore {
*/ */
async createConversation(name?: string): Promise<string> { async createConversation(name?: string): Promise<string> {
const conversationName = name || `Chat ${new Date().toLocaleString()}`; const conversationName = name || `Chat ${new Date().toLocaleString()}`;
const conversation = await DatabaseStore.createConversation(conversationName); const conversation = await DatabaseService.createConversation(conversationName);
this.conversations.unshift(conversation); this.conversations.unshift(conversation);
@ -116,7 +118,7 @@ class ChatStore {
*/ */
async loadConversation(convId: string): Promise<boolean> { async loadConversation(convId: string): Promise<boolean> {
try { try {
const conversation = await DatabaseStore.getConversation(convId); const conversation = await DatabaseService.getConversation(convId);
if (!conversation) { if (!conversation) {
return false; return false;
@ -133,7 +135,7 @@ class ChatStore {
this.currentResponse = streamingState?.response || ''; this.currentResponse = streamingState?.response || '';
if (conversation.currNode) { if (conversation.currNode) {
const allMessages = await DatabaseStore.getConversationMessages(convId); const allMessages = await DatabaseService.getConversationMessages(convId);
this.activeMessages = filterByLeafNodeId( this.activeMessages = filterByLeafNodeId(
allMessages, allMessages,
conversation.currNode, conversation.currNode,
@ -141,7 +143,7 @@ class ChatStore {
) as DatabaseMessage[]; ) as DatabaseMessage[];
} else { } else {
// Load all messages for conversations without currNode (backward compatibility) // Load all messages for conversations without currNode (backward compatibility)
this.activeMessages = await DatabaseStore.getConversationMessages(convId); this.activeMessages = await DatabaseService.getConversationMessages(convId);
} }
return true; return true;
@ -180,13 +182,13 @@ class ChatStore {
if (this.activeMessages.length > 0) { if (this.activeMessages.length > 0) {
parentId = this.activeMessages[this.activeMessages.length - 1].id; parentId = this.activeMessages[this.activeMessages.length - 1].id;
} else { } else {
const allMessages = await DatabaseStore.getConversationMessages( const allMessages = await DatabaseService.getConversationMessages(
this.activeConversation.id this.activeConversation.id
); );
const rootMessage = allMessages.find((m) => m.parent === null && m.type === 'root'); const rootMessage = allMessages.find((m) => m.parent === null && m.type === 'root');
if (!rootMessage) { if (!rootMessage) {
const rootId = await DatabaseStore.createRootMessage(this.activeConversation.id); const rootId = await DatabaseService.createRootMessage(this.activeConversation.id);
parentId = rootId; parentId = rootId;
} else { } else {
parentId = rootMessage.id; parentId = rootMessage.id;
@ -196,7 +198,7 @@ class ChatStore {
parentId = parent; parentId = parent;
} }
const message = await DatabaseStore.createMessageBranch( const message = await DatabaseService.createMessageBranch(
{ {
convId: this.activeConversation.id, convId: this.activeConversation.id,
role, role,
@ -213,7 +215,7 @@ class ChatStore {
this.activeMessages.push(message); this.activeMessages.push(message);
await DatabaseStore.updateCurrentNode(this.activeConversation.id, message.id); await DatabaseService.updateCurrentNode(this.activeConversation.id, message.id);
this.activeConversation.currNode = message.id; this.activeConversation.currNode = message.id;
this.updateConversationTimestamp(); this.updateConversationTimestamp();
@ -386,7 +388,7 @@ class ChatStore {
if (persistImmediately && !modelPersisted) { if (persistImmediately && !modelPersisted) {
modelPersisted = true; modelPersisted = true;
DatabaseStore.updateMessage(assistantMessage.id, { model: normalizedModel }).catch( DatabaseService.updateMessage(assistantMessage.id, { model: normalizedModel }).catch(
(error) => { (error) => {
console.error('Failed to persist model name:', error); console.error('Failed to persist model name:', error);
modelPersisted = false; modelPersisted = false;
@ -470,7 +472,7 @@ class ChatStore {
modelPersisted = true; modelPersisted = true;
} }
await DatabaseStore.updateMessage(assistantMessage.id, updateData); await DatabaseService.updateMessage(assistantMessage.id, updateData);
const messageIndex = this.findMessageIndex(assistantMessage.id); const messageIndex = this.findMessageIndex(assistantMessage.id);
@ -492,7 +494,7 @@ class ChatStore {
this.updateMessageAtIndex(messageIndex, localUpdateData); this.updateMessageAtIndex(messageIndex, localUpdateData);
await DatabaseStore.updateCurrentNode(assistantMessage.convId, assistantMessage.id); await DatabaseService.updateCurrentNode(assistantMessage.convId, assistantMessage.id);
if (this.activeConversation?.id === assistantMessage.convId) { if (this.activeConversation?.id === assistantMessage.convId) {
this.activeConversation.currNode = assistantMessage.id; this.activeConversation.currNode = assistantMessage.id;
@ -531,7 +533,7 @@ class ChatStore {
const [failedMessage] = this.activeMessages.splice(messageIndex, 1); const [failedMessage] = this.activeMessages.splice(messageIndex, 1);
if (failedMessage) { if (failedMessage) {
DatabaseStore.deleteMessage(failedMessage.id).catch((cleanupError) => { DatabaseService.deleteMessage(failedMessage.id).catch((cleanupError) => {
console.error('Failed to remove assistant message after error:', cleanupError); console.error('Failed to remove assistant message after error:', cleanupError);
}); });
} }
@ -595,7 +597,7 @@ class ChatStore {
private async createAssistantMessage(parentId?: string): Promise<DatabaseMessage | null> { private async createAssistantMessage(parentId?: string): Promise<DatabaseMessage | null> {
if (!this.activeConversation) return null; if (!this.activeConversation) return null;
return await DatabaseStore.createMessageBranch( return await DatabaseService.createMessageBranch(
{ {
convId: this.activeConversation.id, convId: this.activeConversation.id,
type: 'text', type: 'text',
@ -752,7 +754,7 @@ class ChatStore {
const messages = const messages =
conversationId === this.activeConversation?.id conversationId === this.activeConversation?.id
? this.activeMessages ? this.activeMessages
: await DatabaseStore.getConversationMessages(conversationId); : await DatabaseService.getConversationMessages(conversationId);
if (!messages.length) return; if (!messages.length) return;
@ -786,7 +788,7 @@ class ChatStore {
}; };
} }
await DatabaseStore.updateMessage(lastMessage.id, updateData); await DatabaseService.updateMessage(lastMessage.id, updateData);
lastMessage.content = this.currentResponse; lastMessage.content = this.currentResponse;
@ -833,13 +835,13 @@ class ChatStore {
return; return;
} }
const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id); const allMessages = await DatabaseService.getConversationMessages(this.activeConversation.id);
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null); const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
const isFirstUserMessage = const isFirstUserMessage =
rootMessage && messageToUpdate.parent === rootMessage.id && messageToUpdate.role === 'user'; rootMessage && messageToUpdate.parent === rootMessage.id && messageToUpdate.role === 'user';
this.updateMessageAtIndex(messageIndex, { content: newContent }); this.updateMessageAtIndex(messageIndex, { content: newContent });
await DatabaseStore.updateMessage(messageId, { content: newContent }); await DatabaseService.updateMessage(messageId, { content: newContent });
if (isFirstUserMessage && newContent.trim()) { if (isFirstUserMessage && newContent.trim()) {
await this.updateConversationTitleWithConfirmation( await this.updateConversationTitleWithConfirmation(
@ -851,7 +853,7 @@ class ChatStore {
const messagesToRemove = this.activeMessages.slice(messageIndex + 1); const messagesToRemove = this.activeMessages.slice(messageIndex + 1);
for (const message of messagesToRemove) { for (const message of messagesToRemove) {
await DatabaseStore.deleteMessage(message.id); await DatabaseService.deleteMessage(message.id);
} }
this.activeMessages = this.activeMessages.slice(0, messageIndex + 1); this.activeMessages = this.activeMessages.slice(0, messageIndex + 1);
@ -867,7 +869,7 @@ class ChatStore {
} }
this.activeMessages.push(assistantMessage); this.activeMessages.push(assistantMessage);
await DatabaseStore.updateCurrentNode(this.activeConversation.id, assistantMessage.id); await DatabaseService.updateCurrentNode(this.activeConversation.id, assistantMessage.id);
this.activeConversation.currNode = assistantMessage.id; this.activeConversation.currNode = assistantMessage.id;
await this.streamChatCompletion( await this.streamChatCompletion(
@ -917,7 +919,7 @@ class ChatStore {
const messagesToRemove = this.activeMessages.slice(messageIndex); const messagesToRemove = this.activeMessages.slice(messageIndex);
for (const message of messagesToRemove) { for (const message of messagesToRemove) {
await DatabaseStore.deleteMessage(message.id); await DatabaseService.deleteMessage(message.id);
} }
this.activeMessages = this.activeMessages.slice(0, messageIndex); this.activeMessages = this.activeMessages.slice(0, messageIndex);
@ -960,7 +962,7 @@ class ChatStore {
*/ */
async updateConversationName(convId: string, name: string): Promise<void> { async updateConversationName(convId: string, name: string): Promise<void> {
try { try {
await DatabaseStore.updateConversation(convId, { name }); await DatabaseService.updateConversation(convId, { name });
const convIndex = this.conversations.findIndex((c) => c.id === convId); const convIndex = this.conversations.findIndex((c) => c.id === convId);
@ -1002,7 +1004,7 @@ class ChatStore {
const currentConfig = config(); const currentConfig = config();
if (currentConfig.askForTitleConfirmation && onConfirmationNeeded) { if (currentConfig.askForTitleConfirmation && onConfirmationNeeded) {
const conversation = await DatabaseStore.getConversation(convId); const conversation = await DatabaseService.getConversation(convId);
if (!conversation) return false; if (!conversation) return false;
const shouldUpdate = await onConfirmationNeeded(conversation.name, newTitle); const shouldUpdate = await onConfirmationNeeded(conversation.name, newTitle);
@ -1024,10 +1026,10 @@ class ChatStore {
async downloadConversation(convId: string): Promise<void> { async downloadConversation(convId: string): Promise<void> {
if (!this.activeConversation || this.activeConversation.id !== convId) { if (!this.activeConversation || this.activeConversation.id !== convId) {
// Load the conversation if not currently active // Load the conversation if not currently active
const conversation = await DatabaseStore.getConversation(convId); const conversation = await DatabaseService.getConversation(convId);
if (!conversation) return; if (!conversation) return;
const messages = await DatabaseStore.getConversationMessages(convId); const messages = await DatabaseService.getConversationMessages(convId);
const conversationData = { const conversationData = {
conv: conversation, conv: conversation,
messages messages
@ -1086,14 +1088,14 @@ class ChatStore {
*/ */
async exportAllConversations(): Promise<DatabaseConversation[]> { async exportAllConversations(): Promise<DatabaseConversation[]> {
try { try {
const allConversations = await DatabaseStore.getAllConversations(); const allConversations = await DatabaseService.getAllConversations();
if (allConversations.length === 0) { if (allConversations.length === 0) {
throw new Error('No conversations to export'); throw new Error('No conversations to export');
} }
const allData: ExportedConversations = await Promise.all( const allData: ExportedConversations = await Promise.all(
allConversations.map(async (conv) => { allConversations.map(async (conv) => {
const messages = await DatabaseStore.getConversationMessages(conv.id); const messages = await DatabaseService.getConversationMessages(conv.id);
return { conv, messages }; return { conv, messages };
}) })
); );
@ -1121,7 +1123,7 @@ class ChatStore {
/** /**
* Imports conversations from a JSON file. * Imports conversations from a JSON file.
* Supports both single conversation (object) and multiple conversations (array). * Supports both single conversation (object) and multiple conversations (array).
* Uses DatabaseStore for safe, encapsulated data access * Uses DatabaseService for safe, encapsulated data access
* Returns the list of imported conversations * Returns the list of imported conversations
*/ */
async importConversations(): Promise<DatabaseConversation[]> { async importConversations(): Promise<DatabaseConversation[]> {
@ -1158,7 +1160,7 @@ class ChatStore {
); );
} }
const result = await DatabaseStore.importConversations(importedData); const result = await DatabaseService.importConversations(importedData);
// Refresh UI // Refresh UI
await this.loadConversations(); await this.loadConversations();
@ -1188,7 +1190,7 @@ class ChatStore {
*/ */
async deleteConversation(convId: string): Promise<void> { async deleteConversation(convId: string): Promise<void> {
try { try {
await DatabaseStore.deleteConversation(convId); await DatabaseService.deleteConversation(convId);
this.conversations = this.conversations.filter((c) => c.id !== convId); this.conversations = this.conversations.filter((c) => c.id !== convId);
@ -1217,7 +1219,7 @@ class ChatStore {
return { totalCount: 0, userMessages: 0, assistantMessages: 0, messageTypes: [] }; return { totalCount: 0, userMessages: 0, assistantMessages: 0, messageTypes: [] };
} }
const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id); const allMessages = await DatabaseService.getConversationMessages(this.activeConversation.id);
const descendants = findDescendantMessages(allMessages, messageId); const descendants = findDescendantMessages(allMessages, messageId);
const allToDelete = [messageId, ...descendants]; const allToDelete = [messageId, ...descendants];
@ -1254,7 +1256,7 @@ class ChatStore {
if (!this.activeConversation) return; if (!this.activeConversation) return;
// Get all messages to find siblings before deletion // Get all messages to find siblings before deletion
const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id); const allMessages = await DatabaseService.getConversationMessages(this.activeConversation.id);
const messageToDelete = allMessages.find((m) => m.id === messageId); const messageToDelete = allMessages.find((m) => m.id === messageId);
if (!messageToDelete) { if (!messageToDelete) {
@ -1287,20 +1289,20 @@ class ChatStore {
const leafNodeId = findLeafNode(allMessages, latestSibling.id); const leafNodeId = findLeafNode(allMessages, latestSibling.id);
// Update conversation to use the leaf node of the latest remaining sibling // Update conversation to use the leaf node of the latest remaining sibling
await DatabaseStore.updateCurrentNode(this.activeConversation.id, leafNodeId); await DatabaseService.updateCurrentNode(this.activeConversation.id, leafNodeId);
this.activeConversation.currNode = leafNodeId; this.activeConversation.currNode = leafNodeId;
} else { } else {
// No siblings left, navigate to parent if it exists // No siblings left, navigate to parent if it exists
if (messageToDelete.parent) { if (messageToDelete.parent) {
const parentLeafId = findLeafNode(allMessages, messageToDelete.parent); const parentLeafId = findLeafNode(allMessages, messageToDelete.parent);
await DatabaseStore.updateCurrentNode(this.activeConversation.id, parentLeafId); await DatabaseService.updateCurrentNode(this.activeConversation.id, parentLeafId);
this.activeConversation.currNode = parentLeafId; this.activeConversation.currNode = parentLeafId;
} }
} }
} }
// Use cascading deletion to remove the message and all its descendants // Use cascading deletion to remove the message and all its descendants
await DatabaseStore.deleteMessageCascading(this.activeConversation.id, messageId); await DatabaseService.deleteMessageCascading(this.activeConversation.id, messageId);
// Refresh active messages to show the updated branch // Refresh active messages to show the updated branch
await this.refreshActiveMessages(); await this.refreshActiveMessages();
@ -1329,7 +1331,7 @@ class ChatStore {
async refreshActiveMessages(): Promise<void> { async refreshActiveMessages(): Promise<void> {
if (!this.activeConversation) return; if (!this.activeConversation) return;
const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id); const allMessages = await DatabaseService.getConversationMessages(this.activeConversation.id);
if (allMessages.length === 0) { if (allMessages.length === 0) {
this.activeMessages = []; this.activeMessages = [];
return; return;
@ -1353,7 +1355,7 @@ class ChatStore {
if (!this.activeConversation) return; if (!this.activeConversation) return;
// Get the current first user message before navigation // Get the current first user message before navigation
const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id); const allMessages = await DatabaseService.getConversationMessages(this.activeConversation.id);
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null); const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
const currentFirstUserMessage = this.activeMessages.find( const currentFirstUserMessage = this.activeMessages.find(
(m) => m.role === 'user' && m.parent === rootMessage?.id (m) => m.role === 'user' && m.parent === rootMessage?.id
@ -1361,7 +1363,7 @@ class ChatStore {
const currentLeafNodeId = findLeafNode(allMessages, siblingId); const currentLeafNodeId = findLeafNode(allMessages, siblingId);
await DatabaseStore.updateCurrentNode(this.activeConversation.id, currentLeafNodeId); await DatabaseService.updateCurrentNode(this.activeConversation.id, currentLeafNodeId);
this.activeConversation.currNode = currentLeafNodeId; this.activeConversation.currNode = currentLeafNodeId;
await this.refreshActiveMessages(); await this.refreshActiveMessages();
@ -1421,7 +1423,7 @@ class ChatStore {
} }
if (shouldBranch) { if (shouldBranch) {
const newMessage = await DatabaseStore.createMessageBranch( const newMessage = await DatabaseService.createMessageBranch(
{ {
convId: messageToEdit.convId, convId: messageToEdit.convId,
type: messageToEdit.type, type: messageToEdit.type,
@ -1436,16 +1438,16 @@ class ChatStore {
messageToEdit.parent! messageToEdit.parent!
); );
await DatabaseStore.updateCurrentNode(this.activeConversation.id, newMessage.id); await DatabaseService.updateCurrentNode(this.activeConversation.id, newMessage.id);
this.activeConversation.currNode = newMessage.id; this.activeConversation.currNode = newMessage.id;
} else { } else {
await DatabaseStore.updateMessage(messageToEdit.id, { await DatabaseService.updateMessage(messageToEdit.id, {
content: newContent, content: newContent,
timestamp: Date.now() timestamp: Date.now()
}); });
// Ensure currNode points to the edited message to maintain correct path // Ensure currNode points to the edited message to maintain correct path
await DatabaseStore.updateCurrentNode(this.activeConversation.id, messageToEdit.id); await DatabaseService.updateCurrentNode(this.activeConversation.id, messageToEdit.id);
this.activeConversation.currNode = messageToEdit.id; this.activeConversation.currNode = messageToEdit.id;
this.updateMessageAtIndex(messageIndex, { this.updateMessageAtIndex(messageIndex, {
@ -1494,7 +1496,7 @@ class ChatStore {
} }
// Simply update the message content in-place // Simply update the message content in-place
await DatabaseStore.updateMessage(messageId, { await DatabaseService.updateMessage(messageId, {
content: newContent, content: newContent,
timestamp: Date.now() timestamp: Date.now()
}); });
@ -1505,7 +1507,7 @@ class ChatStore {
}); });
// Check if first user message for title update // Check if first user message for title update
const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id); const allMessages = await DatabaseService.getConversationMessages(this.activeConversation.id);
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null); const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
const isFirstUserMessage = const isFirstUserMessage =
rootMessage && messageToEdit.parent === rootMessage.id && messageToEdit.role === 'user'; rootMessage && messageToEdit.parent === rootMessage.id && messageToEdit.role === 'user';
@ -1547,7 +1549,7 @@ class ChatStore {
// Check if this is the first user message in the conversation // Check if this is the first user message in the conversation
// First user message is one that has the root message as its parent // First user message is one that has the root message as its parent
const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id); const allMessages = await DatabaseService.getConversationMessages(this.activeConversation.id);
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null); const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
const isFirstUserMessage = const isFirstUserMessage =
rootMessage && messageToEdit.parent === rootMessage.id && messageToEdit.role === 'user'; rootMessage && messageToEdit.parent === rootMessage.id && messageToEdit.role === 'user';
@ -1564,7 +1566,7 @@ class ChatStore {
} }
} }
const newMessage = await DatabaseStore.createMessageBranch( const newMessage = await DatabaseService.createMessageBranch(
{ {
convId: messageToEdit.convId, convId: messageToEdit.convId,
type: messageToEdit.type, type: messageToEdit.type,
@ -1580,7 +1582,7 @@ class ChatStore {
parentId parentId
); );
await DatabaseStore.updateCurrentNode(this.activeConversation.id, newMessage.id); await DatabaseService.updateCurrentNode(this.activeConversation.id, newMessage.id);
this.activeConversation.currNode = newMessage.id; this.activeConversation.currNode = newMessage.id;
this.updateConversationTimestamp(); this.updateConversationTimestamp();
@ -1624,7 +1626,7 @@ class ChatStore {
} }
// Find parent message in all conversation messages, not just active path // Find parent message in all conversation messages, not just active path
const conversationMessages = await DatabaseStore.getConversationMessages( const conversationMessages = await DatabaseService.getConversationMessages(
this.activeConversation.id this.activeConversation.id
); );
const parentMessage = conversationMessages.find((m) => m.id === messageToRegenerate.parent); const parentMessage = conversationMessages.find((m) => m.id === messageToRegenerate.parent);
@ -1636,7 +1638,7 @@ class ChatStore {
this.setConversationLoading(this.activeConversation.id, true); this.setConversationLoading(this.activeConversation.id, true);
this.clearConversationStreaming(this.activeConversation.id); this.clearConversationStreaming(this.activeConversation.id);
const newAssistantMessage = await DatabaseStore.createMessageBranch( const newAssistantMessage = await DatabaseService.createMessageBranch(
{ {
convId: this.activeConversation.id, convId: this.activeConversation.id,
type: 'text', type: 'text',
@ -1651,12 +1653,12 @@ class ChatStore {
parentMessage.id parentMessage.id
); );
await DatabaseStore.updateCurrentNode(this.activeConversation.id, newAssistantMessage.id); await DatabaseService.updateCurrentNode(this.activeConversation.id, newAssistantMessage.id);
this.activeConversation.currNode = newAssistantMessage.id; this.activeConversation.currNode = newAssistantMessage.id;
this.updateConversationTimestamp(); this.updateConversationTimestamp();
await this.refreshActiveMessages(); await this.refreshActiveMessages();
const allConversationMessages = await DatabaseStore.getConversationMessages( const allConversationMessages = await DatabaseService.getConversationMessages(
this.activeConversation.id this.activeConversation.id
); );
const conversationPath = filterByLeafNodeId( const conversationPath = filterByLeafNodeId(
@ -1687,7 +1689,7 @@ class ChatStore {
try { try {
// Get conversation path up to the user message // Get conversation path up to the user message
const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id); const allMessages = await DatabaseService.getConversationMessages(this.activeConversation.id);
const conversationPath = filterByLeafNodeId( const conversationPath = filterByLeafNodeId(
allMessages, allMessages,
userMessageId, userMessageId,
@ -1695,7 +1697,7 @@ class ChatStore {
) as DatabaseMessage[]; ) as DatabaseMessage[];
// Create new assistant message branch // Create new assistant message branch
const assistantMessage = await DatabaseStore.createMessageBranch( const assistantMessage = await DatabaseService.createMessageBranch(
{ {
convId: this.activeConversation.id, convId: this.activeConversation.id,
type: 'text', type: 'text',
@ -1755,7 +1757,7 @@ class ChatStore {
// IMPORTANT: Fetch the latest content from the database to ensure we have // IMPORTANT: Fetch the latest content from the database to ensure we have
// the most up-to-date content, especially after a stopped generation // the most up-to-date content, especially after a stopped generation
// This prevents issues where the in-memory state might be stale // This prevents issues where the in-memory state might be stale
const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id); const allMessages = await DatabaseService.getConversationMessages(this.activeConversation.id);
const dbMessage = allMessages.find((m) => m.id === messageId); const dbMessage = allMessages.find((m) => m.id === messageId);
if (!dbMessage) { if (!dbMessage) {
@ -1843,7 +1845,7 @@ class ChatStore {
timings: timings timings: timings
}; };
await DatabaseStore.updateMessage(messageToContinue.id, updateData); await DatabaseService.updateMessage(messageToContinue.id, updateData);
this.updateMessageAtIndex(messageIndex, updateData); this.updateMessageAtIndex(messageIndex, updateData);
@ -1861,7 +1863,7 @@ class ChatStore {
const partialContent = originalContent + appendedContent; const partialContent = originalContent + appendedContent;
const partialThinking = originalThinking + appendedThinking; const partialThinking = originalThinking + appendedThinking;
await DatabaseStore.updateMessage(messageToContinue.id, { await DatabaseService.updateMessage(messageToContinue.id, {
content: partialContent, content: partialContent,
thinking: partialThinking, thinking: partialThinking,
timestamp: Date.now() timestamp: Date.now()
@ -1891,7 +1893,7 @@ class ChatStore {
}); });
// Ensure database has original content (in case of partial writes) // Ensure database has original content (in case of partial writes)
await DatabaseStore.updateMessage(messageToContinue.id, { await DatabaseService.updateMessage(messageToContinue.id, {
content: originalContent, content: originalContent,
thinking: originalThinking thinking: originalThinking
}); });