130 lines
2.9 KiB
JavaScript
130 lines
2.9 KiB
JavaScript
import { AGENT_DEFS, colorToCss } from './agent-defs.js';
|
|
import { setAgentState } from './agents.js';
|
|
import { appendChatMessage } from './ui.js';
|
|
|
|
/**
|
|
* WebSocket URL resolution order:
|
|
* 1. VITE_WS_URL env var (set at build time for custom deployments)
|
|
* 2. Auto-derived from window.location — works when the Matrix is served from
|
|
* the same host as the API (the default: /tower served by the API server)
|
|
*/
|
|
function resolveWsUrl() {
|
|
const explicit = import.meta.env.VITE_WS_URL;
|
|
if (explicit) return explicit;
|
|
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
return `${proto}//${window.location.host}/api/ws`;
|
|
}
|
|
|
|
const WS_URL = resolveWsUrl();
|
|
|
|
const agentById = Object.fromEntries(AGENT_DEFS.map(d => [d.id, d]));
|
|
|
|
let ws = null;
|
|
let connectionState = 'disconnected';
|
|
let jobCount = 0;
|
|
let reconnectTimer = null;
|
|
const RECONNECT_DELAY_MS = 5000;
|
|
|
|
export function initWebSocket(_scene) {
|
|
connect();
|
|
}
|
|
|
|
function connect() {
|
|
if (ws) {
|
|
ws.onclose = null;
|
|
ws.close();
|
|
}
|
|
|
|
connectionState = 'connecting';
|
|
|
|
try {
|
|
ws = new WebSocket(WS_URL);
|
|
} catch {
|
|
connectionState = 'disconnected';
|
|
scheduleReconnect();
|
|
return;
|
|
}
|
|
|
|
ws.onopen = () => {
|
|
connectionState = 'connected';
|
|
clearTimeout(reconnectTimer);
|
|
ws.send(JSON.stringify({
|
|
type: 'subscribe',
|
|
channel: 'agents',
|
|
clientId: crypto.randomUUID(),
|
|
}));
|
|
};
|
|
|
|
ws.onmessage = (event) => {
|
|
try {
|
|
handleMessage(JSON.parse(event.data));
|
|
} catch {
|
|
}
|
|
};
|
|
|
|
ws.onerror = () => {
|
|
connectionState = 'disconnected';
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
connectionState = 'disconnected';
|
|
scheduleReconnect();
|
|
};
|
|
}
|
|
|
|
function scheduleReconnect() {
|
|
clearTimeout(reconnectTimer);
|
|
reconnectTimer = setTimeout(connect, RECONNECT_DELAY_MS);
|
|
}
|
|
|
|
function handleMessage(msg) {
|
|
switch (msg.type) {
|
|
case 'ping':
|
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
ws.send(JSON.stringify({ type: 'pong' }));
|
|
}
|
|
break;
|
|
case 'agent_state': {
|
|
if (msg.agentId && msg.state) {
|
|
setAgentState(msg.agentId, msg.state);
|
|
}
|
|
break;
|
|
}
|
|
case 'job_started': {
|
|
jobCount++;
|
|
if (msg.agentId) setAgentState(msg.agentId, 'active');
|
|
logEvent(`JOB ${(msg.jobId || '').slice(0, 8)} ▶ started`);
|
|
break;
|
|
}
|
|
case 'job_completed': {
|
|
if (jobCount > 0) jobCount--;
|
|
if (msg.agentId) setAgentState(msg.agentId, 'idle');
|
|
logEvent(`JOB ${(msg.jobId || '').slice(0, 8)} ✓ complete`);
|
|
break;
|
|
}
|
|
case 'chat': {
|
|
const def = agentById[msg.agentId];
|
|
if (def && msg.text) {
|
|
appendChatMessage(def.label, msg.text, colorToCss(def.color), def.id);
|
|
}
|
|
break;
|
|
}
|
|
case 'agent_count':
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
function logEvent(text) {
|
|
appendChatMessage('SYS', text, '#003300', 'sys');
|
|
}
|
|
|
|
export function getConnectionState() {
|
|
return connectionState;
|
|
}
|
|
|
|
export function getJobCount() {
|
|
return jobCount;
|
|
}
|