/** * Visit 链辅助工具(DAG / AGENTIC 模式统一)。 * *
WorkflowEditor / RunHistory 两处共用:实时运行 + 历史回放均渲染同一组 Visit 卡片, * 字段解析逻辑(outputJson / variablesJson / decisionJson / logsJson 反序列化、未知节点类型兜底) * 抽到此工具,避免两份实现漂移。
* *对应设计文档 {@code docs/plans/agentic-workflow-plan.md} §3 数据结构。
*/ import { unwrapNodeOutput } from '../composables/useWorkflowRunner' import { extractStreamingThinking, extractToolHistory, extractLatestTodos, extractNonThinkingLogs } from './runResultReplay' /** * 未知节点类型的兜底配置(灰色),避免 nodeTypeInfo 查不到时模板渲染崩。 * 各页面在 nodeTypeInfo 上层做 lookup 时使用。 */ export const UNKNOWN_NODE_TYPE_INFO = Object.freeze({ color: '#6b7280', typeLabel: '未知', icon: null }) /** * Visit.outputJson 是节点输出 envelope 序列化后的 JSON 字符串; * unwrapNodeOutput 期望对象,故先 parse 再 unwrap。 * * @param {string|object|null} jsonStr * @returns {object|null} unwrap 后的 data 字段;解析失败或为空返回 null */ export function parseVisitOutput(jsonStr) { if (!jsonStr) return null try { const obj = typeof jsonStr === 'string' ? JSON.parse(jsonStr) : jsonStr return unwrapNodeOutput(obj) } catch { return null } } /** * Visit.variablesJson 是工作区变量快照(含初始输入 + 此前所有访问节点输出, * 按 nodeId__varName 形式存放)。 * * @param {string|object|null} jsonStr * @returns {Record兼容字段:未来若同时记录 Planner 决策(nextNodeId / reason),扩展本函数即可。
* * @param {string|object|null} jsonStr * @returns {{offCanvas: boolean, nextNodeId: string|null, reason: string}|null} */ export function parseVisitDecision(jsonStr) { if (!jsonStr) return null try { const obj = typeof jsonStr === 'string' ? JSON.parse(jsonStr) : jsonStr if (!obj || typeof obj !== 'object') return null return { offCanvas: obj.offCanvas === true, nextNodeId: obj.nextNodeId || obj.next || null, reason: obj.reason || '' } } catch { return null } } /** * 把后端 WorkflowRunVisit 还原为前端 Visit 卡片格式(含 streamingThinking / toolHistory / todos / logs)。 * *用于 RunHistory 页面回放,与 WorkflowEditor 实时态 Visit 字段一致: * - 元数据:visitUuid / nodeId / nodeType / nodeLabel / iterSeq / fromVisitUuid / fromNodeId / sortOrder * - 结果:status / output / logs / contextSnapshot / variablesJson / outputJson / errorMsg * - 流式回放:从 logsJson 还原 streamingThinking / toolHistory / todos * - 已完成态:currentStatus / currentTool 留空
* * @param {Object} visit 后端 WorkflowRunVisit(含 outputJson / variablesJson / logsJson / decisionJson) * @param {{color:string,typeLabel:string,icon?:any}} info 节点类型信息(由调用方查 nodeTypeInfo 后传入) * @returns {Object} 前端 Visit 卡片格式 */ export function buildReplayVisit(visit, info) { const v = visit || {} const logsJson = v.logsJson || '' const decision = parseVisitDecision(v.decisionJson) return { visitUuid: v.visitUuid || null, nodeId: v.nodeId, nodeType: v.nodeType, nodeLabel: v.nodeLabel || v.nodeId || '节点', iterSeq: v.iterSeq || 1, fromVisitUuid: v.fromVisitUuid || null, fromNodeId: v.fromNodeId || null, // offCanvas:仅当 decisionJson 显式标记为 true 才为 true(按画布走或起始节点均为 false) offCanvas: decision?.offCanvas === true, sortOrder: v.sortOrder ?? 0, status: (v.status || '').toLowerCase(), errorMsg: v.errorMsg || null, // output:保留 envelope(envelope 在 useWorkflowRunner.unwrapNodeOutput 中解封) output: v.outputJson ? safeParse(v.outputJson) : null, variablesJson: v.variablesJson || '{}', outputJson: v.outputJson || null, contextSnapshot: null, // Visit 不存 contextSnapshot;上下文展示回退到 variablesJson // 流式回放:从 logsJson 还原 streamingThinking: extractStreamingThinking(logsJson), toolHistory: extractToolHistory(logsJson), todos: extractLatestTodos(logsJson), logs: extractNonThinkingLogs(logsJson), // 已完成态无实时 currentStatus: '', currentTool: null, // 视觉信息(由调用方注入) color: info?.color || '#6b7280', typeLabel: info?.typeLabel || '未知' } } function safeParse(str) { try { return JSON.parse(str) } catch { return null } }