visitChain.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. /**
  2. * Visit 链辅助工具(DAG / AGENTIC 模式统一)。
  3. *
  4. * <p>WorkflowEditor / RunHistory 两处共用:实时运行 + 历史回放均渲染同一组 Visit 卡片,
  5. * 字段解析逻辑(outputJson / variablesJson / decisionJson / logsJson 反序列化、未知节点类型兜底)
  6. * 抽到此工具,避免两份实现漂移。</p>
  7. *
  8. * <p>对应设计文档 {@code docs/plans/agentic-workflow-plan.md} §3 数据结构。</p>
  9. */
  10. import { unwrapNodeOutput } from '../composables/useWorkflowRunner'
  11. import {
  12. extractStreamingThinking,
  13. extractToolHistory,
  14. extractLatestTodos,
  15. extractNonThinkingLogs
  16. } from './runResultReplay'
  17. /**
  18. * 未知节点类型的兜底配置(灰色),避免 nodeTypeInfo 查不到时模板渲染崩。
  19. * 各页面在 nodeTypeInfo 上层做 lookup 时使用。
  20. */
  21. export const UNKNOWN_NODE_TYPE_INFO = Object.freeze({
  22. color: '#6b7280',
  23. typeLabel: '未知',
  24. icon: null
  25. })
  26. /**
  27. * Visit.outputJson 是节点输出 envelope 序列化后的 JSON 字符串;
  28. * unwrapNodeOutput 期望对象,故先 parse 再 unwrap。
  29. *
  30. * @param {string|object|null} jsonStr
  31. * @returns {object|null} unwrap 后的 data 字段;解析失败或为空返回 null
  32. */
  33. export function parseVisitOutput(jsonStr) {
  34. if (!jsonStr) return null
  35. try {
  36. const obj = typeof jsonStr === 'string' ? JSON.parse(jsonStr) : jsonStr
  37. return unwrapNodeOutput(obj)
  38. } catch { return null }
  39. }
  40. /**
  41. * Visit.variablesJson 是工作区变量快照(含初始输入 + 此前所有访问节点输出,
  42. * 按 nodeId__varName 形式存放)。
  43. *
  44. * @param {string|object|null} jsonStr
  45. * @returns {Record<string, any>} 解析失败或非对象返回空对象
  46. */
  47. export function parseVisitVariables(jsonStr) {
  48. if (!jsonStr) return {}
  49. try {
  50. const obj = typeof jsonStr === 'string' ? JSON.parse(jsonStr) : jsonStr
  51. return obj && typeof obj === 'object' ? obj : {}
  52. } catch { return {} }
  53. }
  54. /**
  55. * Visit.decisionJson 是跳转元数据。当前仅记录"非画布跳转"标志:
  56. * - null / 空 → 按画布连线跳转(与 DAG 一致,前端不展示"来自 X"徽章)
  57. * - {"offCanvas":true} → AGENTIC 自由跳转(前端展示"来自 X"徽章)
  58. *
  59. * <p>兼容字段:未来若同时记录 Planner 决策(nextNodeId / reason),扩展本函数即可。</p>
  60. *
  61. * @param {string|object|null} jsonStr
  62. * @returns {{offCanvas: boolean, nextNodeId: string|null, reason: string}|null}
  63. */
  64. export function parseVisitDecision(jsonStr) {
  65. if (!jsonStr) return null
  66. try {
  67. const obj = typeof jsonStr === 'string' ? JSON.parse(jsonStr) : jsonStr
  68. if (!obj || typeof obj !== 'object') return null
  69. return {
  70. offCanvas: obj.offCanvas === true,
  71. nextNodeId: obj.nextNodeId || obj.next || null,
  72. reason: obj.reason || ''
  73. }
  74. } catch { return null }
  75. }
  76. /**
  77. * 把后端 WorkflowRunVisit 还原为前端 Visit 卡片格式(含 streamingThinking / toolHistory / todos / logs)。
  78. *
  79. * <p>用于 RunHistory 页面回放,与 WorkflowEditor 实时态 Visit 字段一致:
  80. * - 元数据:visitUuid / nodeId / nodeType / nodeLabel / iterSeq / fromVisitUuid / fromNodeId / sortOrder
  81. * - 结果:status / output / logs / contextSnapshot / variablesJson / outputJson / errorMsg
  82. * - 流式回放:从 logsJson 还原 streamingThinking / toolHistory / todos
  83. * - 已完成态:currentStatus / currentTool 留空</p>
  84. *
  85. * @param {Object} visit 后端 WorkflowRunVisit(含 outputJson / variablesJson / logsJson / decisionJson)
  86. * @param {{color:string,typeLabel:string,icon?:any}} info 节点类型信息(由调用方查 nodeTypeInfo 后传入)
  87. * @returns {Object} 前端 Visit 卡片格式
  88. */
  89. export function buildReplayVisit(visit, info) {
  90. const v = visit || {}
  91. const logsJson = v.logsJson || ''
  92. const decision = parseVisitDecision(v.decisionJson)
  93. return {
  94. visitUuid: v.visitUuid || null,
  95. nodeId: v.nodeId,
  96. nodeType: v.nodeType,
  97. nodeLabel: v.nodeLabel || v.nodeId || '节点',
  98. iterSeq: v.iterSeq || 1,
  99. fromVisitUuid: v.fromVisitUuid || null,
  100. fromNodeId: v.fromNodeId || null,
  101. // offCanvas:仅当 decisionJson 显式标记为 true 才为 true(按画布走或起始节点均为 false)
  102. offCanvas: decision?.offCanvas === true,
  103. sortOrder: v.sortOrder ?? 0,
  104. status: (v.status || '').toLowerCase(),
  105. errorMsg: v.errorMsg || null,
  106. // output:保留 envelope(envelope 在 useWorkflowRunner.unwrapNodeOutput 中解封)
  107. output: v.outputJson ? safeParse(v.outputJson) : null,
  108. variablesJson: v.variablesJson || '{}',
  109. outputJson: v.outputJson || null,
  110. contextSnapshot: null, // Visit 不存 contextSnapshot;上下文展示回退到 variablesJson
  111. // 流式回放:从 logsJson 还原
  112. streamingThinking: extractStreamingThinking(logsJson),
  113. toolHistory: extractToolHistory(logsJson),
  114. todos: extractLatestTodos(logsJson),
  115. logs: extractNonThinkingLogs(logsJson),
  116. // 已完成态无实时
  117. currentStatus: '',
  118. currentTool: null,
  119. // 视觉信息(由调用方注入)
  120. color: info?.color || '#6b7280',
  121. typeLabel: info?.typeLabel || '未知'
  122. }
  123. }
  124. function safeParse(str) {
  125. try { return JSON.parse(str) } catch { return null }
  126. }