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

@@ -1,15 +1,28 @@
import { useConsole } from "../context";
import { DataListRaw } from "../components/ui";
import { shortId } from "../api/client";
// Left-rail compact task list. The live 9-column table does not fit a
// 300px rail, so the rail shows id + status + prompt + executor; the
// full metadata (turn, root turn, host, PR, updated) lives in the right
// detail panel for the selected task. Filters/search are the live ones.
export default function TaskListSection() {
// Left list panel, Muse screenshot style: rounded search on top, a
// 主要聊天 section header with a + button, then the task list. The
// search input IS the existing task search (id/prompt); the status and
// executor filters are kept as a compact row below it — the screenshot
// has no filters, but dropping them would cut functionality, so they
// stay, just compact. List rows show the prompt truncated on one line
// with a status dot, short id, status and executor below it; the
// selected row is highlighted grey, as in the screenshot.
export default function TaskListSection({ onNew }: { onNew: () => void }) {
const ctx = useConsole();
return (
<div className="left-tasks">
<div>
<div className="search-box">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" aria-hidden="true">
<circle cx="11" cy="11" r="7" /><path d="M21 21l-4.3-4.3" />
</svg>
<input type="text" placeholder="搜索" aria-label="搜索任务" value={ctx.searchText} onChange={(e) => ctx.setSearchText(e.target.value)} />
</div>
<div className="list-section-head">
<span>主要聊天</span>
<button type="button" aria-label="派发新任务" title="派发新任务" onClick={onNew}>+</button>
</div>
<div className="bar">
<select aria-label="状态筛选" value={ctx.statusFilter} onChange={(e) => ctx.setStatusFilter(e.target.value)}>
<option value="">状态:全部</option>
@@ -19,7 +32,6 @@ export default function TaskListSection() {
<option value="">执行器:全部</option><option value="local">local</option><option value="docker">docker</option><option value="cloud">cloud</option>
</select>
</div>
<input type="text" placeholder="搜索任务 id / prompt" value={ctx.searchText} onChange={(e) => ctx.setSearchText(e.target.value)} />
<div className="bar">
<button onClick={ctx.refreshTasks}>刷新</button>
<span className="muted">{ctx.taskCountText}</span>
@@ -28,18 +40,21 @@ export default function TaskListSection() {
: !ctx.projectId ? <div className="muted">先选项目</div>
: ctx.filteredTasks.length === 0 ? <div className="muted">{ctx.tasks.length ? "没有符合筛选条件的任务" : "该项目暂无任务"}</div>
: (
<DataListRaw>
<ul className="muse-list">
{ctx.filteredTasks.slice().reverse().map((t) => (
<li key={t.id} className={ctx.currentTask?.id === t.id ? "selected-task" : ""} onClick={() => ctx.showDetail(t.id)} style={{ cursor: "pointer" }}>
<span className="row-main">
<b>{shortId(t.id)}</b> <span className={"st-" + t.status}>{t.status}</span>
<br />
{t.plan ? "[计划] " : ""}{t.forked_from ? "[分支] " : ""}{t.coordinator_parts ? "[协调] " : ""}{t.parent_task_id ? "[协调子任务] " : ""}{String(t.prompt || "").slice(0, 60)}
<br /><span className="muted">{t.executor || ""}</span>
</span>
<li key={t.id} className={"muse-item" + (ctx.currentTask?.id === t.id ? " selected" : "")} onClick={() => ctx.showDetail(t.id)}>
<div className="muse-item-title">
{t.plan ? "[计划] " : ""}{t.forked_from ? "[分支] " : ""}{t.coordinator_parts ? "[协调] " : ""}{t.parent_task_id ? "[协调子任务] " : ""}{String(t.prompt || shortId(t.id)).slice(0, 60)}
</div>
<div className="muse-item-sub">
<span className={"task-dot st-" + t.status} />
<span>{shortId(t.id)}</span>
<span className={"st-" + t.status}>{t.status}</span>
<span>{t.executor || ""}</span>
</div>
</li>
))}
</DataListRaw>
</ul>
)}
</div>
);