UNPKG

@hivetechs/hive-ai

Version:

Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API

274 lines 9.73 kB
/** * ASCII Chart Utilities for Terminal Display * * Provides simple ASCII-based charts and visualizations for CLI output */ /** * ANSI color codes for terminal output */ const colors = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', // Foreground colors black: '\x1b[30m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m', // Background colors bgBlack: '\x1b[40m', bgRed: '\x1b[41m', bgGreen: '\x1b[42m', bgYellow: '\x1b[43m', bgBlue: '\x1b[44m', bgMagenta: '\x1b[45m', bgCyan: '\x1b[46m', bgWhite: '\x1b[47m' }; /** * Create a horizontal bar chart */ export function createBarChart(data, options = {}) { const { width = 40, showValues = true, showPercentages = true, title, unit = '' } = options; // Find max value for scaling const maxValue = Math.max(...data.map(d => d.value)); if (maxValue === 0) return 'No data to display'; let output = ''; // Add title if provided if (title) { output += `${colors.bright}${title}${colors.reset}\n`; output += '─'.repeat(width + 30) + '\n'; } // Generate bars data.forEach(item => { const barLength = Math.round((item.value / maxValue) * width); const barChar = '█'; // Get color const colorCode = item.color ? colors[item.color] : colors.blue; // Create bar const bar = colorCode + barChar.repeat(barLength) + colors.reset; const emptySpace = ' '.repeat(width - barLength); // Format label (truncate and pad to consistent width) const maxLabelLength = 15; const truncatedLabel = item.label.length > maxLabelLength ? item.label.substring(0, maxLabelLength - 3) + '...' : item.label; const label = truncatedLabel.padEnd(maxLabelLength); // Format value and percentage let valueStr = ''; if (showValues) { valueStr = ` ${item.value}${unit}`; } if (showPercentages && item.percentage !== undefined) { valueStr += ` (${item.percentage.toFixed(1)}%)`; } output += `${label} ${bar}${emptySpace}${valueStr}\n`; }); return output; } /** * Create a simple line chart using ASCII */ export function createLineChart(data, options = {}) { const { width = 50, height = 10, title } = options; if (data.length === 0) return 'No data to display'; const maxValue = Math.max(...data); const minValue = Math.min(...data); const range = maxValue - minValue || 1; // Create chart grid const chart = Array(height).fill(null).map(() => Array(width).fill(' ')); // Plot points data.forEach((value, index) => { const x = Math.floor((index / (data.length - 1)) * (width - 1)); const y = height - 1 - Math.floor(((value - minValue) / range) * (height - 1)); if (x >= 0 && x < width && y >= 0 && y < height) { chart[y][x] = '●'; } }); // Add axes for (let i = 0; i < height; i++) { chart[i][0] = '│'; } for (let i = 0; i < width; i++) { chart[height - 1][i] = '─'; } chart[height - 1][0] = '└'; // Convert to string let output = ''; if (title) { output += `${colors.bright}${title}${colors.reset}\n\n`; } // Add max value label output += `${maxValue.toFixed(0).padStart(6)} ┤\n`; // Add chart rows chart.forEach((row, i) => { if (i === Math.floor(height / 2)) { output += `${((maxValue + minValue) / 2).toFixed(0).padStart(6)} ┤`; } else if (i < height - 1) { output += ' │'; } else { output += `${minValue.toFixed(0).padStart(6)} ┴`; } output += row.join('') + '\n'; }); return output; } /** * Create a progress bar */ export function createProgressBar(current, total, options = {}) { const { width = 30, showPercentage = true, label } = options; const percentage = total > 0 ? (current / total) * 100 : 0; const filled = Math.min(Math.max(0, Math.round((percentage / 100) * width)), width); const empty = Math.max(0, width - filled); let bar = '['; bar += colors.green + '█'.repeat(filled) + colors.reset; bar += '░'.repeat(empty); bar += ']'; let output = ''; if (label) { // Truncate label if too long to prevent line overflow const maxLabelLength = 20; // Reserve space for progress bar and percentage const truncatedLabel = label.length > maxLabelLength ? label.substring(0, maxLabelLength - 3) + '...' : label; output += truncatedLabel.padEnd(maxLabelLength) + ': '; } output += bar; if (showPercentage) { output += ` ${percentage.toFixed(1)}%`; } return output; } /** * Create a gauge chart (semicircle) */ export function createGauge(value, max = 100, options = {}) { const { label, thresholds } = options; const percentage = (value / max) * 100; // Determine color based on thresholds let color = colors.green; if (thresholds) { if (percentage >= thresholds.critical) { color = colors.red; } else if (percentage >= thresholds.warning) { color = colors.yellow; } } // Create gauge visualization const gaugeWidth = 21; const filled = Math.round((percentage / 100) * gaugeWidth); let output = ''; if (label) { // Truncate label if too long to prevent overflow const maxLabelLength = gaugeWidth + 8; // Allow some extra space for the gauge frame const truncatedLabel = label.length > maxLabelLength ? label.substring(0, maxLabelLength - 3) + '...' : label; output += `${colors.bright}${truncatedLabel}${colors.reset}\n`; } // Top arc output += ' ╭' + '─'.repeat(gaugeWidth) + '╮\n'; // Gauge bar output += ' │'; output += color + '█'.repeat(filled) + colors.reset; output += '░'.repeat(gaugeWidth - filled); output += '│\n'; // Bottom with value - handle long numbers gracefully const valueText = `${value}/${max}`; const maxValueLength = Math.max(6, Math.min(gaugeWidth - 4, valueText.length + 2)); const leftPadding = Math.floor((gaugeWidth - maxValueLength) / 2); const rightPadding = gaugeWidth - maxValueLength - leftPadding; output += ' ╰' + '─'.repeat(Math.max(0, leftPadding)); output += ` ${valueText} `; output += '─'.repeat(Math.max(0, rightPadding)) + '╯\n'; return output; } /** * Create a simple table */ export function createTable(headers, rows, options = {}) { const { alignments = [], colors: rowColors = [] } = options; // Calculate column widths const columnWidths = headers.map((header, i) => { const maxWidth = Math.max(header.length, ...rows.map(row => (row[i] || '').toString().length)); return maxWidth + 2; // Add padding }); // Create separator const separator = '├' + columnWidths.map(w => '─'.repeat(w)).join('┼') + '┤\n'; const topBorder = '┌' + columnWidths.map(w => '─'.repeat(w)).join('┬') + '┐\n'; const bottomBorder = '└' + columnWidths.map(w => '─'.repeat(w)).join('┴') + '┘\n'; let output = topBorder; // Add headers output += '│'; headers.forEach((header, i) => { const alignment = alignments[i] || 'left'; const width = columnWidths[i]; output += ' ' + alignText(header, width - 2, alignment) + ' │'; }); output += '\n' + separator; // Add rows rows.forEach((row, rowIndex) => { output += '│'; row.forEach((cell, i) => { const alignment = alignments[i] || 'left'; const width = columnWidths[i]; const color = rowColors[rowIndex] || ''; const colorCode = color && colors[color] ? colors[color] : ''; const resetCode = colorCode ? colors.reset : ''; output += ' ' + colorCode + alignText(cell.toString(), width - 2, alignment) + resetCode + ' │'; }); output += '\n'; }); output += bottomBorder; return output; } /** * Helper function to align text */ function alignText(text, width, alignment) { const textLength = text.length; if (textLength >= width) { return text.substring(0, width); } const padding = width - textLength; switch (alignment) { case 'right': return ' '.repeat(padding) + text; case 'center': const leftPad = Math.floor(padding / 2); const rightPad = padding - leftPad; return ' '.repeat(leftPad) + text + ' '.repeat(rightPad); default: // left return text + ' '.repeat(padding); } } /** * Create sparkline chart */ export function createSparkline(data, width = 20) { if (data.length === 0) return ''; const min = Math.min(...data); const max = Math.max(...data); const range = max - min || 1; const sparkChars = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']; // Sample data if too many points let sampledData = data; if (data.length > width) { const step = data.length / width; sampledData = Array(width).fill(0).map((_, i) => data[Math.floor(i * step)]); } return sampledData.map(value => { const normalized = (value - min) / range; const index = Math.round(normalized * (sparkChars.length - 1)); return sparkChars[index]; }).join(''); } //# sourceMappingURL=ascii-charts.js.map