feat: 任务详情 LLM 进度与失败原因标识(列表失败原因摘要)
This commit is contained in:
1 parent
b3f4597d3f
commit
70ad8d7d09
3 files changed
+54
-12
No files matched your search
@@ -23,6 +23,29 @@ import { CoordPanel, AgentsPanel, ApprovalPanel, PeerPanel, ThreadPanel } from "
|
||||
// 只是没被挂进页签)。
|
||||
// Data, actions and sub-sections are unchanged; only grouping
|
||||
// and visibility changed.
|
||||
// LLM failure summary: the raw error_text is usually a codex JSON
|
||||
// blob (e.g. a 503 from the relay). The overview must say, at a
|
||||
// glance, whether the model ever ran and why it stopped — previously
|
||||
// the only copy lived in the "操作与结果" <pre>, so a failed task
|
||||
// looked like "failed" with no reason. This is a display mapping
|
||||
// only; it never claims a connection state the server does not
|
||||
// report (there is no "LLM connected" field — running progress
|
||||
// below is elapsed time + last activity, not a live socket state).
|
||||
function errorSummary(err: string): string {
|
||||
if (/503|Service Unavailable/.test(err)) return "上游模型服务暂时不可用(503):模型一轮都没跑出来,不是任务逻辑失败;等上游恢复或换模型重试";
|
||||
if (/429|rate limit/i.test(err)) return "上游限流(429):模型拒绝了本次调用,可稍后重试";
|
||||
if (/timeout|timed out/i.test(err)) return "上游调用超时:模型响应未在限时内返回";
|
||||
if (/401|403|Invalid API key|unauthorized/i.test(err)) return "上游鉴权失败:网关 key 或权限有问题";
|
||||
if (/ECONNREFUSED|ENOTFOUND|network|fetch failed/i.test(err)) return "连不上上游模型服务:网络或网关地址有问题";
|
||||
return "任务失败(原因见原始错误文本)";
|
||||
}
|
||||
|
||||
function fmtAge(sec: number | null): string {
|
||||
if (sec === null || sec < 0) return "未知";
|
||||
if (sec < 60) return Math.floor(sec) + " 秒前";
|
||||
if (sec < 3600) return Math.floor(sec / 60) + " 分钟前";
|
||||
return (sec / 3600).toFixed(1) + " 小时前";
|
||||
}
|
||||
type DetailTab = "overview" | "collab" | "review" | "threads" | "browser";
|
||||
const DETAIL_TABS: Array<[DetailTab, string]> = [
|
||||
["overview", "概览"], ["collab", "协作"], ["review", "评审与审批"], ["threads", "线程"], ["browser", "浏览器"],
|
||||
@@ -82,6 +105,22 @@ export default function TaskDetailPanel() {
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
{(t.status === "running" || t.status === "queued") ? (() => {
|
||||
const now = Date.now() / 1000;
|
||||
const last = t.last_heartbeat || t.updated_at || null;
|
||||
const idle = last ? now - last : null;
|
||||
return (<Card title="LLM 进度">
|
||||
<div>状态 {t.status === "running" ? "进行中" : "排队中"} · 已用 {dur}{idle !== null ? " · 上次活动 " + fmtAge(idle) : ""}</div>
|
||||
{idle !== null && idle > 180
|
||||
? <div className="muted">超过 3 分钟无活动:可能在等上游模型响应(上游 503 时 worker 会重试后失败,失败后这里会显示失败原因;无活动不等于已断连,服务端没有“LLM 已连接”字段,不作此断言)</div>
|
||||
: <div className="muted">首轮无输出时属正常:模型第一轮回复到达前 trace 里没有消息;回复到达后会在会话区出现,成本里的 LLM 请求数随之增加</div>}
|
||||
</Card>);
|
||||
})() : null}
|
||||
{t.error_text ? (<Card title="失败原因">
|
||||
<div>{errorSummary(t.error_text)}</div>
|
||||
<pre style={{ whiteSpace: "pre-wrap", overflowWrap: "anywhere" }}>{t.error_text.slice(0, 1000)}</pre>
|
||||
<div className="muted">完整错误文本仍在下方“操作与结果”里;模型 {t.model || "默认"} · executor {t.executor || ""}</div>
|
||||
</Card>) : null}
|
||||
<Card title="基本信息">
|
||||
<dl className="detail-kv">
|
||||
<div><dt>任务</dt><dd>{t.id}</dd></div>
|
||||
|
||||
@@ -72,6 +72,9 @@ export default function TaskListSection({ onNew }: { onNew: () => void }) {
|
||||
{" · "}<span title={durTitle}>耗时 {dur}</span>
|
||||
{" · "}<span title={tokTitle}>{tok}</span>
|
||||
</div>
|
||||
{(t.status === "failed" || t.status === "interrupted" || t.status === "merge-failed") && t.error_text ? (
|
||||
<div className="issue-meta" title={t.error_text}>{/503|Service Unavailable/.test(t.error_text) ? "上游 503:模型服务暂时不可用" : t.error_text.replace(/\s+/g, " ").slice(0, 60)}</div>
|
||||
) : null}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user