console: 任务详情改卡片式排版

只改排版,不改数据与操作:原来一整块 <br> 分隔的
信息行拆成卡片——概览(状态 + 耗时/token/成本三张
指标卡)、基本信息(键值网格)、prompt(单独一张
卡,长 prompt 不再挤在信息行末尾)、操作与结果、
协调子任务/子代理链路/评审队列/审批队列/发消息/
线程(各一张卡)。指标卡在窄的详情列里用
auto-fit 网格自动折成一列,不挤压;nav-detail
锚点保留在外层容器上。
This commit is contained in:
host committed 2026-10-30 01:13:32 +08:00
1 parent 6d4f87e286
commit b60f4facbd
3 files changed
+109 -59

No files matched your search

@@ -7,9 +7,16 @@ import DetailActions from "./detail/DetailActions";
import ReviewPanel from "./detail/ReviewPanel";
import { CoordPanel, AgentsPanel, ApprovalPanel, PeerPanel, ThreadPanel } from "./detail/DetailLists";
// Right detail column for the task view. Everything structured lives
// here — never inside a chat bubble: metadata, actions, reviews,
// approvals, coordinator children, sub-agents, threads, cost.
// Right detail column for the task view, card-style layout.
// Everything structured lives here — never inside a chat bubble.
// Layout only: data, actions and sub-sections are unchanged,
// just grouped into cards instead of one <br>-separated block:
// 概览(状态 + 耗时/token/成本三张指标卡)
// 基本信息(键值网格)
// prompt(单独一张卡,长 prompt 不再挤在信息行末尾)
// 操作与结果
// 协调子任务 / 子代理链路 / 评审队列 / 运行时审批队列 /
// 给其他任务发消息 / 线程(各一张卡)
export default function TaskDetailPanel() {
const ctx = useConsole();
const t = ctx.currentTask;
@@ -17,44 +24,72 @@ export default function TaskDetailPanel() {
const [dur, durTitle] = durationOf(t);
const [tok, tokTitle] = tokenOf(t);
const fc = firstChar(t);
const cost = ctx.costSummary;
const modeText = t.plan ? "计划模式(只读)"
: t.goal_mode ? (t.no_time_limit ? "goal 模式无敌版(不限时)" : "goal 模式(不限轮次,模型宣告完成)")
: t.verify_command ? "goal 模式(旧语义,验收命令)" : null;
return (
<Card id="nav-detail" title="任务详情">
<div>
状态 <b className={"st-" + t.status}>{t.status}</b>({STATUS_EXPLAIN[t.status] || "未知状态"})
<br /><span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}><span className="issue-first" title={"任务首字:" + fc} aria-hidden="true">{fc}</span><span title={durTitle}>耗时 {dur}</span><span> · </span><span title={tokTitle}>{tok}</span></span>
<br />{ctx.costSummary === undefined ? <span className="muted">成本加载中…</span>
: ctx.costSummary === null ? <span className="muted">成本明细加载失败(“查成本”按钮仍可手动拉取原始 JSON)</span>
: <span title="成本明细:GET /tasks/{id}/cost 聚合;美元只计有价行,无价 token 单列;缓存是输入 token 的子集,不另计">成本 {fmtCostUsd(ctx.costSummary.usd_micros)} | token {(ctx.costSummary.total_tokens ?? 0).toLocaleString()}(实际 {(ctx.costSummary.actual_tokens ?? 0).toLocaleString()} / 估算 {(ctx.costSummary.estimated_tokens ?? 0).toLocaleString()},缓存 {(ctx.costSummary.cached_input_tokens ?? 0).toLocaleString()},无价 {(ctx.costSummary.unpriced_actual_tokens ?? 0).toLocaleString()})| 轮数 {ctx.costSummary.turns ?? 0} | fallback {ctx.costSummary.fallback_count ?? 0}</span>}
<br />任务 {t.id} | turn {shortId(t.turn_id)} | root turn {shortId(t.root_turn_id)}
<br />executor {t.executor || ""} | 主机 {t.worktree_host || ""}
<br />模型 {t.model || "默认"} | 强度 {t.effort || "默认"}
{(t.child_model || t.child_effort) ? <><br />子任务默认 {(t.child_model || "跟随本任务") + " / " + (t.child_effort || "跟随本任务")}</> : null}
<br />权限 {t.approval || "默认"} | 项目 {shortId(t.project_id)}
<br />更新 {fmt(t.updated_at)}
{t.plan ? <><br />计划模式(只读)</> : null}
{t.forked_from ? <><br />分支自 {shortId(t.forked_from)}</> : null}
{t.goal_mode ? <><br />{t.no_time_limit ? "goal 模式无敌版(不限时)" : "goal 模式(不限轮次,模型宣告完成)"}</> : t.verify_command ? <><br />goal 模式(旧语义,验收命令)</> : null}
{t.coordinator_parts ? <><br />协调员:{t.coordinator_parts.length} 个子任务({t.coordinator_parts.map((p) => p.name).join("、")})</> : null}
{t.parent_task_id ? <><br />协调子任务,父任务 {shortId(t.parent_task_id)}{t.coordinator_part ? "(" + t.coordinator_part + ")" : ""}</> : null}
{(t.attachments && t.attachments.length) ? <><br />附件:{t.attachments.join("、")}</> : null}
{t.pr_url ? <><br /><a href={t.pr_url} target="_blank" rel="noopener noreferrer">PR #{t.pr_number}</a></> : null}
<br />prompt:{t.prompt || ""}
</div>
<DetailActions />
<pre>{ctx.detailBody || "(暂无 result/error 输出)"}</pre>
{ctx.costBox ? <pre>{ctx.costBox}</pre> : null}
<h3>协调子任务</h3>
<CoordPanel />
<h3>子代理链路</h3>
<AgentsPanel />
<h3>评审队列</h3>
<ReviewPanel />
<h3>运行时审批队列</h3>
<ApprovalPanel />
<h3>给其他任务发消息</h3>
<PeerPanel />
<h3>线程</h3>
<ThreadPanel />
</Card>
<div className="detail-stack" id="nav-detail">
<Card title="任务详情">
<div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
<span className="issue-first" title={"任务首字:" + fc} aria-hidden="true">{fc}</span>
<span>状态 <b className={"st-" + t.status}>{t.status}</b>({STATUS_EXPLAIN[t.status] || "未知状态"})</span>
<span className="muted">更新 {fmt(t.updated_at)}</span>
</div>
<div className="detail-cards">
<div className="detail-card" title={durTitle}>
<div className="detail-card-label">耗时</div>
<div className="detail-card-value">{dur}</div>
</div>
<div className="detail-card" title={tokTitle}>
<div className="detail-card-label">token</div>
<div className="detail-card-value">{tok}</div>
</div>
<div className="detail-card" title="成本明细:GET /tasks/{id}/cost 聚合;美元只计有价行,无价 token 单列;缓存是输入 token 的子集,不另计">
<div className="detail-card-label">成本</div>
<div className="detail-card-value">
{cost === undefined ? "加载中…" : cost === null ? "—" : fmtCostUsd(cost.usd_micros)}
</div>
{cost === undefined ? <div className="muted">成本加载中…</div>
: cost === null ? <div className="muted">成本明细加载失败(“查成本”按钮仍可手动拉取原始 JSON)</div>
: <div className="muted">token {(cost.total_tokens ?? 0).toLocaleString()}(实际 {(cost.actual_tokens ?? 0).toLocaleString()} / 估算 {(cost.estimated_tokens ?? 0).toLocaleString()},缓存 {(cost.cached_input_tokens ?? 0).toLocaleString()},无价 {(cost.unpriced_actual_tokens ?? 0).toLocaleString()})| 轮数 {cost.turns ?? 0} | fallback {cost.fallback_count ?? 0}</div>}
</div>
</div>
</Card>
<Card title="基本信息">
<dl className="detail-kv">
<div><dt>任务</dt><dd>{t.id}</dd></div>
<div><dt>turn</dt><dd>{shortId(t.turn_id)}</dd></div>
<div><dt>root turn</dt><dd>{shortId(t.root_turn_id)}</dd></div>
<div><dt>executor</dt><dd>{t.executor || ""}</dd></div>
<div><dt>主机</dt><dd>{t.worktree_host || ""}</dd></div>
<div><dt>模型</dt><dd>{t.model || "默认"}</dd></div>
<div><dt>强度</dt><dd>{t.effort || "默认"}</dd></div>
{(t.child_model || t.child_effort) ? <div><dt>子任务默认</dt><dd>{(t.child_model || "跟随本任务") + " / " + (t.child_effort || "跟随本任务")}</dd></div> : null}
<div><dt>权限</dt><dd>{t.approval || "默认"}</dd></div>
<div><dt>项目</dt><dd>{shortId(t.project_id)}</dd></div>
{modeText ? <div><dt>模式</dt><dd>{modeText}</dd></div> : null}
{t.forked_from ? <div><dt>分支自</dt><dd>{shortId(t.forked_from)}</dd></div> : null}
{t.coordinator_parts ? <div><dt>协调员</dt><dd>{t.coordinator_parts.length} 个子任务({t.coordinator_parts.map((p) => p.name).join("、")})</dd></div> : null}
{t.parent_task_id ? <div><dt>协调子任务</dt><dd>父任务 {shortId(t.parent_task_id)}{t.coordinator_part ? "(" + t.coordinator_part + ")" : ""}</dd></div> : null}
{(t.attachments && t.attachments.length) ? <div><dt>附件</dt><dd>{t.attachments.join("、")}</dd></div> : null}
{t.pr_url ? <div><dt>PR</dt><dd><a href={t.pr_url} target="_blank" rel="noopener noreferrer">PR #{t.pr_number}</a></dd></div> : null}
</dl>
</Card>
<Card title="prompt">
<div style={{ whiteSpace: "pre-wrap", overflowWrap: "anywhere" }}>{t.prompt || "(无 prompt)"}</div>
</Card>
<Card title="操作与结果">
<DetailActions />
<pre>{ctx.detailBody || "(暂无 result/error 输出)"}</pre>
{ctx.costBox ? <pre>{ctx.costBox}</pre> : null}
</Card>
<Card title="协调子任务"><CoordPanel /></Card>
<Card title="子代理链路"><AgentsPanel /></Card>
<Card title="评审队列"><ReviewPanel /></Card>
<Card title="运行时审批队列"><ApprovalPanel /></Card>
<Card title="给其他任务发消息"><PeerPanel /></Card>
<Card title="线程"><ThreadPanel /></Card>
</div>
);
}
+15
View File
@@ -133,6 +133,21 @@ pre{background:var(--inset-bg);color:var(--text);border:1px solid var(--line);bo
.issue-state.st-failed,.issue-state.st-merge-failed{background:var(--danger)}
.issue-state.st-interrupted{background:var(--attention)}
.issue-first{flex:none;width:22px;height:22px;margin-top:1px;border:1px solid var(--line);border-radius:6px;background:var(--subtle-bg);display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:600;color:var(--muted);overflow:hidden}
/* Task detail card layout: cards stacked with a gap, KPI cards in a
responsive grid (auto-fit, so the narrow right column collapses
to one per row instead of squeezing three). Only layout: no data,
action or section is added or removed by this styling. */
.detail-stack{display:flex;flex-direction:column;gap:16px;min-width:0}
.detail-stack > .card{margin:0}
.detail-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-top:12px}
.detail-card{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:12px 16px;min-width:0}
.detail-card-label{color:var(--muted);font-size:12px;font-weight:600}
.detail-card-value{font-size:20px;font-weight:600;line-height:1.3;overflow-wrap:anywhere}
.detail-card .muted{font-size:12px;line-height:1.5;overflow-wrap:anywhere}
.detail-kv{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px 16px;margin:0;font-size:13px}
.detail-kv > div{min-width:0}
.detail-kv dt{color:var(--muted);font-size:12px}
.detail-kv dd{margin:0;overflow-wrap:anywhere}
.issue-title{font-size:14px;line-height:1.5;overflow-wrap:anywhere}
.issue-meta{font-size:12px;color:var(--muted);line-height:1.5}
/* Conversation as a GitHub timeline / comments: white comment boxes