import { createStorage } from '../base/base'; import { StorageEnum } from '../base/enums'; import type { ChatSession, ChatMessage, ChatHistoryStorage, Message, ChatSessionMetadata } from './types'; // Key for storing chat session metadata const CHAT_SESSIONS_META_KEY = 'chat_sessions_meta'; // Create storage for session metadata const chatSessionsMetaStorage = createStorage(CHAT_SESSIONS_META_KEY, [], { storageEnum: StorageEnum.Local, liveUpdate: true, }); // Helper function to get storage key for a specific session's messages const getSessionMessagesKey = (sessionId: string) => `chat_messages_${sessionId}`; // Helper function to create storage for a specific session's messages const getSessionMessagesStorage = (sessionId: string) => { return createStorage(getSessionMessagesKey(sessionId), [], { storageEnum: StorageEnum.Local, liveUpdate: true, }); }; // Helper function to get current timestamp in milliseconds const getCurrentTimestamp = (): number => Date.now(); /** * Creates a chat history storage instance with optimized operations */ export function createChatHistoryStorage(): ChatHistoryStorage { return { getAllSessions: async (): Promise => { const sessionsMeta = await chatSessionsMetaStorage.get(); // For listing purposes, we can return sessions without loading messages // This makes the list view very fast return sessionsMeta.map(meta => ({ ...meta, messages: [], // Empty array as we don't load messages for listing })); }, clearAllSessions: async (): Promise => { const sessionsMeta = await chatSessionsMetaStorage.get(); for (const sessionMeta of sessionsMeta) { const messagesStorage = getSessionMessagesStorage(sessionMeta.id); await messagesStorage.set([]); } await chatSessionsMetaStorage.set([]); }, // Get session metadata without messages (for UI listing) getSessionsMetadata: async (): Promise => { return await chatSessionsMetaStorage.get(); }, getSession: async (sessionId: string): Promise => { const sessionsMeta = await chatSessionsMetaStorage.get(); const sessionMeta = sessionsMeta.find(session => session.id === sessionId); if (!sessionMeta) return null; // Load messages only when a specific session is requested const messagesStorage = getSessionMessagesStorage(sessionId); const messages = await messagesStorage.get(); return { ...sessionMeta, messages, }; }, createSession: async (title: string): Promise => { const newSessionId = crypto.randomUUID(); const currentTime = getCurrentTimestamp(); const newSessionMeta: ChatSessionMetadata = { id: newSessionId, title, createdAt: currentTime, updatedAt: currentTime, messageCount: 0, }; // Create empty messages array for the new session const messagesStorage = getSessionMessagesStorage(newSessionId); await messagesStorage.set([]); // Add session metadata to the index await chatSessionsMetaStorage.set(prevSessions => [...prevSessions, newSessionMeta]); return { ...newSessionMeta, messages: [], }; }, updateTitle: async (sessionId: string, title: string): Promise => { let updatedSessionMeta: ChatSessionMetadata | undefined; // Update the title and capture the updated session in a single pass await chatSessionsMetaStorage.set(prevSessions => { return prevSessions.map(session => { if (session.id === sessionId) { // Create the updated session const updated = { ...session, title, updatedAt: getCurrentTimestamp(), }; // Capture it for return value updatedSessionMeta = updated; return updated; } return session; }); }); // Check if we found and updated the session if (!updatedSessionMeta) { throw new Error('Session not found'); } // Return the already captured metadata return updatedSessionMeta; }, deleteSession: async (sessionId: string): Promise => { // Remove session from metadata await chatSessionsMetaStorage.set(prevSessions => prevSessions.filter(session => session.id !== sessionId)); // Remove the session's messages const messagesStorage = getSessionMessagesStorage(sessionId); await messagesStorage.set([]); }, addMessage: async (sessionId: string, message: Message): Promise => { const newMessage: ChatMessage = { ...message, id: crypto.randomUUID(), }; // First check if session exists and update metadata in a single operation let sessionFound = false; await chatSessionsMetaStorage.set(prevSessions => { return prevSessions.map(session => { if (session.id === sessionId) { sessionFound = true; return { ...session, updatedAt: getCurrentTimestamp(), messageCount: session.messageCount + 1, }; } return session; }); }); // Throw error if session wasn't found if (!sessionFound) { throw new Error(`Session with ID ${sessionId} not found`); } // Only add the message if the session exists const messagesStorage = getSessionMessagesStorage(sessionId); await messagesStorage.set(prevMessages => [...prevMessages, newMessage]); return newMessage; }, deleteMessage: async (sessionId: string, messageId: string): Promise => { // Get the messages storage for this session const messagesStorage = getSessionMessagesStorage(sessionId); // Get current messages to calculate the new count const currentMessages = await messagesStorage.get(); const messageToDelete = currentMessages.find(msg => msg.id === messageId); if (!messageToDelete) return; // Message not found // Remove the message directly from the messages storage await messagesStorage.set(prevMessages => prevMessages.filter(msg => msg.id !== messageId)); // Update the session's metadata (updatedAt timestamp and messageCount) await chatSessionsMetaStorage.set(prevSessions => { return prevSessions.map(session => { if (session.id === sessionId) { return { ...session, updatedAt: getCurrentTimestamp(), messageCount: Math.max(0, session.messageCount - 1), }; } return session; }); }); }, }; } // Export the storage instance for direct use export const chatHistoryStore = createChatHistoryStorage();