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

96 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useConsole } from "../context";
import { Card } from "../components/ui";
import { shortId, fmt } from "../api/client";
import { STATUS_EXPLAIN } from "../api/types";
import { firstChar, durationOf, tokenOf, fmtCostUsd } from "../api/taskMeta";
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, 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;
if (!t) return <Card id="nav-detail" title="任务详情"><div className="muted">点任务行查看详情</div></Card>;
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 (
<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>
);
}