sf-agent-framework
Version:
AI Agent Orchestration Framework for Salesforce Development - Two-phase architecture with 70% context reduction
463 lines (383 loc) • 13.2 kB
JavaScript
/**
* Visualization Engine for SF-Agent Framework
* Creates ASCII art dashboards and progress visualizations
*/
class VisualizationEngine {
constructor(options = {}) {
this.options = {
type: options.type || 'ascii', // ascii, text, or both
width: options.width || 80,
colors: options.colors !== false, // Enable colors by default
...options,
};
// ANSI color codes
this.colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
white: '\x1b[37m',
};
}
/**
* Render main dashboard
*/
renderDashboard(data) {
if (this.options.type === 'text') {
return this.renderTextDashboard(data);
}
return this.renderASCIIDashboard(data);
}
/**
* Render ASCII art dashboard
*/
renderASCIIDashboard(data) {
const width = this.options.width;
const lines = [];
// Header
lines.push(this.renderHeader(data.workflow, width));
lines.push('');
// Progress bar
lines.push(this.renderProgressSection(data.progress, data.phase, data.status));
lines.push('');
// Team status
if (data.team && data.team.length > 0) {
lines.push(this.renderTeamSection(data.team));
lines.push('');
}
// Active tasks
if (data.tasks && data.tasks.length > 0) {
lines.push(this.renderTasksSection(data.tasks, 'Active Tasks'));
lines.push('');
}
// Completed tasks
if (data.completedTasks && data.completedTasks.length > 0) {
lines.push(this.renderCompletedSection(data.completedTasks));
lines.push('');
}
// Live feed
if (data.events) {
lines.push(this.renderLiveFeed(data.events));
}
return lines.join('\n');
}
/**
* Render header with workflow name
*/
renderHeader(workflowName, width) {
const title = ` ${workflowName.toUpperCase()} ORCHESTRATION `;
const padding = Math.max(0, width - title.length - 2);
const leftPad = Math.floor(padding / 2);
const rightPad = padding - leftPad;
const lines = [
'╔' + '═'.repeat(width - 2) + '╗',
'║' + ' '.repeat(leftPad) + this.color('bright', title) + ' '.repeat(rightPad) + '║',
'╚' + '═'.repeat(width - 2) + '╝',
];
return lines.join('\n');
}
/**
* Render progress section
*/
renderProgressSection(progress, phase, status) {
const lines = [];
// Progress bar
const validProgress = progress !== undefined && !isNaN(progress) ? progress : 0;
const bar = this.renderProgressBar(validProgress, 40);
lines.push(
`${this.color('cyan', '▶ PROGRESS:')} ${bar} ${this.color('bright', `${validProgress}%`)}`
);
// Current phase
if (phase) {
lines.push(`${this.color('cyan', '▶ PHASE:')} ${phase}`);
}
// Status
if (status) {
const statusColor = status.includes('Error')
? 'red'
: status.includes('Complete')
? 'green'
: 'yellow';
lines.push(`${this.color('cyan', '▶ STATUS:')} ${this.color(statusColor, status)}`);
}
return lines.join('\n');
}
/**
* Render progress bar
*/
renderProgressBar(progress, width = 20) {
// Handle undefined or NaN progress
const validProgress = progress !== undefined && !isNaN(progress) ? progress : 0;
const filled = Math.floor((validProgress / 100) * width);
const empty = Math.max(0, width - filled);
const filledChar = '█';
const emptyChar = '░';
const bar =
this.color('green', filledChar.repeat(filled)) + this.color('white', emptyChar.repeat(empty));
return `[${bar}]`;
}
/**
* Render team section with status
*/
renderTeamSection(team) {
const lines = [];
lines.push('═══════════════════════════════════════════════════════════════════════════');
lines.push(this.color('bright', 'TEAM STATUS DASHBOARD'));
lines.push('═══════════════════════════════════════════════════════════════════════════');
team.forEach((member) => {
const statusIcon = this.getStatusIcon(member.status);
const statusColor = this.getStatusColor(member.status);
lines.push('');
lines.push(`${member.icon || '👤'} ${this.color('bright', member.name)} (${member.role})`);
lines.push(
` Status: ${this.color(statusColor, statusIcon + ' ' + member.status.toUpperCase())}`
);
if (member.currentTask) {
lines.push(` Task: ${member.currentTask}`);
if (member.progress > 0) {
const miniBar = this.renderProgressBar(member.progress, 15);
lines.push(` Progress: ${miniBar} ${member.progress}%`);
}
} else {
lines.push(` Task: ${this.color('white', 'Available for assignment')}`);
}
});
lines.push('═══════════════════════════════════════════════════════════════════════════');
return lines.join('\n');
}
/**
* Render tasks section
*/
renderTasksSection(tasks, title) {
const lines = [];
lines.push(this.renderSectionHeader(title));
// Create table
const table = this.createTable(
['Task', 'Agent', 'Progress', 'Status'],
tasks.map((task) => [
task.name || 'Unknown',
task.agent || 'Unassigned',
`${task.progress || 0}%`,
task.status || 'In Progress',
])
);
lines.push(table);
return lines.join('\n');
}
/**
* Render completed tasks section
*/
renderCompletedSection(completedTasks) {
const lines = [];
lines.push(this.renderSectionHeader('Recently Completed'));
// Show last 5 completed tasks
const recent = completedTasks.slice(-5);
recent.forEach((task) => {
lines.push(` ${this.color('green', '✓')} ${task.name || task}`);
});
return lines.join('\n');
}
/**
* Render live activity feed
*/
renderLiveFeed(events) {
const lines = [];
lines.push(this.color('cyan', '📡 LIVE ACTIVITY FEED:'));
lines.push('─'.repeat(this.options.width - 5));
// Show last 5 events
const recent = events.slice(-5);
recent.forEach((event) => {
const time = new Date(event.timestamp).toLocaleTimeString();
lines.push(`[${time}] ${event.icon || '•'} ${event.message}`);
});
return lines.join('\n');
}
/**
* Render section header
*/
renderSectionHeader(title) {
const header = ` ${title} `;
const lineWidth = 40;
const padding = Math.max(0, lineWidth - header.length);
const leftPad = Math.floor(padding / 2);
const rightPad = padding - leftPad;
return '─'.repeat(leftPad) + this.color('bright', header) + '─'.repeat(rightPad);
}
/**
* Create a simple ASCII table
*/
createTable(headers, rows) {
const colWidths = headers.map((h, i) => {
const maxWidth = Math.max(h.length, ...rows.map((r) => (r[i] || '').toString().length));
return Math.min(maxWidth, 20); // Cap at 20 chars
});
const lines = [];
// Header
lines.push('┌' + colWidths.map((w) => '─'.repeat(w + 2)).join('┬') + '┐');
lines.push(
'│ ' + headers.map((h, i) => this.color('bright', h.padEnd(colWidths[i]))).join(' │ ') + ' │'
);
lines.push('├' + colWidths.map((w) => '─'.repeat(w + 2)).join('┼') + '┤');
// Rows
rows.forEach((row) => {
lines.push(
'│ ' +
row
.map((cell, i) =>
(cell || '').toString().substring(0, colWidths[i]).padEnd(colWidths[i])
)
.join(' │ ') +
' │'
);
});
// Footer
lines.push('└' + colWidths.map((w) => '─'.repeat(w + 2)).join('┴') + '┘');
return lines.join('\n');
}
/**
* Render text-only dashboard (simpler version)
*/
renderTextDashboard(data) {
const lines = [];
lines.push(`=== ${data.workflow} ===`);
lines.push(`Progress: ${data.progress}%`);
lines.push(`Phase: ${data.phase || 'N/A'}`);
lines.push(`Status: ${data.status || 'Running'}`);
if (data.team) {
lines.push('\nTeam Status:');
data.team.forEach((member) => {
lines.push(` - ${member.name}: ${member.status}`);
});
}
return lines.join('\n');
}
/**
* Render workflow summary
*/
renderSummary(data) {
const lines = [];
lines.push('');
lines.push('╔══════════════════════════════════════════════════════════════════════════╗');
lines.push('║' + this.center('WORKFLOW COMPLETE', 76) + '║');
lines.push('╚══════════════════════════════════════════════════════════════════════════╝');
lines.push('');
lines.push(`${this.color('green', '✅')} Workflow: ${data.workflow}`);
lines.push(`${this.color('green', '✅')} Duration: ${this.formatDuration(data.duration)}`);
lines.push(
`${this.color('green', '✅')} Tasks Completed: ${data.completedTasks ? data.completedTasks.length : 0}`
);
lines.push(`${this.color('green', '✅')} Result: ${data.result}`);
if (data.team) {
lines.push('');
lines.push('Team Performance:');
data.team.forEach((member) => {
const icon = member.progress === 100 ? '✅' : '🔄';
lines.push(` ${icon} ${member.name}: ${member.progress}% complete`);
});
}
return lines.join('\n');
}
/**
* Show error visualization
*/
showError(error) {
const lines = [];
lines.push('');
lines.push(
this.color(
'red',
'╔══════════════════════════════════════════════════════════════════════════╗'
)
);
lines.push(this.color('red', '║' + this.center('ERROR OCCURRED', 76) + '║'));
lines.push(
this.color(
'red',
'╚══════════════════════════════════════════════════════════════════════════╝'
)
);
lines.push('');
lines.push(this.color('red', '❌ Simulation stopped due to error:'));
lines.push(this.color('yellow', ` ${error}`));
lines.push('');
console.log(lines.join('\n'));
}
/**
* Get status icon
*/
getStatusIcon(status) {
const icons = {
available: '🟢',
working: '🔄',
in_progress: '🔄',
complete: '✅',
error: '❌',
paused: '⏸️',
waiting: '⏳',
};
return icons[status.toLowerCase()] || '•';
}
/**
* Get status color
*/
getStatusColor(status) {
const colors = {
available: 'green',
working: 'yellow',
in_progress: 'yellow',
complete: 'green',
error: 'red',
paused: 'white',
waiting: 'cyan',
};
return colors[status.toLowerCase()] || 'white';
}
/**
* Apply color if enabled
*/
color(colorName, text) {
if (!this.options.colors) return text;
const colorCode = this.colors[colorName] || '';
return colorCode + text + this.colors.reset;
}
/**
* Center text
*/
center(text, width) {
const padding = Math.max(0, width - text.length);
const leftPad = Math.floor(padding / 2);
const rightPad = padding - leftPad;
return ' '.repeat(leftPad) + text + ' '.repeat(rightPad);
}
/**
* Format duration
*/
formatDuration(ms) {
if (ms < 1000) return `${ms}ms`;
if (ms < 60000) return `${Math.round(ms / 1000)} seconds`;
if (ms < 3600000) return `${Math.round(ms / 60000)} minutes`;
return `${(ms / 3600000).toFixed(1)} hours`;
}
/**
* Create a command menu visualization
*/
renderCommandMenu(commands) {
const lines = [];
lines.push('');
lines.push(this.renderSectionHeader('Available Commands'));
lines.push('');
commands.forEach((cmd, index) => {
const num = this.color('cyan', `${index + 1}.`);
const command = this.color('yellow', `\`${cmd.command}\``);
lines.push(` ${num} ${command} - ${cmd.description}`);
});
lines.push('');
lines.push('Please select an option (1-' + commands.length + ') or type command.');
return lines.join('\n');
}
}
module.exports = VisualizationEngine;