- Pathfinder.setBlocked: remove blocked.clear() to preserve wall tiles set by setWalls() - Pathfinder.findPath: fix dead-code goal exception — remove redundant isBlocked check, keep goal-tile exception in single guard - OfficeRenderer: track mouseDownPos/_wasDragging; expose wasDragging() method for click-after-drag suppression - OfficeRenderer._render: track _lastDpr to detect monitor DPR changes; use setTransform instead of scale to avoid accumulation - TileMap.render: use clientWidth/clientHeight for viewport culling (CSS space, not buffer pixels) - TaskTab: wrap JSON.parse in try/catch to prevent crash on malformed result_data Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
102 lines
2.9 KiB
JavaScript
102 lines
2.9 KiB
JavaScript
// src/pages/agent-office/AgentOffice.jsx
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
import { useAgentManager } from './hooks/useAgentManager.js';
|
|
import { useOfficeCanvas } from './hooks/useOfficeCanvas.js';
|
|
import TopBar from './components/TopBar.jsx';
|
|
import SidePanel from './components/SidePanel.jsx';
|
|
import './AgentOffice.css';
|
|
|
|
export default function AgentOffice() {
|
|
const {
|
|
agents, pendingTasks, notifications, connected,
|
|
refreshTrigger, clearNotifications
|
|
} = useAgentManager();
|
|
|
|
const {
|
|
canvasRef, updateAgentState, setAgentNotification,
|
|
setTheme, setZoom, hitTest, getZoom, wasDragging
|
|
} = useOfficeCanvas();
|
|
|
|
const [selectedAgent, setSelectedAgent] = useState(null);
|
|
const [theme, setThemeState] = useState(localStorage.getItem('agent-office-theme') || 'modern');
|
|
const [zoom, setZoomState] = useState(2);
|
|
|
|
// WebSocket 상태 → 캔버스 동기화
|
|
useEffect(() => {
|
|
for (const [id, agentState] of Object.entries(agents)) {
|
|
updateAgentState(id, agentState.state, agentState.detail);
|
|
}
|
|
}, [agents, updateAgentState]);
|
|
|
|
// 알림 → 캔버스 동기화
|
|
useEffect(() => {
|
|
for (const [id, count] of Object.entries(notifications)) {
|
|
setAgentNotification(id, count);
|
|
}
|
|
}, [notifications, setAgentNotification]);
|
|
|
|
// 캔버스 클릭 핸들러
|
|
const handleCanvasClick = useCallback((e) => {
|
|
if (wasDragging()) return; // 드래그 후 발생하는 클릭 무시
|
|
const result = hitTest(e.clientX, e.clientY);
|
|
if (result.type === 'agent') {
|
|
setSelectedAgent(result.id);
|
|
clearNotifications(result.id);
|
|
setAgentNotification(result.id, 0);
|
|
} else {
|
|
setSelectedAgent(null);
|
|
}
|
|
}, [hitTest, clearNotifications, setAgentNotification, wasDragging]);
|
|
|
|
// 테마 변경
|
|
const handleThemeChange = useCallback((name) => {
|
|
setThemeState(name);
|
|
setTheme(name);
|
|
}, [setTheme]);
|
|
|
|
// 줌 변경
|
|
const handleZoomChange = useCallback((level) => {
|
|
setZoomState(level);
|
|
setZoom(level);
|
|
}, [setZoom]);
|
|
|
|
// 선택된 에이전트의 pending task
|
|
const pendingTask = selectedAgent
|
|
? pendingTasks.find(t => t.agent_id === selectedAgent)
|
|
: null;
|
|
|
|
return (
|
|
<div className="ao-root">
|
|
<TopBar
|
|
connected={connected}
|
|
theme={theme}
|
|
onThemeChange={handleThemeChange}
|
|
zoom={zoom}
|
|
onZoomChange={handleZoomChange}
|
|
/>
|
|
|
|
<div className="ao-main">
|
|
<canvas
|
|
ref={canvasRef}
|
|
className="ao-canvas"
|
|
onClick={handleCanvasClick}
|
|
/>
|
|
|
|
{selectedAgent && (
|
|
<SidePanel
|
|
agentId={selectedAgent}
|
|
agentState={agents[selectedAgent]}
|
|
pendingTask={pendingTask}
|
|
onClose={() => setSelectedAgent(null)}
|
|
refreshTrigger={refreshTrigger}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function Component() {
|
|
return <AgentOffice />;
|
|
}
|