console: 按 Muse 网页端/桌面端视觉重做
仅视觉与布局改动,功能与 API 未改:全屏四栏(56px 图标栏 + 260px 列表 + 弹性对话 + 300px 右侧栏),左侧搜索 + 主要聊天 列表,中间灰/浅蓝气泡对话 + 圆角输入条,右侧头像 + 已连接 (真实 key/健康状态)+ 今天任务清单,下方仍可展开原详情。 旧 v3 松绿/朱印与顶部大标题栏已移除,产物为单文件 (316226 字节,sha f8fea987…,本地重建字节一致)。 console 标记单测未动,本地 53 项通过。
This commit is contained in:
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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user