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;