diff --git a/src/pages/agent-office/AgentOffice.css b/src/pages/agent-office/AgentOffice.css new file mode 100644 index 0000000..fc1b7c9 --- /dev/null +++ b/src/pages/agent-office/AgentOffice.css @@ -0,0 +1,331 @@ +.ao-page { + display: flex; + flex-direction: column; + height: 100vh; + background: #0d0d1a; + color: #e0e0e0; + font-family: 'Courier New', monospace; +} + +.ao-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 20px; + background: #1a1a2e; + border-bottom: 1px solid #2a2a4a; +} + +.ao-title { + font-size: 1.4rem; + color: #8b5cf6; + margin: 0; + letter-spacing: 2px; +} + +.ao-status { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.85rem; + color: #888; +} + +.ao-dot { + width: 8px; + height: 8px; + border-radius: 50%; +} +.ao-dot--on { background: #34d399; } +.ao-dot--off { background: #f87171; } + +.ao-workspace { + flex: 1; + position: relative; + overflow: hidden; +} + +.ao-canvas-container { + width: 100%; + height: 100%; +} + +.ao-agent-bar { + position: absolute; + top: 12px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 8px; + padding: 6px 12px; + background: rgba(0, 0, 0, 0.6); + border-radius: 20px; + backdrop-filter: blur(8px); +} + +.ao-agent-chip { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 12px; + border: 1px solid #333; + border-radius: 12px; + background: transparent; + color: #ccc; + font-size: 0.8rem; + cursor: pointer; + font-family: inherit; +} +.ao-agent-chip:hover { border-color: #8b5cf6; } +.ao-agent-chip--selected { border-color: #8b5cf6; background: rgba(139, 92, 246, 0.15); } +.ao-agent-chip--alert { animation: ao-pulse 1s infinite; } + +@keyframes ao-pulse { + 0%, 100% { border-color: #fbbf24; } + 50% { border-color: #f59e0b; box-shadow: 0 0 8px rgba(251, 191, 36, 0.4); } +} + +.ao-chip-dot { + width: 6px; + height: 6px; + border-radius: 50%; +} +.ao-chip-dot--idle { background: #666; } +.ao-chip-dot--working { background: #818cf8; } +.ao-chip-dot--waiting { background: #fbbf24; } +.ao-chip-dot--reporting { background: #34d399; } +.ao-chip-dot--break { background: #a78bfa; } + +.ao-chip-badge { + background: #f87171; + color: #fff; + font-size: 0.65rem; + padding: 0 4px; + border-radius: 4px; + font-weight: bold; +} + +.ao-pending-count { + color: #fbbf24; + font-size: 0.75rem; + align-self: center; +} + +.ao-chat-panel { + position: absolute; + right: 16px; + top: 60px; + width: 340px; + max-height: calc(100% - 80px); + background: rgba(26, 26, 46, 0.95); + border: 1px solid #333; + border-radius: 12px; + overflow-y: auto; + backdrop-filter: blur(12px); +} + +.ao-chat-header { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + border-bottom: 1px solid #2a2a4a; +} + +.ao-chat-title { + flex: 1; + font-weight: bold; + color: #e0e0e0; +} + +.ao-chat-state { + font-size: 0.75rem; + padding: 2px 8px; + border-radius: 8px; + text-transform: uppercase; +} +.ao-chat-state--idle { background: #333; } +.ao-chat-state--working { background: #3730a3; } +.ao-chat-state--waiting { background: #92400e; } +.ao-chat-state--break { background: #4c1d95; } + +.ao-chat-close { + background: none; + border: none; + color: #888; + font-size: 1.2rem; + cursor: pointer; +} +.ao-chat-close:hover { color: #fff; } + +.ao-chat-detail { + padding: 8px 16px; + color: #aaa; + font-size: 0.85rem; +} + +.ao-chat-approval { + padding: 12px 16px; + background: rgba(251, 191, 36, 0.1); + border-top: 1px solid #2a2a4a; + border-bottom: 1px solid #2a2a4a; +} +.ao-chat-approval p { + margin: 0 0 8px; + color: #fbbf24; + font-size: 0.85rem; +} +.ao-chat-approval-btns { + display: flex; + gap: 8px; +} + +.ao-btn { + padding: 6px 16px; + border: none; + border-radius: 6px; + font-size: 0.85rem; + cursor: pointer; + font-family: inherit; +} +.ao-btn--approve { background: #065f46; color: #34d399; } +.ao-btn--approve:hover { background: #047857; } +.ao-btn--reject { background: #7f1d1d; color: #f87171; } +.ao-btn--reject:hover { background: #991b1b; } +.ao-btn--send { background: #4c1d95; color: #c4b5fd; } +.ao-btn--send:hover { background: #5b21b6; } + +.ao-chat-commands { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 12px 16px; +} + +.ao-cmd-btn { + padding: 6px 12px; + border: 1px solid #333; + border-radius: 8px; + background: transparent; + color: #ccc; + font-size: 0.8rem; + cursor: pointer; + font-family: inherit; +} +.ao-cmd-btn:hover { border-color: #8b5cf6; background: rgba(139, 92, 246, 0.1); } + +.ao-chat-input-area { + display: flex; + gap: 8px; + padding: 8px 16px 12px; +} +.ao-chat-input { + flex: 1; + padding: 8px 12px; + background: #111; + border: 1px solid #333; + border-radius: 6px; + color: #e0e0e0; + font-size: 0.85rem; + font-family: inherit; +} +.ao-chat-input:focus { border-color: #8b5cf6; outline: none; } + +.ao-chat-result { + padding: 8px 16px; + border-top: 1px solid #2a2a4a; +} +.ao-chat-result h4 { + margin: 0 0 8px; + font-size: 0.8rem; + color: #888; +} +.ao-chat-result pre { + font-size: 0.75rem; + color: #aaa; + overflow-x: auto; + white-space: pre-wrap; + margin: 0; +} + +.ao-history-panel { + position: absolute; + left: 16px; + top: 60px; + width: 340px; + max-height: calc(100% - 80px); + background: rgba(26, 26, 46, 0.95); + border: 1px solid #333; + border-radius: 12px; + overflow-y: auto; + backdrop-filter: blur(12px); +} + +.ao-history-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border-bottom: 1px solid #2a2a4a; + font-weight: bold; +} + +.ao-history-list { padding: 8px; } +.ao-history-empty { text-align: center; color: #666; padding: 20px; } + +.ao-history-item { + padding: 10px 12px; + border-bottom: 1px solid #1a1a2e; +} +.ao-history-item:last-child { border-bottom: none; } + +.ao-history-item-header { + display: flex; + justify-content: space-between; + align-items: center; +} +.ao-history-type { font-size: 0.85rem; color: #ccc; } +.ao-history-badge { + font-size: 0.7rem; + padding: 2px 8px; + border-radius: 4px; + color: #fff; +} +.ao-history-time { + font-size: 0.75rem; + color: #666; + margin-top: 4px; +} +.ao-history-detail { + margin-top: 6px; + font-size: 0.75rem; +} +.ao-history-detail summary { + cursor: pointer; + color: #8b5cf6; +} +.ao-history-detail pre { + color: #aaa; + white-space: pre-wrap; + margin: 4px 0 0; +} + +.ao-toolbar { + display: flex; + gap: 8px; + padding: 8px 20px; + background: #1a1a2e; + border-top: 1px solid #2a2a4a; +} + +.ao-tool-btn { + padding: 6px 14px; + border: 1px solid #333; + border-radius: 6px; + background: transparent; + color: #aaa; + font-size: 0.8rem; + cursor: pointer; + font-family: inherit; +} +.ao-tool-btn:hover { border-color: #8b5cf6; color: #e0e0e0; } diff --git a/src/pages/agent-office/AgentOffice.jsx b/src/pages/agent-office/AgentOffice.jsx new file mode 100644 index 0000000..c915656 --- /dev/null +++ b/src/pages/agent-office/AgentOffice.jsx @@ -0,0 +1,85 @@ +import React, { useRef, useState, useCallback, useEffect } from 'react'; +import { useAgentManager } from './hooks/useAgentManager'; +import { useOfficeCanvas } from './hooks/useOfficeCanvas'; +import ChatPanel from './components/ChatPanel'; +import TaskHistory from './components/TaskHistory'; +import './AgentOffice.css'; + +export function Component() { + const canvasContainerRef = useRef(null); + const [selectedAgent, setSelectedAgent] = useState(null); + const [showHistory, setShowHistory] = useState(null); + + const { agents, pendingTasks, connected, sendCommand, sendApproval } = useAgentManager(); + + const handleAgentClick = useCallback((agentId) => { + setSelectedAgent(prev => prev === agentId ? null : agentId); + }, []); + + const { updateAgentState, moveAgent } = useOfficeCanvas(canvasContainerRef, handleAgentClick); + + useEffect(() => { + for (const [id, info] of Object.entries(agents)) { + updateAgentState(id, info.state, info.detail); + } + }, [agents, updateAgentState]); + + return ( +
+
+

Agent Office

+
+ + {connected ? 'Connected' : 'Disconnected'} +
+
+ +
+
+ +
+ {Object.entries(agents).map(([id, info]) => ( + + ))} + {pendingTasks.length > 0 && ( + {pendingTasks.length} pending + )} +
+ + {selectedAgent && ( + setSelectedAgent(null)} + /> + )} + + {showHistory && ( + setShowHistory(null)} + /> + )} +
+ +
+ {['stock', 'music', 'claude'].map(id => ( + + ))} +
+
+ ); +}