只改排版,不改数据与操作:原来一整块 <br> 分隔的 信息行拆成卡片——概览(状态 + 耗时/token/成本三张 指标卡)、基本信息(键值网格)、prompt(单独一张 卡,长 prompt 不再挤在信息行末尾)、操作与结果、 协调子任务/子代理链路/评审队列/审批队列/发消息/ 线程(各一张卡)。指标卡在窄的详情列里用 auto-fit 网格自动折成一列,不挤压;nav-detail 锚点保留在外层容器上。
96 lines
6.0 KiB
TypeScript
96 lines
6.0 KiB
TypeScript
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>
|
||
);
|
||
}
|