contaigents
Version:
Modular AI Content Ecosystem with Audio Generation
205 lines (204 loc) • 7.75 kB
JavaScript
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))
};
}
}