console: 按 Muse 网页端/桌面端视觉重做

仅视觉与布局改动,功能与 API 未改:全屏四栏(56px 图标栏 +
260px 列表 + 弹性对话 + 300px 右侧栏),左侧搜索 + 主要聊天
列表,中间灰/浅蓝气泡对话 + 圆角输入条,右侧头像 + 已连接
(真实 key/健康状态)+ 今天任务清单,下方仍可展开原详情。
旧 v3 松绿/朱印与顶部大标题栏已移除,产物为单文件
(316226 字节,sha f8fea987…,本地重建字节一致)。
console 标记单测未动,本地 53 项通过。
This commit is contained in:
host committed 2026-10-29 06:52:34 +08:00
1 parent 5ca5cc91cc
commit 66790b5123
12 files changed
+394 -227

No files matched your search

@@ -0,0 +1,86 @@
import { useConsole } from "../context";
import { ModuleIcon, type View } from "../components/IconRail";
import { shortId } from "../api/client";
import TaskDetailPanel from "./TaskDetailPanel";
import MembersPanel from "./MembersPanel";
import EnvContextPanel from "./EnvContextPanel";
import SkillsToolsPanel from "./SkillsToolsPanel";
import HealthPanel from "./HealthPanel";
import AutomationsSection from "./AutomationsSection";
import TriageSection from "./TriageSection";
// Right panel, modelled on the Muse screenshot: profile header with a
// real connection status (API key / health, never a fake user), an icon
// row that switches modules, a 今天 list of recent tasks, and below it
// the selected task's detail (or the current module's context) as a
// collapsible section. Nothing that was in the old right column is
// dropped: the detail/context panels are the same components, only
// rendered inside <details> under the today list.
export default function RightPanel({ view, onSelect, onClose }: {
view: View; onSelect: (v: View) => void; onClose: () => void;
}) {
const ctx = useConsole();
const connected = ctx.keySaved;
const today = ctx.tasks.slice().reverse().slice(0, 10);
const context = (() => {
switch (view) {
case "projects": return <MembersPanel />;
case "env": return <EnvContextPanel />;
case "skills": return <SkillsToolsPanel />;
case "workers": case "admin": case "key": return <HealthPanel />;
case "automations": return <TriageSection />;
case "triage": return <AutomationsSection />;
default: return <TaskDetailPanel />;
}
})();
const iconRow: Array<[View, string]> = [["task", "任务"], ["admin", "管理区"], ["automations", "自动化"], ["key", "API Key"]];
return (
<div>
<div className="right-profile">
<button type="button" className="icon-btn" aria-label="关闭右侧栏" onClick={onClose}>×</button>
<div className="right-avatar" aria-hidden="true">C</div>
<div className="right-name">Codex 命令台</div>
<div className={connected ? "right-status" : "right-status off"}>
{connected ? "● 已连接" : "○ 未连接(先在 API Key 里保存 key)"}
</div>
<div className="muted">{ctx.healthText}</div>
<div className="muted">{ctx.quotaText}</div>
<div className="muted">自动刷新 · 每 10 秒 · 已刷新 {ctx.refreshStamp}</div>
</div>
<div className="right-icon-row" aria-label="快捷方式">
{iconRow.map(([v, label]) => (
<button key={v} type="button" title={label} aria-label={label} className={view === v ? "active" : ""} onClick={() => onSelect(v)}>
<ModuleIcon view={v} />
</button>
))}
</div>
<div>
<div className="list-section-head"><span>今天</span></div>
{today.length === 0 ? (
<div className="muted">今天暂无任务</div>
) : (
<ul className="today-list">
{today.map((t) => {
const done = t.status === "completed" || t.status === "merged";
const time = t.updated_at ? new Date(t.updated_at * 1000).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "";
return (
<li key={t.id} className="today-item" onClick={() => { ctx.showDetail(t.id); onSelect("task"); }}>
<span className={"today-check" + (done ? " done" : "")}>{done ? "✓" : ""}</span>
<span className="today-item-main">
<span className="today-item-title">{String(t.prompt || shortId(t.id)).slice(0, 60) || shortId(t.id)}</span>
<span className="today-item-sub">{shortId(t.id)} · <span className={"st-" + t.status}>{t.status}</span> · {t.executor || ""}</span>
</span>
<span className="today-item-time">{time}</span>
</li>
);
})}
</ul>
)}
</div>
<details className="right-detail" open>
<summary>{view === "task" ? "任务详情" : "模块详情"}</summary>
{context}
</details>
</div>
);
}