diff --git a/src/pages/agent-office/hooks/useAgentManager.js b/src/pages/agent-office/hooks/useAgentManager.js new file mode 100644 index 0000000..b7c8d5e --- /dev/null +++ b/src/pages/agent-office/hooks/useAgentManager.js @@ -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 }; +} diff --git a/src/pages/agent-office/hooks/useOfficeCanvas.js b/src/pages/agent-office/hooks/useOfficeCanvas.js new file mode 100644 index 0000000..f276be9 --- /dev/null +++ b/src/pages/agent-office/hooks/useOfficeCanvas.js @@ -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 }; +}