Spaces:
Running
Running
| /** | |
| * Conversation Storage Utility | |
| * Handles conversation persistence with localStorage | |
| */ | |
| const STORAGE_KEY = 'ca_study_conversations'; | |
| const MAX_CONVERSATIONS = 50; // Limit to prevent localStorage overflow | |
| export class ConversationStorage { | |
| /** | |
| * Load all conversations from localStorage | |
| */ | |
| static loadConversations() { | |
| try { | |
| const stored = localStorage.getItem(STORAGE_KEY); | |
| if (!stored) return []; | |
| const conversations = JSON.parse(stored); | |
| // Convert date strings back to Date objects | |
| return conversations.map(conv => ({ | |
| ...conv, | |
| createdAt: new Date(conv.createdAt), | |
| messages: conv.messages.map(msg => ({ | |
| ...msg, | |
| timestamp: new Date(msg.timestamp) | |
| })) | |
| })); | |
| } catch (error) { | |
| console.error('Error loading conversations:', error); | |
| return []; | |
| } | |
| } | |
| /** | |
| * Save conversations to localStorage | |
| */ | |
| static saveConversations(conversations) { | |
| try { | |
| // Limit the number of conversations to prevent localStorage overflow | |
| const limitedConversations = conversations.slice(0, MAX_CONVERSATIONS); | |
| localStorage.setItem(STORAGE_KEY, JSON.stringify(limitedConversations)); | |
| return true; | |
| } catch (error) { | |
| console.error('Error saving conversations:', error); | |
| // Handle localStorage quota exceeded | |
| if (error.name === 'QuotaExceededError') { | |
| // Try to save with fewer conversations | |
| const reducedConversations = conversations.slice(0, 25); | |
| try { | |
| localStorage.setItem(STORAGE_KEY, JSON.stringify(reducedConversations)); | |
| return true; | |
| } catch (retryError) { | |
| console.error('Error saving reduced conversations:', retryError); | |
| } | |
| } | |
| return false; | |
| } | |
| } | |
| /** | |
| * Add a new conversation | |
| */ | |
| static addConversation(conversation) { | |
| const conversations = this.loadConversations(); | |
| const newConversations = [conversation, ...conversations]; | |
| return this.saveConversations(newConversations); | |
| } | |
| /** | |
| * Update an existing conversation | |
| */ | |
| static updateConversation(conversationId, updates) { | |
| const conversations = this.loadConversations(); | |
| const updatedConversations = conversations.map(conv => | |
| conv.id === conversationId ? { ...conv, ...updates } : conv | |
| ); | |
| return this.saveConversations(updatedConversations); | |
| } | |
| /** | |
| * Delete a conversation | |
| */ | |
| static deleteConversation(conversationId) { | |
| const conversations = this.loadConversations(); | |
| const filteredConversations = conversations.filter(conv => conv.id !== conversationId); | |
| return this.saveConversations(filteredConversations); | |
| } | |
| /** | |
| * Add a message to a conversation | |
| */ | |
| static addMessage(conversationId, message) { | |
| const conversations = this.loadConversations(); | |
| const updatedConversations = conversations.map(conv => { | |
| if (conv.id === conversationId) { | |
| return { | |
| ...conv, | |
| messages: [...conv.messages, message], | |
| updatedAt: new Date() | |
| }; | |
| } | |
| return conv; | |
| }); | |
| return this.saveConversations(updatedConversations); | |
| } | |
| /** | |
| * Search conversations by title or content | |
| */ | |
| static searchConversations(query) { | |
| const conversations = this.loadConversations(); | |
| const lowercaseQuery = query.toLowerCase(); | |
| return conversations.filter(conv => | |
| conv.title.toLowerCase().includes(lowercaseQuery) || | |
| conv.messages.some(msg => | |
| msg.content.toLowerCase().includes(lowercaseQuery) | |
| ) | |
| ); | |
| } | |
| /** | |
| * Get conversation statistics | |
| */ | |
| static getStatistics() { | |
| const conversations = this.loadConversations(); | |
| const totalMessages = conversations.reduce((sum, conv) => sum + conv.messages.length, 0); | |
| return { | |
| totalConversations: conversations.length, | |
| totalMessages, | |
| storageSize: this.getStorageSize(), | |
| oldestConversation: conversations.length > 0 ? | |
| conversations[conversations.length - 1].createdAt : null, | |
| newestConversation: conversations.length > 0 ? | |
| conversations[0].createdAt : null | |
| }; | |
| } | |
| /** | |
| * Get storage size in KB | |
| */ | |
| static getStorageSize() { | |
| try { | |
| const stored = localStorage.getItem(STORAGE_KEY); | |
| return stored ? Math.round(new Blob([stored]).size / 1024) : 0; | |
| } catch (error) { | |
| return 0; | |
| } | |
| } | |
| /** | |
| * Export conversations as JSON | |
| */ | |
| static exportConversations() { | |
| const conversations = this.loadConversations(); | |
| const exportData = { | |
| exportDate: new Date().toISOString(), | |
| version: '1.0', | |
| conversations | |
| }; | |
| const blob = new Blob([JSON.stringify(exportData, null, 2)], { | |
| type: 'application/json' | |
| }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `ca_study_conversations_${new Date().toISOString().split('T')[0]}.json`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| } | |
| /** | |
| * Import conversations from JSON file | |
| */ | |
| static importConversations(file) { | |
| return new Promise((resolve, reject) => { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| try { | |
| const importData = JSON.parse(e.target.result); | |
| if (!importData.conversations || !Array.isArray(importData.conversations)) { | |
| reject(new Error('Invalid conversation file format')); | |
| return; | |
| } | |
| const existingConversations = this.loadConversations(); | |
| const mergedConversations = [...importData.conversations, ...existingConversations]; | |
| // Remove duplicates based on ID | |
| const uniqueConversations = mergedConversations.filter((conv, index, self) => | |
| index === self.findIndex(c => c.id === conv.id) | |
| ); | |
| const success = this.saveConversations(uniqueConversations); | |
| resolve({ success, count: importData.conversations.length }); | |
| } catch (error) { | |
| reject(error); | |
| } | |
| }; | |
| reader.onerror = () => reject(new Error('Error reading file')); | |
| reader.readAsText(file); | |
| }); | |
| } | |
| /** | |
| * Clear all conversations | |
| */ | |
| static clearAllConversations() { | |
| try { | |
| localStorage.removeItem(STORAGE_KEY); | |
| return true; | |
| } catch (error) { | |
| console.error('Error clearing conversations:', error); | |
| return false; | |
| } | |
| } | |
| } | |
| export default ConversationStorage; |