console: 一页式控制台排版 + 数据卡片

保持 Muse 风格(白底、细线、灰/浅蓝气泡),不再四栏切视图:
顶栏(连接/健康/配额/刷新 + 锚点导航)+ 6 张数据卡片
(健康、配额、任务、Workers、项目、项目数据,均取自已有
store 数据,未加载不以 0 充数)+ 所有模块按顺序铺在一页
(派发、任务列表、对话+详情、项目、环境、Workers、技能、
自动化、分诊、管理区、API Key)。图标栏与右侧栏删除,
内容分别移到顶栏与各自模块下;今天列表与任务列表重复,
不再单独保留;对话为空时不再重复渲染派发表单。
含自动选第一个项目修复(主要聊天不再空)与继续消息
历史,console 标记单测本地 53 项通过。
This commit is contained in:
host committed 2026-10-29 09:57:26 +08:00
1 parent a13762cfbe
commit 8b455c6554
9 files changed
+263 -272

No files matched your search

+108 -50
View File
@@ -1,72 +1,130 @@
import { useState } from "react";
import { ConsoleContext } from "./context";
import { ConsoleContext, useConsole } from "./context";
import { useConsoleStore } from "./hooks/useConsoleStore";
import { useAutoRefresh, useIntervalMs } from "./hooks/useAutoRefresh";
import IconRail, { type View } from "./components/IconRail";
import DataCards from "./sections/DataCards";
import DispatchSection from "./sections/DispatchSection";
import TaskListSection from "./sections/TaskListSection";
import ConversationSection from "./sections/ConversationSection";
import TaskDetailPanel from "./sections/TaskDetailPanel";
import ApiKeySection from "./sections/ApiKeySection";
import AdminSection from "./sections/AdminSection";
import ProjectsSection from "./sections/ProjectsSection";
import MembersPanel from "./sections/MembersPanel";
import EnvSection from "./sections/EnvSection";
import EnvContextPanel from "./sections/EnvContextPanel";
import WorkersSection from "./sections/WorkersSection";
import HealthPanel from "./sections/HealthPanel";
import SkillsSection from "./sections/SkillsSection";
import SkillsToolsPanel from "./sections/SkillsToolsPanel";
import AutomationsSection from "./sections/AutomationsSection";
import TriageSection from "./sections/TriageSection";
import TaskListSection from "./sections/TaskListSection";
import ConversationSection from "./sections/ConversationSection";
import RightPanel from "./sections/RightPanel";
// Layout only, Muse web/desktop style: no big header bar.
// Icon rail (56px) + left list (260px) + centre conversation (elastic,
// content centred) + right panel (300px, profile/today/detail). The
// centre top bar is minimal: a back arrow (back to the task view) on
// the left, and, only when the right panel is closed, a button to
// reopen it — the right panel's own X closes it. Selecting a task in
// the left list switches the centre conversation and the right detail
// to that task together; selecting a module switches the centre to
// that module and the right detail to its context.
// Layout only: ONE scrollable console page, Muse web/desktop tokens
// (white, hairlines, grey/blue bubbles) but console ordering — no
// icon-rail column, no left list column, no right panel column, no
// view switching. Everything is on the page: a slim sticky top bar
// (name + real connection status + health + quota + refresh stamp +
// anchor nav), a data-cards row, then every module as a section in
// order. The old right panel's content moved here: its profile/status
// line is the top bar, its 今天 list is redundant with the full task
// list section, and its detail/context panels sit under their own
// sections below (detail under 对话, members under 项目, and so on).
// The old icon rail's module switching is the anchor nav.
const NAV: Array<[string, string]> = [
["#sec-dispatch", "派发"], ["#sec-tasks", "任务"], ["#sec-conv", "对话"],
["#sec-projects", "项目"], ["#sec-env", "环境"], ["#sec-workers", "Workers"],
["#sec-skills", "技能"], ["#sec-automations", "自动化"], ["#sec-triage", "分诊"],
["#sec-admin", "管理区"], ["#sec-key", "API Key"],
];
function TopBar() {
return (
<header className="dash-topbar">
<div className="dash-topbar-row">
<span className="dash-brand">Codex 命令台</span>
<TopStatus />
<span className="muted dash-hide-sm"><TopHealth /></span>
<span className="muted dash-hide-sm"><TopQuota /></span>
<span className="muted dash-stamp"><TopStamp /></span>
</div>
<nav className="dash-nav" aria-label="页面导航">
{NAV.map(([href, label]) => <a key={href} href={href}>{label}</a>)}
</nav>
</header>
);
}
// Tiny readers so the top bar re-renders from the store.
function TopStatus() {
const ctx = useConsole();
return <span className={ctx.keySaved ? "dash-status ok" : "dash-status off"}>{ctx.keySaved ? "● 已连接" : "○ 未连接"}</span>;
}
function TopHealth() {
return <>{useConsole().healthText}</>;
}
function TopQuota() {
return <>{useConsole().quotaText}</>;
}
function TopStamp() {
return <>自动刷新 · 每 10 秒 · 已刷新 {useConsole().refreshStamp}</>;
}
export default function App() {
const store = useConsoleStore();
const [view, setView] = useState<View>("task");
const [rightOpen, setRightOpen] = useState(true);
useAutoRefresh((selectFocused) => store.refreshAll(selectFocused), 10000);
useIntervalMs(() => store.refreshSlow(), 60000);
const centre = (() => {
switch (view) {
case "projects": return <ProjectsSection />;
case "env": return <EnvSection />;
case "workers": return <WorkersSection />;
case "skills": return <SkillsSection />;
case "automations": return <AutomationsSection />;
case "triage": return <TriageSection />;
case "admin": return <AdminSection />;
case "key": return <ApiKeySection />;
default: return <ConversationSection />;
}
})();
const scrollToDispatch = () => {
document.getElementById("sec-dispatch")?.scrollIntoView({ behavior: "smooth" });
};
return (
<ConsoleContext.Provider value={store}>
<div className={"muse-app" + (rightOpen ? "" : " no-right")}>
<IconRail view={view} onSelect={setView} />
<aside className="list-col">
<TaskListSection onNew={() => setView("task")} />
</aside>
<main className="center-col">
<div className="center-topbar">
<button type="button" className="icon-btn" aria-label="返回任务" title="返回任务" onClick={() => setView("task")}>←</button>
{rightOpen ? <span className="muted">Codex 命令台</span> : (
<button type="button" onClick={() => setRightOpen(true)}>显示右侧栏</button>
)}
<TopBar />
<main className="dash-page">
<DataCards />
<section id="sec-dispatch" className="dash-section">
<DispatchSection />
</section>
<section id="sec-tasks" className="dash-section card">
<h2>任务列表</h2>
<TaskListSection onNew={scrollToDispatch} />
</section>
<section id="sec-conv" className="dash-section">
<div className="card">
<h2>对话</h2>
<ConversationSection />
</div>
<div className="center-inner">{centre}</div>
</main>
{rightOpen ? (
<aside className="right-col">
<RightPanel view={view} onSelect={setView} onClose={() => setRightOpen(false)} />
</aside>
) : null}
</div>
<TaskDetailPanel />
</section>
<section id="sec-projects" className="dash-section">
<ProjectsSection />
<MembersPanel />
</section>
<section id="sec-env" className="dash-section">
<EnvSection />
<EnvContextPanel />
</section>
<section id="sec-workers" className="dash-section">
<WorkersSection />
<HealthPanel />
</section>
<section id="sec-skills" className="dash-section">
<SkillsSection />
<SkillsToolsPanel />
</section>
<section id="sec-automations" className="dash-section">
<AutomationsSection />
</section>
<section id="sec-triage" className="dash-section">
<TriageSection />
</section>
<section id="sec-admin" className="dash-section">
<AdminSection />
</section>
<section id="sec-key" className="dash-section">
<ApiKeySection />
</section>
</main>
</ConsoleContext.Provider>
);
}