UNPKG

contaigents

Version:

Modular AI Content Ecosystem with Audio Generation

205 lines (204 loc) 7.75 kB
import fs from 'fs/promises'; import path from 'path'; export class ConversationPersistence { constructor(baseDir) { this.conversationsDir = path.join(baseDir || process.cwd(), '.contaigents', 'conversations'); } /** * Initialize the conversations directory */ async initialize() { try { await fs.mkdir(this.conversationsDir, { recursive: true }); } catch (error) { throw new Error(`Failed to initialize conversations directory: ${error.message}`); } } /** * Save a conversation to disk */ async saveConversation(conversation) { await this.initialize(); const filePath = path.join(this.conversationsDir, `${conversation.id}.json`); const data = { ...conversation, lastAccessed: Date.now() }; try { await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8'); } catch (error) { throw new Error(`Failed to save conversation: ${error.message}`); } } /** * Load a conversation from disk */ async loadConversation(conversationId) { const filePath = path.join(this.conversationsDir, `${conversationId}.json`); try { const data = await fs.readFile(filePath, 'utf-8'); const conversation = JSON.parse(data); // Update last accessed time conversation.lastAccessed = Date.now(); await this.saveConversation(conversation); return conversation; } catch (error) { if (error.code === 'ENOENT') { return null; // File doesn't exist } throw new Error(`Failed to load conversation: ${error.message}`); } } /** * List all saved conversations */ async listConversations() { await this.initialize(); try { const files = await fs.readdir(this.conversationsDir); const conversations = []; for (const file of files) { if (file.endsWith('.json')) { const filePath = path.join(this.conversationsDir, file); try { const data = await fs.readFile(filePath, 'utf-8'); const conversation = JSON.parse(data); conversations.push(conversation); } catch (error) { console.warn(`Failed to load conversation file ${file}: ${error.message}`); } } } // Sort by last accessed time (most recent first) return conversations.sort((a, b) => b.lastAccessed - a.lastAccessed); } catch (error) { throw new Error(`Failed to list conversations: ${error.message}`); } } /** * Delete a conversation */ async deleteConversation(conversationId) { const filePath = path.join(this.conversationsDir, `${conversationId}.json`); try { await fs.unlink(filePath); return true; } catch (error) { if (error.code === 'ENOENT') { return false; // File doesn't exist } throw new Error(`Failed to delete conversation: ${error.message}`); } } /** * Update conversation metadata */ async updateConversationMetadata(conversationId, metadata) { const conversation = await this.loadConversation(conversationId); if (!conversation) { return false; } if (metadata.name !== undefined) conversation.name = metadata.name; if (metadata.description !== undefined) conversation.description = metadata.description; if (metadata.tags !== undefined) conversation.tags = metadata.tags; await this.saveConversation(conversation); return true; } /** * Search conversations by name, description, or content */ async searchConversations(query) { const conversations = await this.listConversations(); const lowerQuery = query.toLowerCase(); return conversations.filter(conv => { // Search in name and description if (conv.name?.toLowerCase().includes(lowerQuery) || conv.description?.toLowerCase().includes(lowerQuery)) { return true; } // Search in tags if (conv.tags?.some(tag => tag.toLowerCase().includes(lowerQuery))) { return true; } // Search in message content return conv.session.messages.some(msg => msg.content.toLowerCase().includes(lowerQuery)); }); } /** * Clean up old conversations (older than specified days) */ async cleanupOldConversations(olderThanDays = 30) { const conversations = await this.listConversations(); const cutoffTime = Date.now() - (olderThanDays * 24 * 60 * 60 * 1000); let deletedCount = 0; for (const conversation of conversations) { if (conversation.lastAccessed < cutoffTime) { const deleted = await this.deleteConversation(conversation.id); if (deleted) { deletedCount++; } } } return deletedCount; } /** * Export conversation to a readable format */ async exportConversation(conversationId, format = 'json') { const conversation = await this.loadConversation(conversationId); if (!conversation) { throw new Error(`Conversation ${conversationId} not found`); } if (format === 'markdown') { let markdown = `# ${conversation.name || 'Conversation'}\n\n`; if (conversation.description) { markdown += `${conversation.description}\n\n`; } markdown += `**Created:** ${new Date(conversation.session.createdAt).toLocaleString()}\n`; markdown += `**Last Updated:** ${new Date(conversation.session.updatedAt).toLocaleString()}\n\n`; if (conversation.tags && conversation.tags.length > 0) { markdown += `**Tags:** ${conversation.tags.join(', ')}\n\n`; } markdown += `---\n\n`; for (const message of conversation.session.messages) { const role = message.role === 'user' ? '👤 **You**' : '🤖 **Assistant**'; markdown += `${role}:\n${message.content}\n\n`; } return markdown; } return JSON.stringify(conversation, null, 2); } /** * Get conversation statistics */ async getStatistics() { const conversations = await this.listConversations(); if (conversations.length === 0) { return { totalConversations: 0, totalMessages: 0, averageMessagesPerConversation: 0, oldestConversation: null, newestConversation: null }; } const totalMessages = conversations.reduce((sum, conv) => sum + conv.session.messages.length, 0); const createdTimes = conversations.map(conv => conv.session.createdAt); return { totalConversations: conversations.length, totalMessages, averageMessagesPerConversation: Math.round(totalMessages / conversations.length), oldestConversation: new Date(Math.min(...createdTimes)), newestConversation: new Date(Math.max(...createdTimes)) }; } }