Files
AgentLab/frontend/src/lib/status.ts
T
ThearchyHelios 03c18c4d78 界面文案整改:口语改书面、术语统一、不再露出实现细节;修掉几处说错的提示;新增文案检查防回潮
一、文案(前端约 1600 处,后端传到界面的约 880 处)
- 术语统一:工作流层面用「运行」(继续运行、重新运行、试运行),节点和工具层面用「执行」,不再用「跑」;
  总时长 / 执行时长 / 等待审批;服务端;助手;Agent;token;工作流;固定版本;模型接入;系统提示;批准 / 驳回;受管级别
- 口语改书面:取不到、没取回来、读不懂、连不上、多半、照样、兜底、揉成这类说法改成「无法 / 未能 / 失败 / 请」;去掉拟人写法
- 不再露出实现细节:界面上不写 checkpoint、同一拍、哈希(凭证处除外),不写改版历史(「以前…」「老数据」);
  报错、校验、门禁、自动修复里裸写的配置键名和枚举值换成界面上的中文标签,由新的 backend/app/engine/labels.py 统一提供;
  发布弹窗里结论句的选项不再是 off / require_citation
- 同一件事只留一种说法:生成阶段文案、出具档位说明、画布锁定提示各合成一份
- 去掉界面上的工业场景示例(MES、ERP、「张工」「王工」)和真实产品名,换成通用示例

二、说错的提示(都有测试证明)
- 数据库测连接失败时的专用提示以前从不出现(按报错文字全等比较,永远比不上):改为按错误类型判断,不再拼出重复的前缀
- 「设置 → 供应商」改指「设置 → 模型接入」;「在设置里建数据源」改指「数据」页
- 缺少 API Key、模型接入已停用的报错不再夹英文,写明去哪里填
- 两处原因说错(「这种引用在后续版本支持」「这一版还不支持」)改成真实原因
- 节点说明里不准确的说法改正:代码节点在 microVM 或系统沙箱中执行;报告撰写、口径卡的说明不再说过头
- 导出的证据 CSV 里两列同名,第三列改为「片段编号」

三、前后端对齐
- 前端按后端原文识别日志、报错的地方,新旧两种写法都认得(历史运行里存的是旧文字),逐条补了新旧原文的核对
- 后端交回模型的原指令放进单独的 for_model 字段,前端在解析响应时去掉,不会显示到界面上

四、检查
- 检查脚本里按原文匹配的断言改成新文案;改完会变成永远成立的反向断言逐条修正,并做了变异验证
- 新增 check-copy(静态扫描前端界面字符串和后端上界面的文案):再出现「跑」「后端」「多半」「令牌」、
  界面上的 Copilot、裸写的配置键名、半角标点等就报错;确需保留的写进 scripts/copy-allow.json 并注明理由
2026-09-30 08:05:15 +08:00

201 lines
9.1 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.
/**
* 运行状态与节点状态的唯一一份文案和外观。
*
* 之前有五张映射表:StatusDot 写「成功 / 等待人工」,运行页轮次头写「完成 /
* 等待人工介入」,问数据写「完成 / 出错了」,运行条写「等你确认」……同一屏上
* 「成功」和「完成」并存,人会以为是两个状态。加一个状态就得去五处各补一遍。
*
* 每个状态同时有剪影(shape)、颜色和文字三条通道:去掉颜色还能靠剪影认出来,
* 去掉剪影还能读字。颜色走 index.css 的状态令牌 --st-*;令牌还没定义时回落到
* ok / warn / err / accent 这几个基础色。
*/
import type { RunStatus } from '../types'
/** 所有能被显示的状态。运行层和节点层共用一张表,其中几个只出现在一层 */
export type StatusCode =
| 'idle' // 节点:还没跑过
| 'queued' // 运行:排队;节点:上游都交付了、自己还没开始(推导)
| 'running'
| 'waiting' // 等人审批
| 'done' // 节点跑完
| 'succeeded' // 运行跑完
| 'failed'
| 'cancelled'
| 'suspended' // 服务重启后挂起,可续跑
| 'held' // 后端的 interrupted 但没有待审批:已挂起 · 可续跑
| 'skipped' // 被 skip_if 跳过
| 'blocked' // 失败节点的下游,这次再也跑不到了(推导)
| 'unreached' // 运行结束时一次都没轮到,比如分支没走的那一侧(推导)
/**
* 九种剪影,外加挂起用的暂停符和未运行用的小空心点。
* 形状是给"去掉颜色也能认"准备的:灰度打印、色弱、投影仪偏色时都要分得开。
*/
export type StatusShape =
| 'ring' // 圆环:运行中
| 'dashed-ring' // 虚线环:排队
| 'square' // 实心圆角方(带勾):完成
| 'diamond' // 菱形:等人
| 'triangle' // 三角:失败
| 'slashed' // 斜杠圆:跳过
| 'stop' // 实心小方:取消
| 'dashed-x' // 虚线方加 ×:阻断
| 'bar' // 横杠:未到达
| 'pause' // 两竖条:挂起
| 'dot' // 小空心点:未运行
export interface StatusMeta {
code: StatusCode
/** 完整叫法,状态标签和 tooltip 用 */
label: string
/** 窄处用的短叫法(节点卡的运行槽、HUD 计数) */
short: string
shape: StatusShape
/** 前景色,CSS 颜色表达式 */
color: string
/** 淡底色,CSS 颜色表达式 */
soft: string
/** 是不是异常态。正常态安静、异常态醒目:只有它们该用底色和强调 */
alert: boolean
/** 是不是终态(这次运行里不会再变了) */
terminal: boolean
/** 推导出来的状态:tooltip 里要写明"推导",别让人以为是后端说的 */
derived?: boolean
/** 一句话解释,放 title */
hint?: string
}
const tok = (name: string, fallback: string) => ({
color: `var(--st-${name}, ${fallback})`,
soft: `var(--st-${name}-soft, color-mix(in srgb, ${fallback} 14%, transparent))`,
})
export const STATUS: Record<StatusCode, StatusMeta> = {
idle: {
code: 'idle', label: '未运行', short: '未运行', shape: 'dot', alert: false, terminal: false,
...tok('idle', 'var(--text-faint)'),
},
queued: {
code: 'queued', label: '排队中', short: '排队', shape: 'dashed-ring', alert: false, terminal: false,
hint: '上游已完成,等待同一步中的其他节点完成', ...tok('queued', 'var(--text-dim)'),
},
running: {
code: 'running', label: '运行中', short: '运行中', shape: 'ring', alert: false, terminal: false,
...tok('running', 'var(--accent)'),
},
waiting: {
code: 'waiting', label: '等待审批', short: '待审批', shape: 'diamond', alert: true, terminal: false,
hint: '停在人工审批处,处理后继续运行', ...tok('waiting', 'var(--warn)'),
},
done: {
code: 'done', label: '已完成', short: '完成', shape: 'square', alert: false, terminal: true,
...tok('done', 'var(--ok)'),
},
succeeded: {
code: 'succeeded', label: '已完成', short: '完成', shape: 'square', alert: false, terminal: true,
hint: '执行已完成。结论是否可用,请查看出具档位和复核结果', ...tok('done', 'var(--ok)'),
},
failed: {
code: 'failed', label: '失败', short: '失败', shape: 'triangle', alert: true, terminal: true,
...tok('failed', 'var(--err)'),
},
cancelled: {
code: 'cancelled', label: '已取消', short: '已取消', shape: 'stop', alert: false, terminal: true,
...tok('cancelled', 'var(--text-faint)'),
},
suspended: {
code: 'suspended', label: '已中断(服务重启)', short: '已中断', shape: 'pause', alert: true, terminal: true,
hint: '服务重启时本次运行尚未结束,已在断点处挂起,可继续运行', ...tok('suspended', 'var(--warn)'),
},
held: {
code: 'held', label: '已挂起 · 可继续运行', short: '已挂起', shape: 'pause', alert: true, terminal: true,
hint: '运行停在断点处,且没有待处理的审批,通常是服务重启所致,可继续运行',
...tok('suspended', 'var(--warn)'),
},
skipped: {
code: 'skipped', label: '已跳过', short: '跳过', shape: 'slashed', alert: false, terminal: true,
hint: '跳过条件成立,本步骤未执行', ...tok('skipped', 'var(--text-faint)'),
},
blocked: {
code: 'blocked', label: '已阻断', short: '阻断', shape: 'dashed-x', alert: true, terminal: true,
derived: true, hint: '推导:上游节点失败,本次运行不会执行到此处', ...tok('blocked', 'var(--err)'),
},
unreached: {
code: 'unreached', label: '未到达', short: '未到达', shape: 'bar', alert: false, terminal: true,
derived: true, hint: '推导:运行结束时未执行到此节点,例如未命中的条件分支', ...tok('unreached', 'var(--text-faint)'),
},
}
/** 旧代码和后端里的别名。未知状态码原样落到 idle 的外观上,文字照写原码 */
const ALIAS: Record<string, StatusCode> = {
success: 'succeeded', ok: 'succeeded', completed: 'succeeded', finished: 'succeeded',
error: 'failed', canceled: 'cancelled', pending: 'queued', interrupted: 'waiting',
}
/**
* 把后端的运行状态落到显示用的状态上。
*
* 后端的 interrupted 有两种:停在审批上(等待审批),和服务重启打断后没有
* 待审批(已挂起 · 可续跑)。二者要看审批列表里有没有这条运行的 pending
* 才分得开——传 pendingApproval。不知道时(没传)按等待审批处理,这是
* interrupted 最常见的来源,也是改造前的说法,不会比原来更错。
*/
export function resolveStatus(status: string | null | undefined, opts?: { pendingApproval?: boolean }): StatusCode {
if (!status) return 'idle'
if (status === 'interrupted') return opts?.pendingApproval === false ? 'held' : 'waiting'
if (status in STATUS) return status as StatusCode
return ALIAS[status] ?? 'idle'
}
/** 状态的完整外观。未知状态码回落到 idle 的外观,文字写原码,免得静默吞掉 */
export function statusMeta(status: string | null | undefined, opts?: { pendingApproval?: boolean }): StatusMeta {
const code = resolveStatus(status, opts)
const meta = STATUS[code]
if (status && code === 'idle' && status !== 'idle' && !(status in ALIAS)) {
return { ...meta, label: status, short: status }
}
return meta
}
/** 只要文字 */
export function statusLabel(status: string | null | undefined, opts?: { pendingApproval?: boolean; short?: boolean }): string {
const meta = statusMeta(status, opts)
return opts?.short ? meta.short : meta.label
}
/**
* 运行列表的状态筛选用:按显示顺序排好的运行状态。
* 这是显示用的码,不能直接当查询参数——waiting / held 在后端都是 interrupted,
* 查询前用 serverStatusOf 换回去。
*/
export const RUN_STATUS_ORDER: StatusCode[] = [
'running', 'waiting', 'held', 'failed', 'succeeded', 'cancelled', 'queued',
]
/**
* 显示用的状态码换回后端 runs.status 认的值,给 api.runs.list({status}) 用。
*
* waiting、held、suspended 在后端都是 interrupted:按它查回来的是两种混在一起,
* 要再用 resolveStatus(run.status, {pendingApproval: hasPendingApproval(approvals,
* run.id)}) 在前端分开。节点才有的状态(idle / skipped / blocked / unreached)
* 不是运行状态,返回 null。
*/
export function serverStatusOf(code: StatusCode): RunStatus | null {
switch (code) {
case 'waiting': case 'held': case 'suspended': return 'interrupted'
case 'done': case 'succeeded': return 'succeeded'
case 'queued': case 'running': case 'failed': case 'cancelled': return code
default: return null
}
}
/**
* 运行是不是还"活着"(会有新事件)。held / suspended 虽然后端写的是
* interrupted,但不会自己动,不算。
*/
export function isLiveStatus(status: string | null | undefined, opts?: { pendingApproval?: boolean }): boolean {
const code = resolveStatus(status, opts)
return code === 'queued' || code === 'running' || code === 'waiting'
}