feat(agent-office): useAgentManager WebSocket hook + useOfficeCanvas rendering hook
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
88
src/pages/agent-office/hooks/useAgentManager.js
Normal file
88
src/pages/agent-office/hooks/useAgentManager.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
|
|
||||||
|
export function useAgentManager() {
|
||||||
|
const [agents, setAgents] = useState({});
|
||||||
|
const [pendingTasks, setPendingTasks] = useState([]);
|
||||||
|
const [connected, setConnected] = useState(false);
|
||||||
|
const wsRef = useRef(null);
|
||||||
|
const reconnectTimer = useRef(null);
|
||||||
|
|
||||||
|
const connect = useCallback(() => {
|
||||||
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
const wsUrl = `${protocol}//${window.location.host}/api/agent-office/ws`;
|
||||||
|
|
||||||
|
const ws = new WebSocket(wsUrl);
|
||||||
|
wsRef.current = ws;
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
setConnected(true);
|
||||||
|
if (reconnectTimer.current) clearTimeout(reconnectTimer.current);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = () => {
|
||||||
|
setConnected(false);
|
||||||
|
reconnectTimer.current = setTimeout(connect, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = () => { ws.close(); };
|
||||||
|
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
const msg = JSON.parse(event.data);
|
||||||
|
|
||||||
|
switch (msg.type) {
|
||||||
|
case 'init': {
|
||||||
|
const agentMap = {};
|
||||||
|
for (const a of msg.agents) {
|
||||||
|
agentMap[a.agent_id] = { state: a.state, detail: a.detail };
|
||||||
|
}
|
||||||
|
setAgents(agentMap);
|
||||||
|
setPendingTasks(msg.pending || []);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'agent_state':
|
||||||
|
setAgents(prev => ({
|
||||||
|
...prev,
|
||||||
|
[msg.agent]: { state: msg.state, detail: msg.detail, taskId: msg.task_id },
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
case 'task_complete':
|
||||||
|
setAgents(prev => ({
|
||||||
|
...prev,
|
||||||
|
[msg.agent]: { ...prev[msg.agent], lastResult: msg.result },
|
||||||
|
}));
|
||||||
|
setPendingTasks(prev => prev.filter(id => id !== msg.task_id));
|
||||||
|
break;
|
||||||
|
case 'command_result':
|
||||||
|
setAgents(prev => ({
|
||||||
|
...prev,
|
||||||
|
[msg.agent]: { ...prev[msg.agent], lastCommand: msg.result },
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
connect();
|
||||||
|
return () => {
|
||||||
|
if (wsRef.current) wsRef.current.close();
|
||||||
|
if (reconnectTimer.current) clearTimeout(reconnectTimer.current);
|
||||||
|
};
|
||||||
|
}, [connect]);
|
||||||
|
|
||||||
|
const sendCommand = useCallback((agent, action, params = {}) => {
|
||||||
|
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||||
|
wsRef.current.send(JSON.stringify({ type: 'command', agent, action, params }));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const sendApproval = useCallback((agent, taskId, approved) => {
|
||||||
|
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||||
|
wsRef.current.send(JSON.stringify({ type: 'approval', agent, task_id: taskId, approved }));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { agents, pendingTasks, connected, sendCommand, sendApproval };
|
||||||
|
}
|
||||||
62
src/pages/agent-office/hooks/useOfficeCanvas.js
Normal file
62
src/pages/agent-office/hooks/useOfficeCanvas.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { useRef, useEffect, useCallback } from 'react';
|
||||||
|
import { OfficeRenderer } from '../canvas/OfficeRenderer';
|
||||||
|
import officeMap from '../assets/office-map.json';
|
||||||
|
|
||||||
|
export function useOfficeCanvas(containerRef, onAgentClick) {
|
||||||
|
const rendererRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.style.display = 'block';
|
||||||
|
canvas.style.width = '100%';
|
||||||
|
canvas.style.height = '100%';
|
||||||
|
canvas.style.imageRendering = 'pixelated';
|
||||||
|
containerRef.current.appendChild(canvas);
|
||||||
|
|
||||||
|
const renderer = new OfficeRenderer(canvas, officeMap);
|
||||||
|
rendererRef.current = renderer;
|
||||||
|
|
||||||
|
const resize = () => {
|
||||||
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
|
renderer.resize(rect.width, rect.height);
|
||||||
|
};
|
||||||
|
|
||||||
|
resize();
|
||||||
|
renderer.start();
|
||||||
|
|
||||||
|
renderer.setOnClick((agentId) => {
|
||||||
|
if (onAgentClick) onAgentClick(agentId);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClick = (e) => {
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
const x = e.clientX - rect.left;
|
||||||
|
const y = e.clientY - rect.top;
|
||||||
|
renderer.handleClick(x, y);
|
||||||
|
};
|
||||||
|
|
||||||
|
canvas.addEventListener('click', handleClick);
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
renderer.stop();
|
||||||
|
canvas.removeEventListener('click', handleClick);
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
if (containerRef.current && canvas.parentNode === containerRef.current) {
|
||||||
|
containerRef.current.removeChild(canvas);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [containerRef, onAgentClick]);
|
||||||
|
|
||||||
|
const updateAgentState = useCallback((agentId, state, detail) => {
|
||||||
|
rendererRef.current?.updateAgentState(agentId, state, detail);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const moveAgent = useCallback((agentId, target) => {
|
||||||
|
rendererRef.current?.moveAgent(agentId, target);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { updateAgentState, moveAgent };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user