run-history-ui.md 15 KB

运行记录 UI 参考

当需要构建展示 agent-management 工作流执行进度或历史运行详情的前端页面/组件时,使用本参考。保留其内容、状态流转和信息层级;视觉设计则按目标产品自身风格适配。

硬性要求:业务方必须完整呈现「实时状态、思考过程、工具调用、待办清单、执行日志、最终结果」六类内容,交互逻辑(折叠策略、multi-visit 渲染、AskFor 处理)与本系统一致。可直接复用的 JS/Vue 实现见 js-implementation.md;接口契约见 external-workflow-api.md。

如果用户提供了已部署的参考页面,可将其作为可选的内容与交互参考,而不是必须照搬的样式来源。

页面结构

采用面向执行的视图。对于管理/历史类页面,左右两栏的运行记录布局是较优的默认选择,但嵌入式或面向特定任务的集成不强制使用。

  • 页面标题:运行记录 或目标应用中的等价文案。
  • 浏览历史运行时需有运行选择区。
  • 选中或当前运行的详情区。
  • 没有运行或未选中运行时,展示清晰的空状态。
  • 初始加载运行数据时,居中展示 loading spinner。

集成到目标应用时,遵循其既有约定,例如:

  • API 封装或服务模块约定。
  • 请求辅助封装、鉴权与错误处理约定。
  • 既有的组件库与图标集。
  • 既有的路由、状态管理、间距、排版与状态样式。

除非用户明确要求近似视觉,否则不要照搬原运行记录页样式。

工作流与运行选择

展示运行历史时,按工作流分组有助于用户查找历史执行:

  • 工作流头部:折叠箭头、工作流图标、工作流名称和运行数量。
  • 工作流标题下方可选地展示工作流标签(小 chip)。
  • 运行条目:
    • 状态圆点
    • 短运行 ID,例如 #a3f9b2c1
    • 状态徽标
    • 启动时间
    • 完成时展示耗时
    • 删除操作仅出现在管理/历史页面,只读嵌入式视图不展示

常用交互:

  • 工作流分组可折叠,默认展开。
  • 点击运行会加载并选中其详情。
  • 当前运行有明显的选中态。

对于纯实时执行视图,完整的历史列表是可选的;展示当前工作流/运行身份、实时状态与节点推进即可。

运行详情

运行详情头部:

  • 状态圆点
  • 运行 #<runId>
  • 状态徽标
  • 启动时间
  • 完成时间(如有)
  • 耗时(如有)
  • 运行级错误块(如存在)

随后按 visit.sortOrder 顺序渲染 Visit 卡片(不是节点卡片,见「multi-visit 渲染」)。

页脚:

  • 当工作空间下载可用时,提供 下载工作空间(运行成功结束时 workflow_complete 事件携带 workingDirFiles 文件列表,可与下载按钮归到一组展示)。
  • 对外部 API 集成,使用 workspace.downloadUrl 或 /api/v1/workflows/{workflowName}/runs/{runId}/workspace。{workflowName} 是 kebab-case 唯一标识(推荐),纯数字 id 作为兼容兜底也合法。
  • 下载作为二进制导航/blob 处理,不要按 JSON 处理。

Visit 卡片与 multi-visit 渲染

路由主键是 visitUuid,不是 nodeId:

  • DAG 模式(按画布拓扑执行):节点只执行一次,一张卡片对应一个节点。
  • AGENTIC 模式(智能体工作流,画布即能力地图,由全局 AI 决定下一步):同一节点可被多次访问,每次访问独立成卡,纵向堆叠,绝不合并。

卡片头部信息:

  • 状态徽标(running/success/failed/skipped)
  • 节点类型标签 + 节点名称(label)
  • 重访标识:visit.iterSeq > 1 时展示「第 N 次访问」徽章
  • 自由跳转标识:AGENTIC 模式下 visit.fromNodeId 存在且跳转不在画布边上(visit.decisionJson === '{"offCanvas":true}')时,展示「来自 {fromNodeName}」徽章
  • 耗时(如有)
  • 点击头部切换折叠/展开

补充建议:AGENTIC 模式下可额外渲染「AI 决策链」(消费 planner_decision 事件的 decision.nextNodeId + decision.reason),让用户理解为什么跳到这个节点。

Visit 卡片内部:六面板结构

节点运行时按以下顺序渲染面板;面板顺序固定为「实时状态 → 思考过程 → 待办清单 → 工具调用 → 执行日志」,最终输出在面板区之后单独渲染。

① 实时状态(liveStatus)

  • 数据来源:node_stream kind=status 的 content(覆盖式,永远只保留最新一条)。
  • 展示为高亮的一行文案,如「压缩上下文中」「正在调用工具 search」。
  • 运行结束后自动隐藏(该面板只服务运行中场景)。

② 思考过程(thinking)

  • 数据来源:node_stream kind=thinking 的 content 依次追加。
  • markdown 渲染(要求见「markdown 渲染」章节),可含表格、代码块、公式、多级标题。
  • 运行中默认展开;节点完成后跟随卡片折叠。
  • 长内容限高 + 内部滚动。

③ 待办清单(todoList)

  • 数据来源:node_stream kind=todo_update 的 content(JSON 字符串,解析后为 TodoWrite 工具输出)整体覆盖。
  • 解析出条目列表,每条含 content 与 status(pending/in_progress/completed),渲染为 ✓/○/进行中图标 + 文本。
  • 无数据时整个面板不渲染(不渲染空占位框)。

④ 工具调用(toolCalls)

  • 数据来源:tool_call(开始)、tool_progress(细粒度进度)、tool_result(结束)三类 node_stream 事件。
  • 每个工具调用渲染为一张小卡片:工具名 + 状态(运行中/完成/失败)+ 参数 + 结果摘要。
  • tool_call 与 tool_result 事件不含 callId,按「同 Visit 内到达顺序」配对:tool_call 入队,tool_result 关闭最早未关闭的同名调用(Hermes 工具串行执行,顺序配对安全)。
  • tool_call / tool_result 的 content 已被服务端截断到 500 字符,仅作预览;完整内容从节点 logs(TOOL_CALL.detail / TOOL_RESULT.detail)获取。
  • 无工具调用时整个面板不渲染。

⑤ 执行日志(logs)

  • 数据来源:node_stream kind=step 追加;工具调用、状态变更等也可同步追加一行摘要日志。
  • 追加式纯文本/等宽列表,每行带时间戳。
  • 与思考过程严格分开:thinking 内容不重复进日志。

⑥ 最终输出(output)

  • 数据来源:node_status 终态事件的 output(或回放场景 visit.outputJson)。
  • envelope 解包后渲染(规则见下),始终展开,不随卡片折叠隐藏。
  • 输出主体 markdown 渲染;多字段输出用键值区或 JSON 视图。

AskFor 面板(条件出现)

  • 数据来源:node_stream kind=clarify_request 的 content(JSON 字符串:{"clarify_id", "question", "choices"})。
  • 渲染问题文案 + 选项按钮 + 自定义输入框(多行)+ 提交按钮。
  • 提交后按钮禁用、展示「已提交」。
  • 提交:外部 API 已开放 POST /api/v1/runs/{runId}/resume(v1.2,路径不带 workflowName 段);同一 clarifyId 重复提交返回 409,按幂等约定视为成功(详见 external-workflow-api.md 的「AskFor(用户确认)」)。

envelope 解包规则

节点 output 是输出信封(envelope)结构:

{ "status": 200, "message": "...", "data": { "业务字段": "..." } }

status 是数字:200 成功 / 400 失败。

解包逻辑(伪代码):

function isEnvelope(o) {
  return o && typeof o === 'object' && !Array.isArray(o)
    && ('status' in o) && ('data' in o)   // 必须同时含 status 与 data
}
function unwrapOutput(o) {
  if (!isEnvelope(o)) return o          // 非 envelope:原样展示
  return o.data ?? {}                    // 业务字段在 data 内
}
  • 状态染色:envelope.status === 400(或节点状态 FAILED)时输出区红化,优先展示 envelope.message 的失败原因。
  • 工作流最终输出(workflow_complete.output 与查询结果的 outputs)同样带 envelope 壳(成功时 status: 200、message: "工作流执行成功"),用同样的 unwrapOutput 取 data 渲染。
  • 空数据(data 为空对象且无 message)时不渲染输出区,避免空占位框。

折叠策略

  • 运行中卡片:面板全部展开,实时滚动展示最新内容。
  • 节点完成(收到终态 node_status):卡片自动折叠为头部一行(状态 + 名称 + 耗时),聚焦后续节点。
  • 用户可随时点击头部手动展开/收起;用户手动操作优先于自动折叠。
  • 最终输出不随卡片折叠隐藏(始终可见,或在展开区置顶)。
  • 历史回放(从 visit_recorded / 查询结果接口加载):全部卡片默认折叠,与运行结束后的视觉一致。

回放场景的数据源

展示历史运行(非实时 SSE)时,按以下优先级取数:

  1. AGENTIC 模式 / 需要 Visit 粒度:SSE 重放(Last-Event-ID 从头开始,服务端缓存 25 小时)或内部 Visit 接口;每个 visit_recorded 事件的 visit.logsJson 可还原六面板:
    • thinking → 拼接 type=THINKING 的 message
    • toolCalls → TOOL_CALL / TOOL_RESULT 序列
    • todoList → TODO_UPDATE 的 detail
    • liveStatus → 运行结束场景不需要(隐藏)
  2. DAG 模式:查询结果接口的 nodes[] + 每节点 logsUrl 拉取 {type, message, detail, timestamp} 日志数组,同样按 type 分流到六面板。

节点类型映射

使用一致的节点标签。下方颜色与图标仅为示例,不是必须的样式:

nodeType 标签 颜色 图标建议
userInput 用户输入 #10B981 聊天/输入
llm 大模型 #8B5CF6 闪光
skill 技能 #06B6D4 烧瓶
agent 智能体 #F59E0B 火箭
smartAction 智能操作 #EC4899 魔杖
condition 条件 #F97316 分支
output 输出 #EF4444 出口/输出

状态映射

文案:

  • running 或 RUNNING:执行中
  • success 或 SUCCESS:成功
  • failed 或 FAILED:失败
  • skipped 或 SKIPPED:跳过
  • 未知:未知

若使用 CSS 类,将状态归一化为小写,例如 status-success。

可选的状态色意图(当目标设计体系使用状态色时):

  • 执行中:蓝/紫强调
  • 成功:绿
  • 失败:红
  • 跳过/未知:弱化灰

markdown 渲染要求

思考过程与最终输出必须按 markdown 渲染(本系统智能体输出普遍含 markdown),最低要求:

  • GFM:表格、删除线、任务列表。
  • 代码高亮:highlight.js(建议 /lib/common 子集控制包体),代码块横向滚动不换行,配滚动条样式。
  • XSS 防护:渲染 HTML 前必须过 DOMPurify——LLM 输出与用户输入都可能携带恶意脚本。
  • 数学公式(可选):KaTeX,面向含公式的业务场景。
  • 推荐依赖版本:marked ^12、marked-highlight ^2.2、highlight.js ^11.12、dompurify ^3.2、katex ^0.16。

实时 SSE 视图适配

事件处理总表(6 种事件类型;没有 run_started 事件):

事件 处理
node_status(RUNNING) 按 visitUuid 创建卡片(若不存在),状态置执行中
node_status(终态) 更新卡片状态、绑定 output/error、触发自动折叠
node_stream(8 种 kind) 按 kind 分流到对应面板(见上文六面板)
visit_recorded 补充卡片元数据(nodeLabel/nodeType/iterSeq/fromNodeId/sortOrder),必要时按 sortOrder 重排卡片
planner_decision 可选:渲染 AI 决策链
workflow_complete 运行成功终态;存储最终输出;展示工作空间文件列表与下载入口;停止重连
workflow_error 运行失败终态;展示错误;停止重连

断线重连要求(服务端机制见 external-workflow-api.md):

  • 断开后指数退避重连(1s/2s/4s/8s/16s,5 次),重连请求携带 Last-Event-ID。
  • 5 次失败降级轮询 GET /runs/{runId}(30s/次);SSE 恢复后立即停止。
  • 重连期间顶部横幅提示(如「网络波动,正在恢复执行进度…」)。
  • 补发事件按 visitUuid 幂等合并,不得产生重复卡片。
  • 刷新页面后重连:从头订阅(Last-Event-ID 缺省),服务端补发全部缓存事件,实现完整回放。

终态事件到达后,可再查询持久化结果端点,对齐持久化输出、日志数量与工作空间元数据。

辅助逻辑

实现以下辅助函数或等价物:

  • formatTime(t):兼容 ISO 字符串与 Spring/Java 的数组式日期数组。
  • duration(run):基于 started/completed 计算耗时;按秒、分秒或时分适配展示。
  • parseJson(str):成功返回解析后的对象,否则返回 null。
  • extractThinking(logsJson):解析日志数组,拼接 THINKING.message。
  • parseTodo(content):JSON.parse todo_update 的 content,容错返回条目数组。
  • parseClarify(content):JSON.parse clarify_request 的 content,容错返回 {clarify_id, question, choices}。
  • isEnvelope(o) / unwrapOutput(o):见「envelope 解包规则」。

对实时 SSE 视图,即便日志是以增量方式到达而非来自持久化 JSON,也应保持同样的派生数据形态。

视觉规范

  • 保持信息密度高的运维型 UI。除非目标产品显式使用,否则避免落地页/营销式排版。
  • Visit 卡片按目标设计体系,使用紧凑卡片或面板;不要在卡片里再嵌套卡片(工具调用小卡片属于列表项,不算嵌套卡片)。
  • 圆点、徽标、图标与列表行使用稳定尺寸,避免布局抖动。
  • 长文本要换行或可滚动;输出、思考或日志文本绝不可溢出容器。
  • 无内容的面板不渲染空占位框。
  • 在外部系统中,优先保证含义与工作流清晰度;视觉风格应贴合宿主应用。

验证清单

  • 空、加载中、选中、执行中、成功、失败、无日志等状态均能正确渲染。
  • 六面板齐全:实时状态(结束后隐藏)、思考过程、待办清单、工具调用、执行日志、最终输出。
  • 长思考流不会破坏布局;markdown 表格/代码块/公式渲染正确。
  • 大体量 JSON 输出可读且有边界约束;envelope 解包正确(业务字段取自 data)。
  • 工具日志可展开,且不与思考文本重复。
  • multi-visit:同节点多次访问独立成卡,带「第 N 次访问」标识;不发生合并或覆盖。
  • 节点完成后卡片自动折叠;手动展开/收起正常。
  • 断线重连后卡片不重复;刷新页面可完整回放。
  • AskFor 面板:选项可选、自定义可输入、提交后禁用(409 幂等重复提交视为成功)。
  • 工作空间下载可正常工作,不触发 JSON 拦截器错误。
  • 移动端或窄屏下,状态徽标、运行 ID 或节点头部不会重叠。