workflowNode.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /**
  2. * 工作流节点共享工具
  3. *
  4. * 提取自 WorkflowList.vue 与 WorkflowEditor.vue 中重复的节点配置:
  5. * - 颜色映射、类型标签、默认数据
  6. * - parseNodes / thumbnail 渲染辅助
  7. */
  8. // ========== 节点类型颜色映射 ==========
  9. export const nodeTypeColors = {
  10. userInput: '#10B981',
  11. llm: '#8B5CF6',
  12. skill: '#06B6D4',
  13. agent: '#F59E0B',
  14. smartAction: '#EC4899',
  15. condition: '#F97316',
  16. output: '#EF4444',
  17. }
  18. // ========== 节点类型中文标签 ==========
  19. export const nodeTypeLabels = {
  20. userInput: '用户输入',
  21. llm: '大模型处理',
  22. skill: '技能',
  23. agent: '智能体',
  24. smartAction: '智能操作',
  25. condition: '条件分支',
  26. output: '输出',
  27. }
  28. /**
  29. * 解析 graphData(字符串或对象)为节点摘要数组
  30. * @param {string|object} graphData
  31. * @returns {Array<{type:string, color:string, label:string}>}
  32. */
  33. export function parseNodes(graphData) {
  34. if (!graphData) return []
  35. try {
  36. const data = typeof graphData === 'string' ? JSON.parse(graphData) : graphData
  37. return (data.nodes || []).map(n => ({
  38. type: n.type,
  39. color: nodeTypeColors[n.type] || '#64748B',
  40. label: n.data?.label || n.label || n.type,
  41. }))
  42. } catch {
  43. return []
  44. }
  45. }
  46. /**
  47. * 解析 graphData 为带位置信息的图形结构(用于缩略图渲染)
  48. *
  49. * 返回的 nodes 中 rx/ry 为相对坐标 [0,1]:
  50. * - 单点或共线时自动居中(避免除零与压扁)
  51. * - 由调用方(如 NodeThumbnail)映射到具体 SVG 视口
  52. *
  53. * @param {string|object} graphData
  54. * @returns {{
  55. * hasContent: boolean,
  56. * nodes: Array<{id:string, type:string, color:string, label:string, rx:number, ry:number}>,
  57. * edges: Array<{source:object, target:object}>
  58. * }}
  59. */
  60. export function parseGraph(graphData) {
  61. if (!graphData) return { hasContent: false, nodes: [], edges: [] }
  62. try {
  63. const data = typeof graphData === 'string' ? JSON.parse(graphData) : graphData
  64. const rawNodes = data.nodes || []
  65. const rawEdges = data.edges || []
  66. if (!rawNodes.length) return { hasContent: false, nodes: [], edges: [] }
  67. const xs = rawNodes.map(n => n.position?.x ?? 0)
  68. const ys = rawNodes.map(n => n.position?.y ?? 0)
  69. const minX = Math.min(...xs), maxX = Math.max(...xs)
  70. const minY = Math.min(...ys), maxY = Math.max(...ys)
  71. const spanX = maxX - minX
  72. const spanY = maxY - minY
  73. const nodes = rawNodes.map(n => {
  74. const nx = n.position?.x ?? 0
  75. const ny = n.position?.y ?? 0
  76. return {
  77. id: String(n.id ?? ''),
  78. type: n.type,
  79. color: nodeTypeColors[n.type] || '#64748B',
  80. label: n.data?.label || n.label || n.type,
  81. rx: spanX > 0 ? (nx - minX) / spanX : 0.5,
  82. ry: spanY > 0 ? (ny - minY) / spanY : 0.5,
  83. }
  84. })
  85. const nodeMap = new Map(nodes.map(n => [n.id, n]))
  86. const edges = (rawEdges || [])
  87. .map(e => ({
  88. source: nodeMap.get(String(e.source)),
  89. target: nodeMap.get(String(e.target)),
  90. }))
  91. .filter(e => e.source && e.target)
  92. return { hasContent: true, nodes, edges }
  93. } catch {
  94. return { hasContent: false, nodes: [], edges: [] }
  95. }
  96. }
  97. /**
  98. * 根据节点类型返回默认 data 字段(与 WorkflowEditor.getDefaultData 保持一致)
  99. */
  100. export function getDefaultData(type) {
  101. switch (type) {
  102. case 'userInput':
  103. return { label: '用户输入', variables: [] }
  104. case 'llm':
  105. return {
  106. label: '大模型处理',
  107. modelId: null,
  108. systemPrompt: '',
  109. userPrompt: '',
  110. inputs: [],
  111. outputs: [{ name: 'result', label: 'LLM 输出', type: 'string', description: '', required: false }],
  112. failStrategy: 'abort',
  113. }
  114. case 'agent':
  115. return { label: '智能体', agentId: '', agentName: '', modelId: null, failStrategy: 'abort' }
  116. case 'skill':
  117. return { label: '技能', skillId: '', skillName: '', modelId: null, failStrategy: 'abort' }
  118. case 'smartAction':
  119. return {
  120. label: '智能操作',
  121. actionPrompt: '',
  122. modelId: null,
  123. inputs: [],
  124. outputs: [{ name: 'result', label: '操作结果', type: 'string', description: '', required: false }],
  125. failStrategy: 'abort',
  126. }
  127. case 'condition':
  128. return { label: '条件分支', conditions: [{ type: 'IF', expression: '' }] }
  129. case 'output':
  130. return { label: '输出', fields: [{ name: 'result', label: '输出结果', type: 'string', description: '', required: false }] }
  131. default:
  132. return { label: '节点' }
  133. }
  134. }