UNPKG

capsule-ai-cli

Version:

The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing

247 lines 10.3 kB
import chalk from 'chalk'; import { TextProcessor } from './text-processor.js'; import { uiStateManager } from './ui-state-manager.js'; export class ConversationRenderer { conversationBuffer = []; addItem(item) { this.conversationBuffer.push(item); } updateLastAssistantMessage(content) { const lastIndex = this.conversationBuffer.length - 1; if (lastIndex >= 0 && this.conversationBuffer[lastIndex].type === 'assistant') { this.conversationBuffer[lastIndex].content = content; return true; } return false; } getBuffer() { return this.conversationBuffer; } clearBuffer() { this.conversationBuffer.length = 0; } processBufferedOutput() { const bufferedOutput = uiStateManager.flushBufferedOutput(); for (const output of bufferedOutput) { const item = uiStateManager.formatBufferedOutput(output); if (item) { this.addItem(item); } } } addBufferedOutput(output) { const item = uiStateManager.formatBufferedOutput(output); if (item) { this.addItem(item); } } getLastAssistantIndex() { for (let i = this.conversationBuffer.length - 1; i >= 0; i--) { if (this.conversationBuffer[i].type === 'assistant') { return i; } } return -1; } formatItem(item) { switch (item.type) { case 'user': return chalk.blue('> ') + item.content; case 'assistant': return chalk.green('◆ ') + item.content; case 'tool-call': return chalk.white(`⏺ ${item.metadata?.toolName}(${item.content})`); case 'tool-result': if (item.metadata?.toolName === 'Edit' && item.metadata?.success && item.metadata?.diffData) { return this.formatFileEditDiff(item); } return item.metadata?.success ? chalk.gray(` ⎿ ✓ ${item.content}`) : chalk.red(` ⎿ ✗ ${item.content}`); case 'system': return item.metadata?.isWelcomeHeader ? item.content : chalk.dim(item.content); case 'error': return chalk.red('✗ ') + item.content; default: return item.content; } } getDisplayLines(terminalWidth) { const displayLines = []; this.conversationBuffer.forEach((item, index) => { if (index > 0) { const prevItem = this.conversationBuffer[index - 1]; if (this.shouldAddSpacing(prevItem, item)) { displayLines.push(''); } } const content = String(item.content || ''); const contentLines = content.split('\n'); let isFirstLine = true; for (const contentLine of contentLines) { let linePrefix = ''; let indent = ''; if (item.type === 'assistant') { linePrefix = isFirstLine ? chalk.green('◆ ') : ' '; indent = ' '; } else if (item.type === 'system' && item.metadata?.isWelcomeHeader) { linePrefix = ''; indent = ''; } else { linePrefix = isFirstLine ? this.formatItem({ ...item, content: '' }) : ' '; indent = ' '; } const wrappedLines = TextProcessor.wrapText(contentLine, terminalWidth - TextProcessor.getVisibleLength(linePrefix)); for (let j = 0; j < wrappedLines.length; j++) { const wrappedPrefix = j === 0 ? linePrefix : indent; displayLines.push(wrappedPrefix + wrappedLines[j]); } isFirstLine = false; } }); return displayLines; } shouldAddSpacing(prevItem, currentItem) { if (currentItem.type === 'user' || currentItem.type === 'assistant') { return true; } if (currentItem.type === 'tool-call' && prevItem.type !== 'tool-call') { return true; } return false; } formatMessageContent(content) { let formatted = content; formatted = formatted.replace(/^(#{2,3})\s+(.+)$/gm, (_match, _hashes, title) => chalk.bold(title)); formatted = formatted.replace(/^(\s*)-\s+/gm, (_match, spaces) => `${spaces}• `); formatted = formatted.replace(/^(\s*)(\d+)\.\s+/gm, (_match, spaces, num) => `${spaces}${num}. `); formatted = formatted.replace(/\*\*([^*]+)\*\*/g, (_match, text) => chalk.bold(text)); formatted = formatted.replace(/`([^`]+)`/g, (_match, code) => chalk.yellow(code)); const parts = formatted.split(/```(\w+)?\n?/); let finalFormatted = ''; for (let index = 0; index < parts.length; index++) { const part = parts[index]; if (index % 2 === 0) { finalFormatted += part; } else if (/^\w+$/.test(part)) { finalFormatted += chalk.gray('```' + part) + '\n'; } else { finalFormatted += chalk.yellow(part.trim()) + '\n' + chalk.gray('```'); } } return finalFormatted; } getConversationPreview(maxLength = 50) { const firstUserMessage = this.conversationBuffer.find(item => item.type === 'user'); if (!firstUserMessage) { return 'New Chat'; } const preview = firstUserMessage.content .replace(/\n/g, ' ') .substring(0, maxLength); return preview + (firstUserMessage.content.length > maxLength ? '...' : ''); } getStats() { const stats = { messageCount: this.conversationBuffer.length, userMessageCount: 0, assistantMessageCount: 0, toolCallCount: 0, errorCount: 0 }; this.conversationBuffer.forEach(item => { switch (item.type) { case 'user': stats.userMessageCount++; break; case 'assistant': stats.assistantMessageCount++; break; case 'tool-call': stats.toolCallCount++; break; case 'error': stats.errorCount++; break; } }); return stats; } formatFileEditDiff(item) { const diff = item.metadata?.diffData; if (!diff) { return chalk.gray(` ⎿ ✓ ${item.content}`); } const lines = []; lines.push(chalk.gray(` ⎿ `) + chalk.green(`Updated ${diff.filePath} with ${diff.additions} addition${diff.additions !== 1 ? 's' : ''} and ${diff.deletions} deletion${diff.deletions !== 1 ? 's' : ''}`)); const oldLines = diff.oldText.split('\n'); const newLines = diff.newText.split('\n'); const startLine = Math.max(1, diff.lineNumber - 2); for (let i = 0; i < Math.max(oldLines.length, newLines.length); i++) { const lineNum = startLine + i; const oldLine = oldLines[i]; const newLine = newLines[i]; if (oldLine === newLine && oldLine !== undefined) { lines.push(chalk.gray(` ${lineNum.toString().padStart(3)} `) + chalk.gray(oldLine)); } else { if (oldLine !== undefined && newLine === undefined) { lines.push(chalk.red(` ${lineNum.toString().padStart(3)} - ${oldLine}`)); } else if (oldLine === undefined && newLine !== undefined) { lines.push(chalk.green(` ${lineNum.toString().padStart(3)} + ${newLine}`)); } else if (oldLine !== undefined && newLine !== undefined) { lines.push(chalk.red(` ${lineNum.toString().padStart(3)} - ${oldLine}`)); lines.push(chalk.green(` ${lineNum.toString().padStart(3)} + ${newLine}`)); } } } return lines.join('\n'); } exportToMarkdown() { let markdown = '# Conversation Export\n\n'; markdown += `_Exported on ${new Date().toLocaleString()}_\n\n`; markdown += '---\n\n'; this.conversationBuffer.forEach((item, index) => { const timestamp = item.timestamp.toLocaleTimeString(); switch (item.type) { case 'user': markdown += `### User [${timestamp}]\n\n${item.content}\n\n`; break; case 'assistant': markdown += `### Assistant [${timestamp}]`; if (item.metadata?.model) { markdown += ` (${item.metadata.model})`; } markdown += `\n\n${item.content}\n\n`; break; case 'tool-call': markdown += `#### Tool Call: ${item.metadata?.toolName} [${timestamp}]\n`; markdown += `\`\`\`\n${item.content}\n\`\`\`\n\n`; break; case 'tool-result': markdown += `#### Tool Result [${timestamp}]\n`; markdown += `Status: ${item.metadata?.success ? '✓ Success' : '✗ Failed'}\n`; markdown += `\`\`\`\n${item.content}\n\`\`\`\n\n`; break; case 'system': markdown += `_System: ${item.content}_\n\n`; break; case 'error': markdown += `**Error [${timestamp}]:** ${item.content}\n\n`; break; } if (index < this.conversationBuffer.length - 1) { markdown += '---\n\n'; } }); return markdown; } } export const conversationRenderer = new ConversationRenderer(); //# sourceMappingURL=conversation-renderer.js.map