refactor: DatabaseStore -> DatabaseService
This commit is contained in:
parent
7db3d87434
commit
ccd6c27183
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:**
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue