feat(agent-office): Blog + Realestate 에이전트 UI 추가

- AGENT_META/IDS에 blog/realestate 추가 (4 컬럼 대시보드)
- SpriteSheet: 블로그(노트북 액센트)/청약(서류가방 액센트) 픽셀 캐릭터
- office-map: 사무실 책상 4개로 확장, blog_desk/realestate_desk waypoint 추가
- AgentColumn/ChatPanel: 에이전트별 퀵 명령 버튼 (키워드 리서치, 매칭 리포트 등)
- CommandColumn: 타겟 선택지 4명, 빠른 명령 6개, 아이콘 맵핑
- DocumentPanel: 에이전트별 탭 4개

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 03:06:19 +09:00
parent cfc45fc43f
commit 6728b2269e
8 changed files with 78 additions and 17 deletions

View File

@@ -20,6 +20,14 @@ const AGENT_COMMANDS = {
{ action: 'compose', label: '작곡 시작', icon: '🎵', needsInput: true },
{ action: 'credits', label: '크레딧', icon: '💳' },
],
blog: [
{ action: 'research', label: '키워드 리서치', icon: '🔍', needsInput: true },
{ action: 'list_trend_keywords', label: '트렌드 목록', icon: '📋' },
],
realestate: [
{ action: 'fetch_matches', label: '매칭 리포트', icon: '🏢' },
{ action: 'dashboard', label: '대시보드', icon: '📊' },
],
};
const AgentColumn = ({ agentId, meta, agentState, notification, onCommand, onApproval, onClearNotification }) => {
@@ -73,7 +81,10 @@ const AgentColumn = ({ agentId, meta, agentState, notification, onCommand, onApp
const handleSend = () => {
if (!input.trim() || !activeCommand) return;
onCommand(agentId, activeCommand, activeCommand === 'compose' ? { prompt: input } : { message: input });
const params = activeCommand === 'compose' ? { prompt: input }
: activeCommand === 'research' ? { keyword: input }
: { message: input };
onCommand(agentId, activeCommand, params);
setInput('');
setActiveCommand(null);
};

View File

@@ -10,6 +10,21 @@ const AGENT_COMMANDS = {
{ action: 'compose', label: '작곡 시작', icon: '🎵', needsInput: true },
{ action: 'credits', label: '크레딧 확인', icon: '💳' },
],
blog: [
{ action: 'research', label: '키워드 리서치', icon: '🔍', needsInput: true },
{ action: 'list_trend_keywords', label: '트렌드 목록', icon: '📋' },
],
realestate: [
{ action: 'fetch_matches', label: '매칭 리포트', icon: '🏢' },
{ action: 'dashboard', label: '대시보드', icon: '📊' },
],
};
const AGENT_NAMES = {
stock: '주식 트레이더',
music: '음악 프로듀서',
blog: '블로그 마케터',
realestate: '청약 애널리스트',
};
const ChatPanel = ({ agentId, agentState, onCommand, onApproval, onClose }) => {
@@ -21,8 +36,8 @@ const ChatPanel = ({ agentId, agentState, onCommand, onApproval, onClose }) => {
const handleSend = () => {
if (!input.trim() || !activeCommand) return;
const params = activeCommand === 'compose'
? { prompt: input }
const params = activeCommand === 'compose' ? { prompt: input }
: activeCommand === 'research' ? { keyword: input }
: { message: input };
onCommand(agentId, activeCommand, params);
setInput('');
@@ -41,8 +56,7 @@ const ChatPanel = ({ agentId, agentState, onCommand, onApproval, onClose }) => {
<div className="ao-chat-panel">
<div className="ao-chat-header">
<span className="ao-chat-title">
{agentId === 'stock' ? '주식 트레이더' :
agentId === 'music' ? '음악 프로듀서' : agentId}
{AGENT_NAMES[agentId] || agentId}
</span>
<span className={`ao-chat-state ao-chat-state--${state.state || 'idle'}`}>
{state.state || 'idle'}
@@ -87,7 +101,11 @@ const ChatPanel = ({ agentId, agentState, onCommand, onApproval, onClose }) => {
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleSend()}
placeholder={activeCommand === 'compose' ? '프롬프트 입력...' : '메시지 입력...'}
placeholder={
activeCommand === 'compose' ? '프롬프트 입력...'
: activeCommand === 'research' ? '키워드 입력...'
: '메시지 입력...'
}
autoFocus
/>
<button className="ao-btn ao-btn--send" onClick={handleSend}>전송</button>

View File

@@ -3,12 +3,24 @@ import React, { useState } from 'react';
const TARGETS = [
{ id: 'stock', name: '주식 트레이더' },
{ id: 'music', name: '음악 프로듀서' },
{ id: 'blog', name: '블로그 마케터' },
{ id: 'realestate', name: '청약 애널리스트' },
];
const TARGET_ICONS = {
stock: '📈',
music: '🎵',
blog: '✍️',
realestate: '🏢',
};
const QUICK_COMMANDS = [
{ target: 'stock', action: 'fetch_news', label: '뉴스 수집' },
{ target: 'stock', action: 'test_telegram', label: 'TG 테스트' },
{ target: 'music', action: 'credits', label: '크레딧 확인' },
{ target: 'blog', action: 'list_trend_keywords', label: '트렌드 목록' },
{ target: 'realestate', action: 'fetch_matches', label: '매칭 리포트' },
{ target: 'realestate', action: 'dashboard', label: '청약 대시보드' },
];
const CommandColumn = ({ agents, onCommand }) => {
@@ -79,7 +91,7 @@ const CommandColumn = ({ agents, onCommand }) => {
<div className="ao-col-commands">
{QUICK_COMMANDS.map((cmd, i) => (
<button key={i} className="ao-cmd-btn" onClick={() => handleQuick(cmd)}>
{cmd.target === 'stock' ? '📈' : '🎵'} {cmd.label}
{TARGET_ICONS[cmd.target] || '🤖'} {cmd.label}
</button>
))}
</div>

View File

@@ -110,11 +110,16 @@ const DocumentPanel = ({ onClose }) => {
{tab === 'detail' && (
<div className="ao-doc-detail">
<div className="ao-doc-agent-select">
{['stock', 'music'].map(id => (
<button key={id}
className={`ao-doc-tab ${selectedAgent === id ? 'ao-doc-tab--active' : ''}`}
onClick={() => setSelectedAgent(id)}
>{id === 'stock' ? '주식 트레이더' : '음악 프로듀서'}</button>
{[
{ id: 'stock', name: '주식 트레이더' },
{ id: 'music', name: '음악 프로듀서' },
{ id: 'blog', name: '블로그 마케터' },
{ id: 'realestate', name: '청약 애널리스트' },
].map(a => (
<button key={a.id}
className={`ao-doc-tab ${selectedAgent === a.id ? 'ao-doc-tab--active' : ''}`}
onClick={() => setSelectedAgent(a.id)}
>{a.name}</button>
))}
</div>
<div className="ao-doc-detail-tabs">