diff --git a/service-web/client/src/pages/ai/flow/ElParser.tsx b/service-web/client/src/pages/ai/flow/ElParser.tsx new file mode 100644 index 0000000..9186410 --- /dev/null +++ b/service-web/client/src/pages/ai/flow/ElParser.tsx @@ -0,0 +1,76 @@ +import {type Edge, type Node} from '@xyflow/react' + +export const buildEL = (nodes: Node[], edges: Edge[]): string => { + const nodeMap: Map = new Map() + // 构建邻接列表和内图 + const adjList = new Map() + const inDegree = new Map() + for (const node of nodes) { + nodeMap.set(node.id, node) + adjList.set(node.id, []) + inDegree.set(node.id, 0) + } + for (const edge of edges) { + adjList.get(edge.source)!.push(edge.target) + inDegree.set(edge.target, inDegree.get(edge.target)! + 1) + } + + // Compute levels (longest path from start) + const levelMap = new Map() + + function computeLevel(nodeId: string): number { + if (levelMap.has(nodeId)) return levelMap.get(nodeId)! + const preds = edges.filter(e => e.target === nodeId).map(e => e.source) + const level = preds.length === 0 ? 0 : Math.max(...preds.map(p => computeLevel(p))) + 1 + levelMap.set(nodeId, level) + return level + } + + for (const node of nodes) computeLevel(node.id) + + // Group nodes by level + const maxLevel = Math.max(...Array.from(levelMap.values())) + const levels: string[][] = Array.from({length: maxLevel + 1}, () => []) + for (const node of nodes) levels[levelMap.get(node.id)!].push(node.id) + + const covertNodeFromId = (id: string) => { + let node = nodeMap.get(id)! + return `node("${node.type}").bind("nodeId", ${node.id})` + } + + // Build EL expression + const expressions: string[] = [] + for (let i = 0; i <= maxLevel; i++) { + const nodesAtLevel = levels[i] + if (nodesAtLevel.length === 0) continue + + // 识别从这个级别开始的串行链 + const serialChains: string[] = [] + for (const nodeId of nodesAtLevel) { + let chain = [nodeId] + let current = nodeId + while (adjList.get(current)?.length === 1) { + const next = adjList.get(current)![0] + if (inDegree.get(next) === 1 && levelMap.get(next) === i + chain.length) { + chain.push(next) + current = next + } else break + } + if (chain.length > 1) { + serialChains.push(`THEN(${chain.map(id => covertNodeFromId(id)).join(',')})`) + // Remove processed nodes from their levels + for (let j = 1; j < chain.length; j++) { + const level = levelMap.get(chain[j])! + levels[level] = levels[level].filter(n => n !== chain[j]) + } + } else { + serialChains.push(covertNodeFromId(nodeId)) + } + } + + // Combine chains or nodes at this level + expressions.push(serialChains.length > 1 ? `WHEN(${serialChains.join(', ')})` : serialChains[0]) + } + + return `THEN(${expressions.join(',')})` +} \ No newline at end of file