UNPKG

contaigents

Version:

Modular AI Content Ecosystem with Audio Generation

378 lines (375 loc) • 15.5 kB
import { ChatService } from './chatService.js'; import { ConversationPersistence } from './conversation/ConversationPersistence.js'; import readline from 'readline'; export class AgentCLI { constructor(baseDir) { this.currentSession = null; this.currentConversationId = null; this.chatService = new ChatService(baseDir); this.persistence = new ConversationPersistence(baseDir); } /** * Start a new conversation or resume an existing one */ async startConversation(options = {}) { // Try to resume existing conversation if (options.conversationId) { const persisted = await this.persistence.loadConversation(options.conversationId); if (persisted) { console.log(`šŸ“‚ Resuming conversation: ${persisted.name || persisted.id}`); if (persisted.description) { console.log(`šŸ“ Description: ${persisted.description}`); } if (persisted.tags && persisted.tags.length > 0) { console.log(`šŸ·ļø Tags: ${persisted.tags.join(', ')}`); } console.log(`šŸ’¬ Messages: ${persisted.session.messages.length}`); console.log(''); // Restore session to ChatService this.currentSession = persisted.session; this.currentConversationId = persisted.id; this.chatService.restoreSession(persisted.session); return persisted.session; } else { console.log(`āš ļø Conversation ${options.conversationId} not found. Starting new conversation.`); } } // Create new conversation const session = await this.chatService.createSession(options); this.currentSession = session; this.currentConversationId = session.id; // Save initial conversation if (options.autosave !== false) { await this.saveCurrentConversation({ name: options.conversationName, description: options.conversationDescription, tags: options.tags }); } console.log(`šŸ’¬ New conversation started: ${session.id}`); if (options.conversationName) { console.log(`šŸ“ Name: ${options.conversationName}`); } return session; } /** * Send a message in the current conversation */ async sendMessage(message, options) { if (!this.currentSession) { throw new Error('No active conversation. Start a conversation first.'); } const response = await this.chatService.sendMessage(this.currentSession.id, message, options); // Auto-save after each message await this.saveCurrentConversation(); return response; } /** * Save the current conversation */ async saveCurrentConversation(metadata) { if (!this.currentSession || !this.currentConversationId) { return; } const persisted = { id: this.currentConversationId, session: this.currentSession, lastAccessed: Date.now(), ...metadata }; await this.persistence.saveConversation(persisted); } /** * List all saved conversations */ async listConversations() { return await this.persistence.listConversations(); } /** * Search conversations */ async searchConversations(query) { return await this.persistence.searchConversations(query); } /** * Delete a conversation */ async deleteConversation(conversationId) { return await this.persistence.deleteConversation(conversationId); } /** * Export a conversation */ async exportConversation(conversationId, format = 'json') { return await this.persistence.exportConversation(conversationId, format); } /** * Get conversation statistics */ async getStatistics() { return await this.persistence.getStatistics(); } /** * Start interactive CLI session */ async startInteractiveSession(options = {}) { console.log("šŸ¤– Starting Enhanced Contaigents Agent CLI..."); // Set up debug logging if enabled if (options.debug) { const debugFile = options.debugFile || 'agent-cli-debug.log'; console.log(`šŸ” Debug logging enabled (saving to ${debugFile})`); } // Start or resume conversation const session = await this.startConversation(options); console.log("Type 'help' for commands, 'exit' to quit."); console.log("šŸ’” Tip: For multiline input, paste your content and press Enter twice to submit.\n"); // Set up readline interface with multiline support const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: 'šŸ‘¤ You: ' }); // Handle graceful shutdown const cleanup = async () => { console.log("\nšŸ‘‹ Saving conversation and exiting..."); await this.saveCurrentConversation(); if (options.debug) { const debugLogger = this.chatService.getDebugLogger(); const dumpFile = `agent-cli-debug-dump-${Date.now()}.json`; await debugLogger.saveDumpToFile(dumpFile); console.log(`šŸ” Debug session saved to: ${dumpFile}`); } console.log("Goodbye!"); rl.close(); process.exit(0); }; process.on('SIGINT', () => cleanup().catch(console.error)); process.on('SIGTERM', () => cleanup().catch(console.error)); // Enhanced multiline input handling with paste detection let multilineBuffer = []; let isMultilineMode = false; let pasteTimeout = null; let rapidInputCount = 0; let lastInputTime = 0; let isProcessing = false; // Flag to prevent concurrent processing // Start the interactive loop rl.prompt(); rl.on('line', async (input) => { const currentTime = Date.now(); const timeSinceLastInput = currentTime - lastInputTime; lastInputTime = currentTime; // Check if we're already processing a request if (isProcessing) { console.log("ā³ Please wait, I'm still processing your previous message..."); rl.prompt(); return; } // Detect rapid input (likely paste) - multiple lines within 100ms if (timeSinceLastInput < 100) { rapidInputCount++; } else { rapidInputCount = 0; } const userInput = input.trim(); // Handle empty line in multiline mode if (!userInput && isMultilineMode) { // Empty line - submit multiline input const fullInput = multilineBuffer.join('\n').trim(); multilineBuffer = []; isMultilineMode = false; rapidInputCount = 0; if (fullInput) { try { isProcessing = true; console.log("šŸ¤” Thinking..."); const response = await this.sendMessage(fullInput, options); console.log(`šŸ¤– Assistant: ${response.content}\n`); } catch (error) { console.error(`āŒ Error: ${error.message}\n`); } finally { isProcessing = false; } } rl.prompt(); return; } // Handle empty line in normal mode if (!userInput && !isMultilineMode) { rl.prompt(); return; } // If we're in multiline mode, add to buffer if (isMultilineMode) { multilineBuffer.push(input); // Clear any existing timeout and set a new one if (pasteTimeout) { clearTimeout(pasteTimeout); } // Auto-submit after 1 second of no input in multiline mode pasteTimeout = setTimeout(async () => { if (multilineBuffer.length > 0 && !isProcessing) { console.log("\nšŸ“ Auto-submitting multiline input..."); const fullInput = multilineBuffer.join('\n').trim(); multilineBuffer = []; isMultilineMode = false; rapidInputCount = 0; if (fullInput) { try { isProcessing = true; console.log("šŸ¤” Thinking..."); const response = await this.sendMessage(fullInput, options); console.log(`šŸ¤– Assistant: ${response.content}\n`); } catch (error) { console.error(`āŒ Error: ${error.message}\n`); } finally { isProcessing = false; } } rl.prompt(); } }, 1000); rl.prompt(); return; } // Handle special commands (only in single-line mode) if (userInput.toLowerCase() === 'exit' || userInput.toLowerCase() === 'quit') { cleanup().catch(console.error); return; } if (userInput.toLowerCase() === 'help') { this.showHelp(); rl.prompt(); return; } if (userInput.toLowerCase().startsWith('save ')) { const name = userInput.substring(5).trim(); await this.saveCurrentConversation({ name }); console.log(`šŸ’¾ Conversation saved as: ${name}\n`); rl.prompt(); return; } if (userInput.toLowerCase() === 'list') { await this.showConversationList(); rl.prompt(); return; } if (userInput.toLowerCase().startsWith('search ')) { const query = userInput.substring(7).trim(); await this.showSearchResults(query); rl.prompt(); return; } if (userInput.toLowerCase() === 'stats') { await this.showStatistics(); rl.prompt(); return; } // Detect paste operation (rapid input or long single line) const isPastedContent = rapidInputCount >= 2 || input.length > 200 || input.includes('\n'); if (isPastedContent || userInput.toLowerCase() === 'multiline') { if (userInput.toLowerCase() === 'multiline') { console.log("šŸ“ Entering multiline mode. Type your message and press Enter twice to submit."); isMultilineMode = true; rl.prompt(); return; } else { // Handle pasted content multilineBuffer.push(input); isMultilineMode = true; console.log(`šŸ“ Detected pasted content. Collecting input... (press Enter on empty line to submit)`); rl.prompt(); return; } } // Process regular single-line message try { isProcessing = true; console.log("šŸ¤” Thinking..."); const response = await this.sendMessage(userInput, options); console.log(`šŸ¤– Assistant: ${response.content}\n`); } catch (error) { console.error(`āŒ Error: ${error.message}\n`); } finally { isProcessing = false; } rl.prompt(); }); rl.on('close', () => { cleanup().catch(console.error); }); } showHelp() { console.log(` šŸ“š Enhanced Agent CLI Commands: help - Show this help message save <name> - Save conversation with a name list - List all saved conversations search <query> - Search conversations by content stats - Show conversation statistics multiline - Enter multiline input mode exit, quit - Save and exit šŸ“ Input Methods: • Single line: Type your message and press Enter • Multiline: Paste content or type 'multiline', then press Enter twice to submit • Long text: Automatically detected and handled as multiline šŸ’” Tips: - Conversations are automatically saved after each message - Use natural language to interact with AI and tools - The AI can read, write, and search files in your project - All tool operations are logged and can be debugged - Paste multiline content directly - it will be detected automatically `); } async showConversationList() { const conversations = await this.listConversations(); if (conversations.length === 0) { console.log("šŸ“­ No saved conversations found.\n"); return; } console.log("šŸ“‚ Saved Conversations:"); conversations.slice(0, 10).forEach((conv, index) => { const name = conv.name || conv.id.substring(0, 8); const messageCount = conv.session.messages.length; const lastAccessed = new Date(conv.lastAccessed).toLocaleDateString(); console.log(` ${index + 1}. ${name} (${messageCount} messages, ${lastAccessed})`); }); if (conversations.length > 10) { console.log(` ... and ${conversations.length - 10} more`); } console.log(''); } async showSearchResults(query) { const results = await this.searchConversations(query); if (results.length === 0) { console.log(`šŸ” No conversations found matching "${query}"\n`); return; } console.log(`šŸ” Found ${results.length} conversation(s) matching "${query}":`); results.slice(0, 5).forEach((conv, index) => { const name = conv.name || conv.id.substring(0, 8); const messageCount = conv.session.messages.length; console.log(` ${index + 1}. ${name} (${messageCount} messages)`); }); console.log(''); } async showStatistics() { const stats = await this.getStatistics(); console.log(` šŸ“Š Conversation Statistics: Total Conversations: ${stats.totalConversations} Total Messages: ${stats.totalMessages} Average Messages per Conversation: ${stats.averageMessagesPerConversation} Oldest Conversation: ${stats.oldestConversation?.toLocaleDateString() || 'N/A'} Newest Conversation: ${stats.newestConversation?.toLocaleDateString() || 'N/A'} `); } }