feat(agent-office): notification badges + CEO desk document panel + telegram test

- Add notification state management with badge counts in useAgentManager
- Render exclamation badge on agent sprites (separate from status icons)
- Add CEO desk document icon with click-to-open activity panel
- Create DocumentPanel with unified activity feed + per-agent detail tabs
- Add telegram test button to stock agent ChatPanel
- Remove TaskHistory + bottom toolbar (replaced by DocumentPanel)
- Add getActivityFeed API helper

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-11 15:19:14 +09:00
parent 25715a2198
commit deb285695a
11 changed files with 459 additions and 96 deletions

View File

@@ -2,21 +2,26 @@ 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 DocumentPanel from './components/DocumentPanel';
import './AgentOffice.css';
export function Component() {
const canvasContainerRef = useRef(null);
const [selectedAgent, setSelectedAgent] = useState(null);
const [showHistory, setShowHistory] = useState(null);
const [showDocument, setShowDocument] = useState(false);
const { agents, pendingTasks, connected, sendCommand, sendApproval } = useAgentManager();
const { agents, pendingTasks, connected, notifications, sendCommand, sendApproval, clearNotifications } = useAgentManager();
const handleAgentClick = useCallback((agentId) => {
setSelectedAgent(prev => prev === agentId ? null : agentId);
clearNotifications(agentId);
}, [clearNotifications]);
const handleCeoClick = useCallback(() => {
setShowDocument(prev => !prev);
}, []);
const { updateAgentState, moveAgent } = useOfficeCanvas(canvasContainerRef, handleAgentClick);
const { updateAgentState, moveAgent, setAgentNotification, setCeoDocBadge } = useOfficeCanvas(canvasContainerRef, handleAgentClick, handleCeoClick);
useEffect(() => {
for (const [id, info] of Object.entries(agents)) {
@@ -24,6 +29,20 @@ export function Component() {
}
}, [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]);
useEffect(() => {
const total = Object.values(notifications).reduce((s, n) => s + n, 0);
setCeoDocBadge(total);
}, [notifications, setCeoDocBadge]);
return (
<div className="ao-page">
<div className="ao-header">
@@ -46,7 +65,9 @@ export function Component() {
>
<span className={`ao-chip-dot ao-chip-dot--${info.state}`} />
{id}
{info.state === 'waiting' && <span className="ao-chip-badge">!</span>}
{notifications[id] > 0 && (
<span className="ao-chip-badge">{notifications[id]}</span>
)}
</button>
))}
{pendingTasks.length > 0 && (
@@ -64,22 +85,10 @@ export function Component() {
/>
)}
{showHistory && (
<TaskHistory
agentId={showHistory}
onClose={() => setShowHistory(null)}
/>
{showDocument && (
<DocumentPanel onClose={() => setShowDocument(false)} />
)}
</div>
<div className="ao-toolbar">
{Object.keys(agents).map(id => (
<button key={id} className="ao-tool-btn"
onClick={() => setShowHistory(prev => prev === id ? null : id)}>
📋 {id} 이력
</button>
))}
</div>
</div>
);
}