forked from Rockachopa/the-matrix
3D visualization for AI agent swarms built with Three.js. Matrix green/noir cyberpunk aesthetic. - 4 agents: Timmy (orchestrator), Forge (builder), Seer (planner), Echo (comms) - Central core pillar, animated green grid, digital rain - Agent info panels, chat, task list, memory views - WebSocket protocol for real-time state updates - iPad-ready: touch controls, add-to-homescreen - Post-processing: bloom, scanlines, vignette - No build step — pure ES modules via esm.sh CDN Created with Perplexity Computer
323 lines
10 KiB
JavaScript
323 lines
10 KiB
JavaScript
// ===== WebSocket Client + MockWebSocket =====
|
|
// Handles communication between the 3D world and agent backend
|
|
|
|
const AGENT_DEFS = {
|
|
timmy: { name: 'Timmy', role: 'Main Orchestrator', color: '#00ff41' },
|
|
forge: { name: 'Forge', role: 'Builder Agent', color: '#ff8c00' },
|
|
seer: { name: 'Seer', role: 'Planner / Observer', color: '#9d4edd' },
|
|
echo: { name: 'Echo', role: 'Communications', color: '#00d4ff' },
|
|
};
|
|
|
|
const AGENT_IDS = Object.keys(AGENT_DEFS);
|
|
|
|
const CANNED_RESPONSES = {
|
|
timmy: [
|
|
"All agents reporting nominal. I'm coordinating the current sprint.",
|
|
"Understood. I'll dispatch that to the appropriate agent.",
|
|
"Running diagnostics on the system now. Stand by.",
|
|
"I've updated the task queue. Forge is picking up the next item.",
|
|
"Status check complete — all systems green.",
|
|
],
|
|
forge: [
|
|
"Building that component now. ETA: 12 minutes.",
|
|
"The codebase is clean. Ready for the next feature.",
|
|
"I found a bug in the auth module. Patching now.",
|
|
"Deployment pipeline is green. Pushing to staging.",
|
|
"Refactoring complete. Tests passing.",
|
|
],
|
|
seer: [
|
|
"I see a pattern forming in the data. Analyzing further.",
|
|
"The plan has been updated. Three critical paths identified.",
|
|
"Forecasting suggests peak load at 14:00 UTC. Scaling recommended.",
|
|
"I've mapped all dependencies. No circular references detected.",
|
|
"Risk assessment complete. Proceeding with caution on module 7.",
|
|
],
|
|
echo: [
|
|
"Broadcast sent to all channels successfully.",
|
|
"I've notified the team about the status change.",
|
|
"Incoming transmission received. Routing to Timmy.",
|
|
"Communication logs archived. 47 messages in the last hour.",
|
|
"Alert dispatched. All stakeholders have been pinged.",
|
|
],
|
|
};
|
|
|
|
const TASK_TITLES = [
|
|
'Fix authentication bug', 'Deploy staging environment', 'Refactor database schema',
|
|
'Update API documentation', 'Optimize query performance', 'Implement rate limiting',
|
|
'Review pull request #42', 'Run security audit', 'Scale worker instances',
|
|
'Migrate to new CDN', 'Update SSL certificates', 'Analyze user patterns',
|
|
'Build dashboard widget', 'Configure monitoring alerts', 'Backup production data',
|
|
];
|
|
|
|
const MEMORY_ENTRIES = [
|
|
'Detected pattern: user prefers morning deployments',
|
|
'System load average decreased by 23% after optimization',
|
|
'New dependency vulnerability found in lodash@4.17.20',
|
|
'Agent coordination latency reduced to 12ms',
|
|
'User session patterns suggest peak activity at 10am EST',
|
|
'Codebase complexity score: 42 (within acceptable range)',
|
|
'Memory usage stable at 67% across all nodes',
|
|
'Anomaly detected in API response times — investigating',
|
|
'Successful failover test completed in 3.2 seconds',
|
|
'Cache hit ratio improved to 94% after config change',
|
|
];
|
|
|
|
function uuid() {
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
const r = Math.random() * 16 | 0;
|
|
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
|
});
|
|
}
|
|
|
|
function pick(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
|
|
|
|
export class MockWebSocket {
|
|
constructor() {
|
|
this.listeners = {};
|
|
this.agents = {};
|
|
this.tasks = {};
|
|
this.connections = [];
|
|
this.systemStatus = { agents_online: 4, tasks_pending: 0, tasks_running: 0, uptime: '0h 0m' };
|
|
this._startTime = Date.now();
|
|
this._intervals = [];
|
|
|
|
this._initAgents();
|
|
this._initTasks();
|
|
this._startSimulation();
|
|
}
|
|
|
|
on(event, callback) {
|
|
if (!this.listeners[event]) this.listeners[event] = [];
|
|
this.listeners[event].push(callback);
|
|
}
|
|
|
|
emit(event, data) {
|
|
(this.listeners[event] || []).forEach(cb => cb(data));
|
|
}
|
|
|
|
send(message) {
|
|
const msg = typeof message === 'string' ? JSON.parse(message) : message;
|
|
|
|
if (msg.type === 'chat_message') {
|
|
this._handleChat(msg);
|
|
} else if (msg.type === 'task_action') {
|
|
this._handleTaskAction(msg);
|
|
}
|
|
}
|
|
|
|
_initAgents() {
|
|
AGENT_IDS.forEach(id => {
|
|
this.agents[id] = {
|
|
id,
|
|
...AGENT_DEFS[id],
|
|
state: 'idle',
|
|
current_task: null,
|
|
glow_intensity: 0.5,
|
|
uptime: '0h 0m',
|
|
last_action: new Date().toISOString(),
|
|
messages: [],
|
|
memories: [],
|
|
};
|
|
});
|
|
}
|
|
|
|
_initTasks() {
|
|
const statuses = ['completed', 'completed', 'in_progress', 'pending', 'pending'];
|
|
const priorities = ['high', 'normal', 'normal', 'normal', 'high'];
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
const task = {
|
|
task_id: uuid(),
|
|
agent_id: pick(AGENT_IDS),
|
|
title: TASK_TITLES[i],
|
|
status: statuses[i],
|
|
priority: priorities[i],
|
|
};
|
|
this.tasks[task.task_id] = task;
|
|
}
|
|
|
|
this._updateSystemStatus();
|
|
}
|
|
|
|
_updateSystemStatus() {
|
|
const tasks = Object.values(this.tasks);
|
|
const elapsed = Date.now() - this._startTime;
|
|
const hours = Math.floor(elapsed / 3600000);
|
|
const mins = Math.floor((elapsed % 3600000) / 60000);
|
|
|
|
this.systemStatus = {
|
|
agents_online: AGENT_IDS.filter(id => this.agents[id]).length,
|
|
tasks_pending: tasks.filter(t => t.status === 'pending').length,
|
|
tasks_running: tasks.filter(t => t.status === 'in_progress').length,
|
|
tasks_completed: tasks.filter(t => t.status === 'completed').length,
|
|
tasks_failed: tasks.filter(t => t.status === 'failed').length,
|
|
total_tasks: tasks.length,
|
|
uptime: `${hours}h ${mins}m`,
|
|
};
|
|
}
|
|
|
|
_startSimulation() {
|
|
// Agent state changes every 4-8 seconds
|
|
this._intervals.push(setInterval(() => {
|
|
const agentId = pick(AGENT_IDS);
|
|
const states = ['idle', 'working', 'working', 'waiting'];
|
|
const state = pick(states);
|
|
const agent = this.agents[agentId];
|
|
agent.state = state;
|
|
agent.glow_intensity = state === 'working' ? 0.9 : state === 'waiting' ? 0.6 : 0.4;
|
|
agent.current_task = state === 'working' ? pick(TASK_TITLES) : null;
|
|
agent.last_action = new Date().toISOString();
|
|
|
|
this.emit('message', {
|
|
type: 'agent_state',
|
|
agent_id: agentId,
|
|
state,
|
|
current_task: agent.current_task,
|
|
glow_intensity: agent.glow_intensity,
|
|
});
|
|
}, 4000 + Math.random() * 4000));
|
|
|
|
// New tasks every 8-15 seconds
|
|
this._intervals.push(setInterval(() => {
|
|
const task = {
|
|
task_id: uuid(),
|
|
agent_id: Math.random() > 0.3 ? pick(AGENT_IDS) : null,
|
|
title: pick(TASK_TITLES),
|
|
status: 'pending',
|
|
priority: Math.random() > 0.7 ? 'high' : 'normal',
|
|
};
|
|
this.tasks[task.task_id] = task;
|
|
this._updateSystemStatus();
|
|
|
|
this.emit('message', { type: 'task_created', ...task });
|
|
}, 8000 + Math.random() * 7000));
|
|
|
|
// Task status updates every 5-10 seconds
|
|
this._intervals.push(setInterval(() => {
|
|
const pendingTasks = Object.values(this.tasks).filter(t => t.status === 'pending' || t.status === 'in_progress');
|
|
if (pendingTasks.length === 0) return;
|
|
|
|
const task = pick(pendingTasks);
|
|
if (task.status === 'pending') {
|
|
task.status = 'in_progress';
|
|
task.agent_id = task.agent_id || pick(AGENT_IDS);
|
|
} else {
|
|
task.status = Math.random() > 0.15 ? 'completed' : 'failed';
|
|
}
|
|
this._updateSystemStatus();
|
|
|
|
this.emit('message', {
|
|
type: 'task_update',
|
|
task_id: task.task_id,
|
|
agent_id: task.agent_id,
|
|
title: task.title,
|
|
status: task.status,
|
|
priority: task.priority,
|
|
});
|
|
}, 5000 + Math.random() * 5000));
|
|
|
|
// Memory events every 6-12 seconds
|
|
this._intervals.push(setInterval(() => {
|
|
const agentId = pick(AGENT_IDS);
|
|
const content = pick(MEMORY_ENTRIES);
|
|
const entry = { timestamp: new Date().toISOString(), content };
|
|
this.agents[agentId].memories.unshift(entry);
|
|
if (this.agents[agentId].memories.length > 50) this.agents[agentId].memories.pop();
|
|
|
|
this.emit('message', {
|
|
type: 'memory_event',
|
|
agent_id: agentId,
|
|
content,
|
|
timestamp: entry.timestamp,
|
|
});
|
|
}, 6000 + Math.random() * 6000));
|
|
|
|
// Connection lines flicker every 3-6 seconds
|
|
this._intervals.push(setInterval(() => {
|
|
const a = pick(AGENT_IDS);
|
|
let b = pick(AGENT_IDS);
|
|
while (b === a) b = pick(AGENT_IDS);
|
|
|
|
const active = Math.random() > 0.3;
|
|
const connKey = [a, b].sort().join('-');
|
|
|
|
if (active) {
|
|
if (!this.connections.includes(connKey)) this.connections.push(connKey);
|
|
} else {
|
|
this.connections = this.connections.filter(c => c !== connKey);
|
|
}
|
|
|
|
this.emit('message', {
|
|
type: 'connection',
|
|
agent_id: a,
|
|
target_id: b,
|
|
active,
|
|
});
|
|
}, 3000 + Math.random() * 3000));
|
|
|
|
// System status broadcast every 5 seconds
|
|
this._intervals.push(setInterval(() => {
|
|
this._updateSystemStatus();
|
|
this.emit('message', { type: 'system_status', ...this.systemStatus });
|
|
}, 5000));
|
|
}
|
|
|
|
_handleChat(msg) {
|
|
const agentId = msg.agent_id;
|
|
const agent = this.agents[agentId];
|
|
if (!agent) return;
|
|
|
|
agent.messages.push({ role: 'user', content: msg.content });
|
|
|
|
// Simulate typing delay
|
|
setTimeout(() => {
|
|
this.emit('typing', { agent_id: agentId });
|
|
}, 200);
|
|
|
|
setTimeout(() => {
|
|
const response = pick(CANNED_RESPONSES[agentId] || CANNED_RESPONSES.timmy);
|
|
agent.messages.push({ role: 'assistant', content: response });
|
|
|
|
this.emit('message', {
|
|
type: 'agent_message',
|
|
agent_id: agentId,
|
|
role: 'assistant',
|
|
content: response,
|
|
});
|
|
}, 1000 + Math.random() * 1500);
|
|
}
|
|
|
|
_handleTaskAction(msg) {
|
|
const task = this.tasks[msg.task_id];
|
|
if (!task) return;
|
|
|
|
if (msg.action === 'approve') {
|
|
task.status = 'in_progress';
|
|
} else if (msg.action === 'veto') {
|
|
task.status = 'failed';
|
|
}
|
|
this._updateSystemStatus();
|
|
|
|
this.emit('message', {
|
|
type: 'task_update',
|
|
task_id: task.task_id,
|
|
agent_id: task.agent_id,
|
|
title: task.title,
|
|
status: task.status,
|
|
priority: task.priority,
|
|
});
|
|
}
|
|
|
|
getAgent(id) { return this.agents[id]; }
|
|
getAgentTasks(id) { return Object.values(this.tasks).filter(t => t.agent_id === id); }
|
|
getSystemStatus() { this._updateSystemStatus(); return this.systemStatus; }
|
|
|
|
dispose() {
|
|
this._intervals.forEach(clearInterval);
|
|
this._intervals = [];
|
|
this.listeners = {};
|
|
}
|
|
}
|
|
|
|
export { AGENT_DEFS, AGENT_IDS };
|