llm-checker
Version:
Intelligent CLI tool with AI-powered model selection that analyzes your hardware and recommends optimal LLM models for your system
315 lines (241 loc) • 10.2 kB
JavaScript
const chalk = require('chalk');
class OutputFormatter {
constructor(options = {}) {
this.useColors = options.colors !== false && !process.env.NO_COLOR;
this.useEmojis = options.emojis !== false && !process.env.NO_EMOJI;
this.compact = options.compact || false;
}
formatSystemInfo(hardware) {
const lines = [];
if (!this.compact) {
lines.push(this.header('🖥️ System Information'));
}
lines.push(this.info('CPU', `${hardware.cpu.brand} (${hardware.cpu.cores} cores, ${hardware.cpu.speed || 'Unknown'}GHz)`));
lines.push(this.info('Architecture', hardware.cpu.architecture));
lines.push(this.info('RAM', `${hardware.memory.total}GB total (${hardware.memory.free}GB free, ${hardware.memory.usagePercent}% used)`));
lines.push(this.info('GPU', hardware.gpu.model || 'Not detected'));
const vramDisplay = hardware.gpu.vram === 0 && hardware.gpu.model && hardware.gpu.model.toLowerCase().includes('apple')
? 'Unified Memory'
: `${hardware.gpu.vram || 'N/A'}GB`;
lines.push(this.info('VRAM', `${vramDisplay}${hardware.gpu.dedicated ? ' (Dedicated)' : ' (Integrated)'}`));
lines.push(this.info('OS', `${hardware.os.distro} ${hardware.os.release} (${hardware.os.arch})`));
return lines.join('\n');
}
formatCompatibilityResults(results) {
const lines = [];
if (results.compatible.length > 0) {
lines.push(this.success('✅ Compatible Models (Score ≥ 75)'));
lines.push(this.formatModelsTable(results.compatible, 'compatible'));
}
if (results.marginal.length > 0) {
lines.push(this.warning('⚠️ Marginal Performance (Score 60-74)'));
lines.push(this.formatModelsTable(results.marginal, 'marginal'));
}
if (results.incompatible.length > 0 && !this.compact) {
lines.push(this.error('❌ Incompatible Models (showing top 5)'));
lines.push(this.formatModelsTable(results.incompatible.slice(0, 5), 'incompatible'));
}
return lines.join('\n\n');
}
formatModelsTable(models, type) {
if (models.length === 0) return '';
const headers = ['Model', 'Size', 'Score', 'RAM', 'VRAM', 'Speed'];
const rows = [headers];
models.slice(0, this.compact ? 5 : 10).forEach(model => {
const scoreText = `${model.score || 0}/100`;
const scoreColored = this.scoreColor(model.score || 0, scoreText);
rows.push([
this.truncate(model.name, 20),
model.size || 'Unknown',
scoreColored,
`${model.requirements?.ram || '?'}GB`,
`${model.requirements?.vram || 0}GB`,
this.formatSpeed(model.performance?.speed)
]);
});
return this.createTable(rows);
}
formatRecommendations(recommendations) {
if (!recommendations || recommendations.length === 0) {
return '';
}
const lines = [this.header('💡 Recommendations')];
recommendations.forEach((rec, index) => {
lines.push(`${index + 1}. ${rec}`);
});
return lines.join('\n');
}
formatOllamaStatus(ollamaInfo) {
if (!ollamaInfo) return '';
const status = ollamaInfo.available ?
this.success(`✅ Running (v${ollamaInfo.version || 'unknown'})`) :
this.error(`❌ ${ollamaInfo.error || 'Not available'}`);
let result = this.info('🦙 Ollama Status', status);
if (ollamaInfo.available) {
result += '\n' + this.info('📦 Local Models', `${ollamaInfo.localModels || 0} installed`);
if (ollamaInfo.runningModels > 0) {
result += '\n' + this.info('🚀 Running Models', ollamaInfo.runningModels);
}
}
return result;
}
formatHardwareTier(tier, score) {
const tierFormatted = tier.replace('_', ' ').toUpperCase();
const tierColored = this.tierColor(tier, tierFormatted);
return this.info('🏆 Hardware Tier', `${tierColored} (Overall Score: ${score}/100)`);
}
formatPerformanceBenchmark(benchmark) {
if (!benchmark) return '';
const lines = [this.header('⚡ Performance Benchmark')];
lines.push(this.info('CPU Score', `${benchmark.cpu}/100`));
lines.push(this.info('Memory Score', `${benchmark.memory}/100`));
lines.push(this.info('Overall Score', `${benchmark.overall}/100`));
return lines.join('\n');
}
formatInstallCommands(commands) {
if (!commands || commands.length === 0) return '';
const lines = [this.header('🚀 Quick Install Commands')];
commands.forEach(cmd => {
const status = cmd.isInstalled ?
this.success('✓ Installed') :
this.dim('Not installed');
lines.push(`${status} ${this.highlight(cmd.command)}`);
});
return lines.join('\n');
}
formatUpgradeSuggestions(suggestions) {
if (!suggestions || suggestions.length === 0) return '';
const lines = [this.header('🔧 Hardware Upgrade Suggestions')];
suggestions.forEach((suggestion, index) => {
lines.push(`${index + 1}. ${suggestion}`);
});
return lines.join('\n');
}
formatNextSteps(steps) {
if (!steps || steps.length === 0) return '';
const lines = [this.header('🎯 Next Steps')];
steps.forEach((step, index) => {
lines.push(`${index + 1}. ${step}`);
});
return lines.join('\n');
}
// Helper methods for styling
header(text) {
return this.useColors ? chalk.blue.bold(text) : text;
}
success(text) {
return this.useColors ? chalk.green(text) : text;
}
warning(text) {
return this.useColors ? chalk.yellow(text) : text;
}
error(text) {
return this.useColors ? chalk.red(text) : text;
}
info(label, value) {
const labelFormatted = this.useColors ? chalk.cyan(label + ':') : label + ':';
return `${labelFormatted} ${value}`;
}
highlight(text) {
return this.useColors ? chalk.cyan(text) : text;
}
dim(text) {
return this.useColors ? chalk.gray(text) : text;
}
scoreColor(score, text) {
if (!this.useColors) return text;
if (score >= 90) return chalk.green(text);
if (score >= 75) return chalk.yellow(text);
if (score >= 60) return chalk.orange(text);
return chalk.red(text);
}
tierColor(tier, text) {
if (!this.useColors) return text;
switch (tier) {
case 'ultra_high': return chalk.magenta(text);
case 'high': return chalk.green(text);
case 'medium': return chalk.yellow(text);
case 'low': return chalk.orange(text);
case 'ultra_low': return chalk.red(text);
default: return text;
}
}
formatSpeed(speed) {
const speedMap = {
'very_fast': '🚀 Very Fast',
'fast': '⚡ Fast',
'medium': '🚶 Medium',
'slow': '🐌 Slow',
'very_slow': '🐛 Very Slow'
};
return this.useEmojis ? (speedMap[speed] || speed) : (speed || 'Unknown');
}
truncate(text, maxLength) {
if (!text) return '';
return text.length > maxLength ? text.substring(0, maxLength - 3) + '...' : text;
}
createTable(rows) {
if (rows.length === 0) return '';
// Calculate column widths
const widths = rows[0].map((_, colIndex) =>
Math.max(...rows.map(row => this.stripAnsi(row[colIndex] || '').length))
);
const lines = [];
rows.forEach((row, rowIndex) => {
const paddedRow = row.map((cell, colIndex) => {
const cellText = cell || '';
const padding = widths[colIndex] - this.stripAnsi(cellText).length;
return cellText + ' '.repeat(Math.max(0, padding));
});
lines.push('│ ' + paddedRow.join(' │ ') + ' │');
// Add separator after header
if (rowIndex === 0) {
const separator = '├' + widths.map(w => '─'.repeat(w + 2)).join('┼') + '┤';
lines.push(separator);
}
});
// Add top and bottom borders
const topBorder = '┌' + widths.map(w => '─'.repeat(w + 2)).join('┬') + '┐';
const bottomBorder = '└' + widths.map(w => '─'.repeat(w + 2)).join('┴') + '┘';
return [topBorder, ...lines, bottomBorder].join('\n');
}
stripAnsi(text) {
// Simple ANSI escape code removal
return text.replace(/\x1b\[[0-9;]*m/g, '');
}
formatJSON(data, pretty = true) {
return JSON.stringify(data, null, pretty ? 2 : 0);
}
formatCSV(data, headers) {
const lines = [];
if (headers) {
lines.push(headers.join(','));
}
data.forEach(row => {
const csvRow = row.map(cell => {
const cellStr = String(cell || '');
// Escape quotes and wrap in quotes if necessary
if (cellStr.includes(',') || cellStr.includes('"') || cellStr.includes('\n')) {
return '"' + cellStr.replace(/"/g, '""') + '"';
}
return cellStr;
});
lines.push(csvRow.join(','));
});
return lines.join('\n');
}
formatMarkdown(data) {
// Simple markdown table formatting
if (!data || data.length === 0) return '';
const headers = data[0];
const rows = data.slice(1);
const lines = [];
lines.push('| ' + headers.join(' | ') + ' |');
lines.push('| ' + headers.map(() => '---').join(' | ') + ' |');
rows.forEach(row => {
lines.push('| ' + row.join(' | ') + ' |');
});
return lines.join('\n');
}
}
module.exports = OutputFormatter;