From 226e368347530c9d6b8774032502ea68a4ed1e55 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sat, 11 Apr 2026 08:59:19 +0900 Subject: [PATCH] feat(agent-office): ChatPanel with commands/approval + TaskHistory panel Co-Authored-By: Claude Opus 4.6 --- .../agent-office/components/ChatPanel.jsx | 109 ++++++++++++++++++ .../agent-office/components/TaskHistory.jsx | 61 ++++++++++ 2 files changed, 170 insertions(+) create mode 100644 src/pages/agent-office/components/ChatPanel.jsx create mode 100644 src/pages/agent-office/components/TaskHistory.jsx diff --git a/src/pages/agent-office/components/ChatPanel.jsx b/src/pages/agent-office/components/ChatPanel.jsx new file mode 100644 index 0000000..6555d33 --- /dev/null +++ b/src/pages/agent-office/components/ChatPanel.jsx @@ -0,0 +1,109 @@ +import React, { useState } from 'react'; + +const AGENT_COMMANDS = { + stock: [ + { action: 'fetch_news', label: 'λ‰΄μŠ€ μˆ˜μ§‘', icon: 'πŸ“°' }, + { action: 'list_alerts', label: 'μ•ŒλžŒ λͺ©λ‘', icon: 'πŸ””' }, + ], + music: [ + { action: 'compose', label: 'μž‘κ³‘ μ‹œμž‘', icon: '🎡', needsInput: true }, + { action: 'credits', label: 'ν¬λ ˆλ”§ 확인', icon: 'πŸ’³' }, + ], + claude: [ + { action: 'instruct', label: 'μ§€μ‹œν•˜κΈ°', icon: 'πŸ’¬', needsInput: true }, + ], +}; + +const ChatPanel = ({ agentId, agentState, onCommand, onApproval, onClose }) => { + const [input, setInput] = useState(''); + const [activeCommand, setActiveCommand] = useState(null); + + const commands = AGENT_COMMANDS[agentId] || []; + const state = agentState || {}; + + const handleSend = () => { + if (!input.trim() || !activeCommand) return; + const params = activeCommand === 'compose' + ? { prompt: input } + : { message: input }; + onCommand(agentId, activeCommand, params); + setInput(''); + setActiveCommand(null); + }; + + const handleQuickAction = (cmd) => { + if (cmd.needsInput) { + setActiveCommand(cmd.action); + } else { + onCommand(agentId, cmd.action, {}); + } + }; + + return ( +
+
+ + {agentId === 'stock' ? '주식 νŠΈλ ˆμ΄λ”' : + agentId === 'music' ? 'μŒμ•… ν”„λ‘œλ“€μ„œ' : 'Claude AI'} + + + {state.state || 'idle'} + + +
+ + {state.detail && ( +
{state.detail}
+ )} + + {state.state === 'waiting' && state.taskId && ( +
+

승인 λŒ€κΈ° 쀑인 μž‘μ—…μ΄ μžˆμŠ΅λ‹ˆλ‹€

+
+ + +
+
+ )} + +
+ {commands.map(cmd => ( + + ))} +
+ + {activeCommand && ( +
+ setInput(e.target.value)} + onKeyDown={e => e.key === 'Enter' && handleSend()} + placeholder={activeCommand === 'compose' ? 'ν”„λ‘¬ν”„νŠΈ μž…λ ₯...' : 'λ©”μ‹œμ§€ μž…λ ₯...'} + autoFocus + /> + +
+ )} + + {state.lastResult && ( +
+

졜근 결과

+
{JSON.stringify(state.lastResult, null, 2)}
+
+ )} +
+ ); +}; + +export default ChatPanel; diff --git a/src/pages/agent-office/components/TaskHistory.jsx b/src/pages/agent-office/components/TaskHistory.jsx new file mode 100644 index 0000000..7e8a7aa --- /dev/null +++ b/src/pages/agent-office/components/TaskHistory.jsx @@ -0,0 +1,61 @@ +import React, { useState, useEffect } from 'react'; +import { getAgentTasks } from '../../../api'; + +const STATUS_BADGE = { + pending: { label: 'λŒ€κΈ°', color: '#fbbf24' }, + approved: { label: '승인됨', color: '#60a5fa' }, + working: { label: '진행쀑', color: '#818cf8' }, + succeeded: { label: 'μ™„λ£Œ', color: '#34d399' }, + failed: { label: 'μ‹€νŒ¨', color: '#f87171' }, +}; + +const TaskHistory = ({ agentId, onClose }) => { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (!agentId) return; + setLoading(true); + getAgentTasks(agentId, 30) + .then(data => setTasks(data.tasks || [])) + .catch(() => setTasks([])) + .finally(() => setLoading(false)); + }, [agentId]); + + return ( +
+
+ μž‘μ—… 이λ ₯ β€” {agentId} + +
+
+ {loading &&

λ‘œλ”© 쀑...

} + {!loading && tasks.length === 0 &&

이λ ₯ μ—†μŒ

} + {tasks.map(task => { + const badge = STATUS_BADGE[task.status] || STATUS_BADGE.pending; + return ( +
+
+ {task.task_type} + + {badge.label} + +
+
+ {task.created_at?.replace('T', ' ').slice(0, 19)} +
+ {task.result_data && ( +
+ 결과 보기 +
{JSON.stringify(task.result_data, null, 2)}
+
+ )} +
+ ); + })} +
+
+ ); +}; + +export default TaskHistory;