UNPKG

automagik-cli

Version:

Automagik CLI - A powerful command-line interface for interacting with Automagik Hive multi-agent AI systems

179 lines (178 loc) 7.09 kB
/** * Markdown renderer for terminal display using ink components */ import React from 'react'; import { Box, Text } from 'ink'; import { Colors } from '../colors.js'; export class MarkdownRenderer { static parseMarkdown(text) { const tokens = []; const lines = text.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Empty line if (line.trim() === '') { tokens.push({ type: 'line_break', content: '' }); continue; } // Headings const headingMatch = line.match(/^(#{1,6})\s+(.+)$/); if (headingMatch) { tokens.push({ type: 'heading', content: headingMatch[2], level: headingMatch[1].length, }); continue; } // Code blocks if (line.startsWith('```')) { const language = line.substring(3).trim(); const codeLines = []; i++; // Skip the opening ``` while (i < lines.length && !lines[i].startsWith('```')) { codeLines.push(lines[i]); i++; } tokens.push({ type: 'code_block', content: codeLines.join('\n'), language: language || 'text', }); continue; } // List items if (line.match(/^\s*[-*+]\s+/)) { const content = line.replace(/^\s*[-*+]\s+/, ''); tokens.push({ type: 'list_item', content: content, }); continue; } // Regular paragraph with inline formatting tokens.push(...this.parseInlineFormatting(line)); } return tokens; } static parseInlineFormatting(text) { const tokens = []; let currentText = text; // Simple regex patterns for inline formatting const patterns = [ { regex: /`([^`]+)`/g, type: 'inline_code' }, { regex: /\*\*([^*]+)\*\*/g, type: 'bold' }, { regex: /\*([^*]+)\*/g, type: 'italic' }, { regex: /\[([^\]]+)\]\(([^)]+)\)/g, type: 'link' }, ]; let hasFormatting = false; for (const pattern of patterns) { if (pattern.regex.test(currentText)) { hasFormatting = true; break; } } if (!hasFormatting) { tokens.push({ type: 'paragraph', content: currentText }); return tokens; } // For now, treat as paragraph and let the renderer handle inline formatting tokens.push({ type: 'paragraph', content: currentText }); return tokens; } static renderToReact(tokens) { const elements = []; tokens.forEach((token, index) => { switch (token.type) { case 'heading': elements.push(React.createElement(Box, { key: index, marginY: 1 }, React.createElement(Text, { color: Colors.AccentBlue, bold: true, underline: token.level === 1, }, `${'#'.repeat(token.level || 1)} ${token.content}`))); break; case 'paragraph': elements.push(React.createElement(Box, { key: index, marginBottom: 1 }, React.createElement(Text, { color: Colors.Foreground, }, this.renderInlineFormatting(token.content)))); break; case 'code_block': elements.push(React.createElement(Box, { key: index, borderStyle: 'round', borderColor: Colors.Gray, paddingX: 1, marginY: 1, }, React.createElement(Text, { color: Colors.AccentGreen, backgroundColor: Colors.Background, }, token.content))); break; case 'inline_code': elements.push(React.createElement(Text, { key: index, color: Colors.AccentGreen, backgroundColor: Colors.Gray, }, token.content)); break; case 'list_item': elements.push(React.createElement(Box, { key: index, marginLeft: 2 }, React.createElement(Text, { color: Colors.AccentYellow, }, '• '), React.createElement(Text, { color: Colors.Foreground, }, token.content))); break; case 'line_break': elements.push(React.createElement(Box, { key: index, height: 1 })); break; default: elements.push(React.createElement(Text, { key: index, color: Colors.Foreground, }, token.content)); } }); return React.createElement(Box, { flexDirection: 'column' }, ...elements); } static renderInlineFormatting(text) { const elements = []; let currentText = text; let elementIndex = 0; // Handle inline code const codeRegex = /`([^`]+)`/g; let lastIndex = 0; let match; while ((match = codeRegex.exec(currentText)) !== null) { // Add text before the match if (match.index > lastIndex) { const beforeText = currentText.substring(lastIndex, match.index); if (beforeText) { elements.push(React.createElement(Text, { key: elementIndex++ }, beforeText)); } } // Add the code element elements.push(React.createElement(Text, { key: elementIndex++, color: Colors.AccentGreen, backgroundColor: Colors.Gray, }, match[1])); lastIndex = match.index + match[0].length; } // Add remaining text if (lastIndex < currentText.length) { const remainingText = currentText.substring(lastIndex); if (remainingText) { elements.push(React.createElement(Text, { key: elementIndex++ }, remainingText)); } } // If no inline formatting found, return the original text if (elements.length === 0) { elements.push(React.createElement(Text, { key: 0 }, currentText)); } return elements; } } export const renderMarkdown = (text) => { const tokens = MarkdownRenderer.parseMarkdown(text); return MarkdownRenderer.renderToReact(tokens); };