# 运行记录 UI 参考 当需要构建展示 agent-management 工作流执行进度或历史运行详情的前端页面/组件时,使用本参考。保留其内容、状态流转和信息层级;视觉设计则按目标产品自身风格适配。 **硬性要求**:业务方必须完整呈现「实时状态、思考过程、工具调用、待办清单、执行日志、最终结果」六类内容,交互逻辑(折叠策略、multi-visit 渲染、AskFor 处理)与本系统一致。可直接复用的 JS/Vue 实现见 `js-implementation.md`;接口契约见 `external-workflow-api.md`。 如果用户提供了已部署的参考页面,可将其作为可选的内容与交互参考,而不是必须照搬的样式来源。 ## 页面结构 采用面向执行的视图。对于管理/历史类页面,左右两栏的运行记录布局是较优的默认选择,但嵌入式或面向特定任务的集成不强制使用。 - 页面标题:`运行记录` 或目标应用中的等价文案。 - 浏览历史运行时需有运行选择区。 - 选中或当前运行的详情区。 - 没有运行或未选中运行时,展示清晰的空状态。 - 初始加载运行数据时,居中展示 loading spinner。 集成到目标应用时,遵循其既有约定,例如: - API 封装或服务模块约定。 - 请求辅助封装、鉴权与错误处理约定。 - 既有的组件库与图标集。 - 既有的路由、状态管理、间距、排版与状态样式。 除非用户明确要求近似视觉,否则不要照搬原运行记录页样式。 ## 工作流与运行选择 展示运行历史时,按工作流分组有助于用户查找历史执行: - 工作流头部:折叠箭头、工作流图标、工作流名称和运行数量。 - 工作流标题下方可选地展示工作流标签(小 chip)。 - 运行条目: - 状态圆点 - 短运行 ID,例如 `#a3f9b2c1` - 状态徽标 - 启动时间 - 完成时展示耗时 - 删除操作仅出现在管理/历史页面,只读嵌入式视图不展示 常用交互: - 工作流分组可折叠,默认展开。 - 点击运行会加载并选中其详情。 - 当前运行有明显的选中态。 对于纯实时执行视图,完整的历史列表是可选的;展示当前工作流/运行身份、实时状态与节点推进即可。 ## 运行详情 运行详情头部: - 状态圆点 - `运行 #` - 状态徽标 - 启动时间 - 完成时间(如有) - 耗时(如有) - 运行级错误块(如存在) 随后**按 `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)结构: ```json { "status": 200, "message": "...", "data": { "业务字段": "..." } } ``` `status` 是**数字**:`200` 成功 / `400` 失败。 解包逻辑(伪代码): ```js 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 或节点头部不会重叠。