Files
codex-office/src/components/SidePanel.tsx
T

122 lines
4.2 KiB
TypeScript
Raw Normal View History

import { STATUS_COLORS, STATUS_LABELS } from "@/lib/constants";
import { generateAppearance } from "@/lib/appearance";
import { useOfficeStore } from "@/store/office-store";
import { getDirector } from "@/sim/runtime";
import { Pawn } from "./Pawn";
const ROLE_LABELS = { lead: "Lead Agent", agent: "Agent", subagent: "Subagent" } as const;
export function SidePanel() {
const selectedAgentId = useOfficeStore((s) => s.selectedAgentId);
const agent = useOfficeStore((s) => (s.selectedAgentId ? s.agents.get(s.selectedAgentId) : undefined));
const agents = useOfficeStore((s) => s.agents);
const events = useOfficeStore((s) => s.events);
const selectAgent = useOfficeStore((s) => s.selectAgent);
const mode = useOfficeStore((s) => s.mode);
return (
<aside className="side-panel">
{selectedAgentId && agent ? (
<div className="agent-card">
<div className="agent-card-head">
<svg viewBox="-30 -42 60 68" width="64" height="72">
<Pawn appearance={generateAppearance(agent.id)} pose="stand" motion="none" />
</svg>
<div>
<h2>
{agent.role === "lead" && <span className="mark"> </span>}
{agent.name}
</h2>
<span className="role-badge">{ROLE_LABELS[agent.role]}</span>
<code className="model-tag">{agent.model}</code>
</div>
<button className="close-btn" onClick={() => selectAgent(null)}>
</button>
</div>
<div className="agent-stat-row">
<span className="status-chip" style={{ backgroundColor: `${STATUS_COLORS[agent.status]}22`, color: STATUS_COLORS[agent.status] }}>
{STATUS_LABELS[agent.status]}
</span>
{agent.currentTool && <code className="tool-now">{agent.currentTool.name}()</code>}
</div>
<dl className="agent-facts">
<div>
<dt>工具調用</dt>
<dd>{agent.toolCallCount} </dd>
</div>
<div>
<dt>所在區域</dt>
<dd>{agent.zone}</dd>
</div>
{agent.parentId && (
<div>
<dt>上級</dt>
<dd>
<button className="link-btn" onClick={() => selectAgent(agent.parentId)}>
{agents.get(agent.parentId)?.name ?? agent.parentId}
</button>
</dd>
</div>
)}
{agent.childIds.length > 0 && (
<div>
<dt>Subagents</dt>
<dd>
{agent.childIds.map((cid) => (
<button key={cid} className="link-btn" onClick={() => selectAgent(cid)}>
{agents.get(cid)?.name ?? cid}
</button>
))}
</dd>
</div>
)}
</dl>
{agent.toolHistory.length > 0 && (
<div className="tool-history">
<h3>最近工具</h3>
<div className="tool-chips">
{agent.toolHistory.map((t, i) => (
<code key={i}>{t}</code>
))}
</div>
</div>
)}
{agent.role !== "subagent" && mode === "sim" && (
<button
className="btn assign-btn"
onClick={() => {
if (!getDirector().assignTask(agent.id)) {
useOfficeStore.getState().addEvent("💬", `${agent.name} 正忙,稍後再指派`);
}
}}
>
📋 指派任務
</button>
)}
</div>
) : (
<div className="panel-hint">
<p>點擊辦公室裡的 agent 查看詳情</p>
</div>
)}
<div className="event-feed">
<h3>事件紀錄</h3>
<ul>
{events.map((e) => (
<li key={e.at}>
<span className="event-icon">{e.icon}</span>
{e.text}
</li>
))}
</ul>
</div>
</aside>
);
}