/** * 工作流节点共享工具 * * 提取自 WorkflowList.vue 与 WorkflowEditor.vue 中重复的节点配置: * - 颜色映射、类型标签、默认数据 * - parseNodes / thumbnail 渲染辅助 */ // ========== 节点类型颜色映射 ========== export const nodeTypeColors = { userInput: '#10B981', llm: '#8B5CF6', skill: '#06B6D4', agent: '#F59E0B', smartAction: '#EC4899', condition: '#F97316', output: '#EF4444', } // ========== 节点类型中文标签 ========== export const nodeTypeLabels = { userInput: '用户输入', llm: '大模型处理', skill: '技能', agent: '智能体', smartAction: '智能操作', condition: '条件分支', output: '输出', } /** * 解析 graphData(字符串或对象)为节点摘要数组 * @param {string|object} graphData * @returns {Array<{type:string, color:string, label:string}>} */ export function parseNodes(graphData) { if (!graphData) return [] try { const data = typeof graphData === 'string' ? JSON.parse(graphData) : graphData return (data.nodes || []).map(n => ({ type: n.type, color: nodeTypeColors[n.type] || '#64748B', label: n.data?.label || n.label || n.type, })) } catch { return [] } } /** * 解析 graphData 为带位置信息的图形结构(用于缩略图渲染) * * 返回的 nodes 中 rx/ry 为相对坐标 [0,1]: * - 单点或共线时自动居中(避免除零与压扁) * - 由调用方(如 NodeThumbnail)映射到具体 SVG 视口 * * @param {string|object} graphData * @returns {{ * hasContent: boolean, * nodes: Array<{id:string, type:string, color:string, label:string, rx:number, ry:number}>, * edges: Array<{source:object, target:object}> * }} */ export function parseGraph(graphData) { if (!graphData) return { hasContent: false, nodes: [], edges: [] } try { const data = typeof graphData === 'string' ? JSON.parse(graphData) : graphData const rawNodes = data.nodes || [] const rawEdges = data.edges || [] if (!rawNodes.length) return { hasContent: false, nodes: [], edges: [] } const xs = rawNodes.map(n => n.position?.x ?? 0) const ys = rawNodes.map(n => n.position?.y ?? 0) const minX = Math.min(...xs), maxX = Math.max(...xs) const minY = Math.min(...ys), maxY = Math.max(...ys) const spanX = maxX - minX const spanY = maxY - minY const nodes = rawNodes.map(n => { const nx = n.position?.x ?? 0 const ny = n.position?.y ?? 0 return { id: String(n.id ?? ''), type: n.type, color: nodeTypeColors[n.type] || '#64748B', label: n.data?.label || n.label || n.type, rx: spanX > 0 ? (nx - minX) / spanX : 0.5, ry: spanY > 0 ? (ny - minY) / spanY : 0.5, } }) const nodeMap = new Map(nodes.map(n => [n.id, n])) const edges = (rawEdges || []) .map(e => ({ source: nodeMap.get(String(e.source)), target: nodeMap.get(String(e.target)), })) .filter(e => e.source && e.target) return { hasContent: true, nodes, edges } } catch { return { hasContent: false, nodes: [], edges: [] } } } /** * 根据节点类型返回默认 data 字段(与 WorkflowEditor.getDefaultData 保持一致) */ export function getDefaultData(type) { switch (type) { case 'userInput': return { label: '用户输入', variables: [] } case 'llm': return { label: '大模型处理', modelId: null, systemPrompt: '', userPrompt: '', inputs: [], outputs: [{ name: 'result', label: 'LLM 输出', type: 'string', description: '', required: false }], failStrategy: 'abort', } case 'agent': return { label: '智能体', agentId: '', agentName: '', modelId: null, failStrategy: 'abort' } case 'skill': return { label: '技能', skillId: '', skillName: '', modelId: null, failStrategy: 'abort' } case 'smartAction': return { label: '智能操作', actionPrompt: '', modelId: null, inputs: [], outputs: [{ name: 'result', label: '操作结果', type: 'string', description: '', required: false }], failStrategy: 'abort', } case 'condition': return { label: '条件分支', conditions: [{ type: 'IF', expression: '' }] } case 'output': return { label: '输出', fields: [{ name: 'result', label: '输出结果', type: 'string', description: '', required: false }] } default: return { label: '节点' } } }