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,47 @@
import type { ReactNode } from "react";
// Far-left icon rail from the Muse screenshot: one simple inline-SVG
// icon per module, no emoji, no external image. This replaces LeftNav
// as the module switcher; the left list panel below it is the task list.
export type View = "task" | "projects" | "env" | "workers" | "skills" | "automations" | "triage" | "admin" | "key";
function Svg({ children }: { children: ReactNode }) {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
{children}
</svg>
);
}
export function ModuleIcon({ view }: { view: View }) {
switch (view) {
case "task": return <Svg><path d="M21 11.5a8.5 8.5 0 0 1-12.4 7.5L3 21l2-5.6A8.5 8.5 0 1 1 21 11.5Z" /></Svg>;
case "projects": return <Svg><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8Z" /><path d="M14 3v5h5" /><path d="M9 13h6M9 17h6" /></Svg>;
case "env": return <Svg><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z" /></Svg>;
case "workers": return <Svg><rect x="3" y="3" width="7" height="7" rx="1.5" /><rect x="14" y="3" width="7" height="7" rx="1.5" /><rect x="3" y="14" width="7" height="7" rx="1.5" /><rect x="14" y="14" width="7" height="7" rx="1.5" /></Svg>;
case "skills": return <Svg><path d="M9 18h6M10 21h4" /><path d="M12 3a6 6 0 0 0-3.5 10.8c.8.6 1.5 1.4 1.5 2.2h4c0-.8.7-1.6 1.5-2.2A6 6 0 0 0 12 3Z" /></Svg>;
case "automations": return <Svg><circle cx="12" cy="12" r="9" /><path d="M12 7v5l3 2" /></Svg>;
case "triage": return <Svg><circle cx="12" cy="12" r="9" /><path d="M8 12.5l2.5 2.5L16 9.5" /></Svg>;
case "admin": return <Svg><path d="M12 3l7 3v5c0 5-3.5 8-7 9-3.5-1-7-4-7-9V6Z" /><path d="M9 12l2 2 4-4" /></Svg>;
case "key": return <Svg><circle cx="8" cy="15" r="4" /><path d="M11.5 11.5L20 3" /><path d="M17 6l2 2M20 3l-1 3" /></Svg>;
default: return <Svg><circle cx="12" cy="12" r="9" /></Svg>;
}
}
const ITEMS: Array<[View, string]> = [
["task", "任务"], ["projects", "项目"], ["env", "环境"], ["workers", "Workers"],
["skills", "技能与 MCP"], ["automations", "自动化"], ["triage", "分诊"],
["admin", "管理区"], ["key", "API Key"],
];
export default function IconRail({ view, onSelect }: { view: View; onSelect: (v: View) => void }) {
return (
<nav className="icon-rail" aria-label="模块快捷方式">
{ITEMS.map(([v, label]) => (
<button key={v} type="button" title={label} aria-label={label} className={view === v ? "active" : ""} onClick={() => onSelect(v)}>
<ModuleIcon view={v} />
</button>
))}
</nav>
);
}
@@ -1,17 +0,0 @@
export type View = "task" | "projects" | "env" | "workers" | "skills" | "automations" | "triage" | "admin" | "key";
const ITEMS: Array<[View, string]> = [
["task", "任务"], ["projects", "项目"], ["env", "环境"], ["workers", "Workers"],
["skills", "技能与 MCP"], ["automations", "自动化"], ["triage", "分诊"],
["admin", "管理区"], ["key", "API Key"],
];
export default function LeftNav({ view, onSelect }: { view: View; onSelect: (v: View) => void }) {
return (
<nav className="left-nav" aria-label="模块导航">
{ITEMS.map(([v, label]) => (
<button key={v} className={view === v ? "active" : ""} onClick={() => onSelect(v)}>{label}</button>
))}
</nav>
);
}
@@ -54,19 +54,25 @@ export function ChatInput({ onSend, disabled, placeholder }: {
onSend(v);
setText("");
};
// Pill input bar, Muse screenshot style: the textarea is the pill
// itself, and send is a light-blue circular arrow. No mic or
// attachment button here: the continue chat has no voice or
// attachment API (attachments live in Dispatch), so a mic/+ icon
// would be a dead button; the left list header's + is the new-task
// entry instead.
return (
<div className="chat-input-bar">
<textarea
aria-label="会话输入"
className="chat-input"
rows={1}
placeholder={placeholder || "输入消息,回车发送,Shift+回车换行"}
placeholder={placeholder || "消息"}
value={text}
disabled={disabled}
onChange={(e) => setText(e.target.value)}
onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } }}
/>
<button className="primary chat-send" disabled={disabled || !text.trim()} onClick={send}>发送</button>
<button type="button" className="chat-send" aria-label="发送" disabled={disabled || !text.trim()} onClick={send}>↑</button>
</div>
);
}