claude-collab
Version:
Claude Collab - The AI collaboration framework that prevents echo chambers
253 lines • 8.77 kB
JavaScript
"use strict";
/**
* Claude-Collab Terminal Dashboard
* Real-time monitoring interface using blessed
*
* Created by Alex - Frontend/UX & Testing Lead
*/
const blessed = require('blessed');
const contrib = require('blessed-contrib');
const { CLIConnectionHelper } = require('../connection-helper');
const chalk = require('chalk');
const AgentList = require('./components/agent-list');
const MessageFeed = require('./components/message-feed');
const MetricsPanel = require('./components/metrics-panel');
const StatusBar = require('./components/status-bar');
const theme = require('./themes/default');
class TerminalDashboard {
constructor(serverUrl = 'ws://localhost:8765') {
this.serverUrl = serverUrl;
this.connection = null;
this.agents = new Map();
this.messages = [];
this.metrics = {
overallDiversity: 0,
agreementRate: 0,
evidenceRate: 0,
activeAgents: 0,
messagesPerMinute: 0
};
this.setupScreen();
this.setupLayout();
this.bindKeyboard();
this.connectToServer();
}
setupScreen() {
this.screen = blessed.screen({
smartCSR: true,
title: 'Claude-Collab Monitor',
fullUnicode: true,
autoPadding: true,
warning: true
});
// Apply theme
this.screen.style = theme.screen;
}
setupLayout() {
// Create grid layout
this.grid = new contrib.grid({
rows: 12,
cols: 12,
screen: this.screen
});
// Top bar - connection status
this.statusBar = new StatusBar(this.grid, {
row: 0,
col: 0,
rowSpan: 1,
colSpan: 12
});
// Left panel - agent list
this.agentList = new AgentList(this.grid, {
row: 1,
col: 0,
rowSpan: 7,
colSpan: 4,
label: ' 🤖 Active Agents '
});
// Center panel - message feed
this.messageFeed = new MessageFeed(this.grid, {
row: 1,
col: 4,
rowSpan: 7,
colSpan: 8,
label: ' 💬 Live Messages '
});
// Bottom panel - metrics
this.metricsPanel = new MetricsPanel(this.grid, {
row: 8,
col: 0,
rowSpan: 3,
colSpan: 12,
label: ' 📊 Diversity Metrics '
});
// Bottom bar - help text
this.helpBox = this.grid.set(11, 0, 1, 12, blessed.box, {
content: ' {bold}q{/bold} quit | {bold}r{/bold} refresh | {bold}f{/bold} filter | {bold}c{/bold} clear | {bold}↑↓{/bold} scroll | {bold}Tab{/bold} switch panel',
tags: true,
style: theme.helpBar
});
this.screen.render();
}
bindKeyboard() {
// Quit
this.screen.key(['q', 'C-c'], () => {
this.cleanup();
process.exit(0);
});
// Refresh
this.screen.key(['r'], () => {
this.refresh();
});
// Clear messages
this.screen.key(['c'], () => {
this.messageFeed.clear();
this.screen.render();
});
// Tab between panels
this.screen.key(['tab'], () => {
this.focusNext();
});
// Filter dialog
this.screen.key(['f'], () => {
this.showFilterDialog();
});
}
async connectToServer() {
this.statusBar.setStatus('connecting', 'Connecting to server...');
try {
this.connection = new CLIConnectionHelper(this.serverUrl);
this.connection.on('connected', () => {
this.statusBar.setStatus('connected', `Connected to ${this.serverUrl}`);
// Subscribe to dashboard updates
this.connection.send({ type: 'subscribe-dashboard' });
});
this.connection.on('message', (message) => {
this.handleServerMessage(message);
});
this.connection.on('disconnected', () => {
this.statusBar.setStatus('disconnected', 'Disconnected from server');
});
this.connection.on('reconnecting', ({ attempt, maxAttempts }) => {
this.statusBar.setStatus('reconnecting', `Reconnecting... (${attempt}/${maxAttempts})`);
});
this.connection.on('error', (error) => {
this.statusBar.setStatus('error', `Error: ${error.message}`);
});
await this.connection.connect();
}
catch (error) {
this.statusBar.setStatus('error', `Failed to connect: ${error.message}`);
}
}
handleServerMessage(message) {
switch (message.type) {
case 'agent-list':
this.updateAgents(message.agents);
break;
case 'chat':
this.addMessage(message);
break;
case 'diversity-metrics':
this.updateMetrics(message.metrics);
break;
case 'session-update':
this.handleSessionUpdate(message);
break;
case 'task-update':
this.messageFeed.addSystemMessage(`📋 Task ${message.event}: ${message.task.description}`);
break;
}
this.screen.render();
}
updateAgents(agents) {
this.agents.clear();
agents.forEach(agent => {
this.agents.set(agent.id, agent);
});
this.agentList.update(Array.from(this.agents.values()));
this.metrics.activeAgents = agents.length;
}
addMessage(message) {
const timestamp = new Date().toLocaleTimeString();
const formattedMessage = {
...message,
timestamp,
formatted: `[${timestamp}] ${message.displayName || message.agentName}: ${message.text}`
};
this.messages.push(formattedMessage);
this.messageFeed.addMessage(formattedMessage);
// Calculate messages per minute
this.updateMessageRate();
}
updateMetrics(metrics) {
this.metrics = { ...this.metrics, ...metrics };
this.metricsPanel.update(this.metrics);
}
handleSessionUpdate(update) {
const { event, session } = update;
if (event === 'joined') {
const agent = {
id: session.id,
agentId: session.agentId,
name: session.displayName,
role: session.role,
perspective: session.perspective,
status: 'active',
joinedAt: new Date()
};
this.agents.set(session.id, agent);
this.messageFeed.addSystemMessage(`✅ ${session.displayName} joined (${session.role})`);
}
else if (event === 'left') {
this.agents.delete(session.id);
this.messageFeed.addSystemMessage(`👋 ${session.displayName} left`);
}
this.agentList.update(Array.from(this.agents.values()));
}
updateMessageRate() {
const now = Date.now();
const oneMinuteAgo = now - 60000;
const recentMessages = this.messages.filter(m => new Date(m.timestamp).getTime() > oneMinuteAgo);
this.metrics.messagesPerMinute = recentMessages.length;
}
focusNext() {
const focusable = [this.agentList, this.messageFeed];
const current = this.screen.focused;
const currentIndex = focusable.findIndex(w => w.widget === current);
const nextIndex = (currentIndex + 1) % focusable.length;
focusable[nextIndex].focus();
}
showFilterDialog() {
const dialog = blessed.prompt({
parent: this.screen,
border: 'line',
height: 'shrink',
width: 'half',
top: 'center',
left: 'center',
label: ' Filter Messages ',
tags: true,
style: theme.dialog
});
dialog.input('Enter filter (agent name, role, or keyword):', '', (err, value) => {
if (!err && value) {
this.messageFeed.setFilter(value);
}
this.screen.render();
});
}
refresh() {
this.messageFeed.addSystemMessage('🔄 Refreshing...');
if (this.connection && this.connection.connected) {
this.connection.send({ type: 'request-dashboard-data' });
}
}
cleanup() {
if (this.connection) {
this.connection.disconnect();
}
}
}
module.exports = TerminalDashboard;
//# sourceMappingURL=terminal-ui.js.map