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