From 403046c4d0524ecaca84232d57645a3200b8ed9c Mon Sep 17 00:00:00 2001 From: gahusb Date: Mon, 27 Apr 2026 08:38:11 +0900 Subject: [PATCH] refactor(agent-office): rewrite AgentOffice with full-screen canvas and side panel Co-Authored-By: Claude Sonnet 4.6 --- src/pages/agent-office/AgentOffice.jsx | 162 ++++++++++++------------- 1 file changed, 76 insertions(+), 86 deletions(-) diff --git a/src/pages/agent-office/AgentOffice.jsx b/src/pages/agent-office/AgentOffice.jsx index d803b3c..90c9643 100644 --- a/src/pages/agent-office/AgentOffice.jsx +++ b/src/pages/agent-office/AgentOffice.jsx @@ -1,110 +1,100 @@ -import React, { useRef, useState, useCallback, useEffect } from 'react'; -import { useAgentManager } from './hooks/useAgentManager'; -import { useOfficeCanvas } from './hooks/useOfficeCanvas'; -import AgentColumn from './components/AgentColumn'; -import CommandColumn from './components/CommandColumn'; -import { useIsMobile } from '../../hooks/useIsMobile'; -import MobileSheet from '../../components/MobileSheet'; +// 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'; -const AGENT_META = { - stock: { name: '주식 트레이더', color: '#4488cc' }, - music: { name: '음악 프로듀서', color: '#44aa88' }, - blog: { name: '블로그 마케터', color: '#d97706' }, - realestate: { name: '청약 애널리스트', color: '#c026d3' }, -}; +export default function AgentOffice() { + const { + agents, pendingTasks, notifications, connected, + refreshTrigger, clearNotifications + } = useAgentManager(); -const AGENT_IDS = ['stock', 'music', 'blog', 'realestate']; + const { + canvasRef, updateAgentState, setAgentNotification, + setTheme, setZoom, hitTest, getZoom + } = useOfficeCanvas(); -export function Component() { - const canvasContainerRef = useRef(null); - const isMobile = useIsMobile(); - const [agentDetailSheet, setAgentDetailSheet] = useState(null); // agentId or null - - const { agents, pendingTasks, connected, notifications, sendCommand, sendApproval, clearNotifications } = useAgentManager(); - - const handleAgentClick = useCallback((agentId) => { - clearNotifications(agentId); - if (isMobile) { - setAgentDetailSheet(agentId); - } - }, [clearNotifications, isMobile]); - - const handleCeoClick = useCallback(() => {}, []); - - const { updateAgentState, setAgentNotification, setCeoDocBadge } = useOfficeCanvas(canvasContainerRef, handleAgentClick, handleCeoClick); + 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, info] of Object.entries(agents)) { - updateAgentState(id, info.state, info.detail); + 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); } - for (const id of Object.keys(agents)) { - if (!notifications[id]) setAgentNotification(id, 0); - } - }, [notifications, agents, setAgentNotification]); + }, [notifications, setAgentNotification]); - useEffect(() => { - const total = Object.values(notifications).reduce((s, n) => s + n, 0); - setCeoDocBadge(total); - }, [notifications, setCeoDocBadge]); + // 캔버스 클릭 핸들러 + const handleCanvasClick = useCallback((e) => { + 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]); + + // 테마 변경 + 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 ( -
-
-

Agent Office

-
- - {connected ? 'Connected' : 'Disconnected'} -
-
+
+ -
- {AGENT_IDS.map(id => ( - clearNotifications(id)} - /> - ))} - + -
-
-
-
- - {/* 모바일: 에이전트 상세 바텀시트 */} - setAgentDetailSheet(null)} - title={agentDetailSheet ? (AGENT_META[agentDetailSheet]?.name ?? agentDetailSheet) : ''} - > - {agentDetailSheet && ( - clearNotifications(agentDetailSheet)} + {selectedAgent && ( + setSelectedAgent(null)} + refreshTrigger={refreshTrigger} /> )} - +
); } + +export function Component() { + return ; +}