utils.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. import {
  2. Position,
  3. getConnectedEdges,
  4. getIncomers,
  5. getOutgoers,
  6. } from 'reactflow'
  7. import dagre from '@dagrejs/dagre'
  8. import { v4 as uuid4 } from 'uuid'
  9. import {
  10. cloneDeep,
  11. groupBy,
  12. isEqual,
  13. uniqBy,
  14. } from 'lodash-es'
  15. import type {
  16. Edge,
  17. InputVar,
  18. Node,
  19. ToolWithProvider,
  20. ValueSelector,
  21. } from './types'
  22. import { BlockEnum, ErrorHandleMode } from './types'
  23. import {
  24. CUSTOM_NODE,
  25. ITERATION_CHILDREN_Z_INDEX,
  26. ITERATION_NODE_Z_INDEX,
  27. NODE_WIDTH_X_OFFSET,
  28. START_INITIAL_POSITION,
  29. } from './constants'
  30. import { CUSTOM_ITERATION_START_NODE } from './nodes/iteration-start/constants'
  31. import type { QuestionClassifierNodeType } from './nodes/question-classifier/types'
  32. import type { IfElseNodeType } from './nodes/if-else/types'
  33. import { branchNameCorrect } from './nodes/if-else/utils'
  34. import type { ToolNodeType } from './nodes/tool/types'
  35. import type { IterationNodeType } from './nodes/iteration/types'
  36. import { CollectionType } from '@/app/components/tools/types'
  37. import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
  38. const WHITE = 'WHITE'
  39. const GRAY = 'GRAY'
  40. const BLACK = 'BLACK'
  41. const isCyclicUtil = (nodeId: string, color: Record<string, string>, adjList: Record<string, string[]>, stack: string[]) => {
  42. color[nodeId] = GRAY
  43. stack.push(nodeId)
  44. for (let i = 0; i < adjList[nodeId].length; ++i) {
  45. const childId = adjList[nodeId][i]
  46. if (color[childId] === GRAY) {
  47. stack.push(childId)
  48. return true
  49. }
  50. if (color[childId] === WHITE && isCyclicUtil(childId, color, adjList, stack))
  51. return true
  52. }
  53. color[nodeId] = BLACK
  54. if (stack.length > 0 && stack[stack.length - 1] === nodeId)
  55. stack.pop()
  56. return false
  57. }
  58. const getCycleEdges = (nodes: Node[], edges: Edge[]) => {
  59. const adjList: Record<string, string[]> = {}
  60. const color: Record<string, string> = {}
  61. const stack: string[] = []
  62. for (const node of nodes) {
  63. color[node.id] = WHITE
  64. adjList[node.id] = []
  65. }
  66. for (const edge of edges)
  67. adjList[edge.source]?.push(edge.target)
  68. for (let i = 0; i < nodes.length; i++) {
  69. if (color[nodes[i].id] === WHITE)
  70. isCyclicUtil(nodes[i].id, color, adjList, stack)
  71. }
  72. const cycleEdges = []
  73. if (stack.length > 0) {
  74. const cycleNodes = new Set(stack)
  75. for (const edge of edges) {
  76. if (cycleNodes.has(edge.source) && cycleNodes.has(edge.target))
  77. cycleEdges.push(edge)
  78. }
  79. }
  80. return cycleEdges
  81. }
  82. export function getIterationStartNode(iterationId: string): Node {
  83. return generateNewNode({
  84. id: `${iterationId}start`,
  85. type: CUSTOM_ITERATION_START_NODE,
  86. data: {
  87. title: '',
  88. desc: '',
  89. type: BlockEnum.IterationStart,
  90. isInIteration: true,
  91. },
  92. position: {
  93. x: 24,
  94. y: 68,
  95. },
  96. zIndex: ITERATION_CHILDREN_Z_INDEX,
  97. parentId: iterationId,
  98. selectable: false,
  99. draggable: false,
  100. }).newNode
  101. }
  102. export function generateNewNode({ data, position, id, zIndex, type, ...rest }: Omit<Node, 'id'> & { id?: string }): {
  103. newNode: Node
  104. newIterationStartNode?: Node
  105. } {
  106. const newNode = {
  107. id: id || `${Date.now()}`,
  108. type: type || CUSTOM_NODE,
  109. data,
  110. position,
  111. targetPosition: Position.Left,
  112. sourcePosition: Position.Right,
  113. zIndex: data.type === BlockEnum.Iteration ? ITERATION_NODE_Z_INDEX : zIndex,
  114. ...rest,
  115. } as Node
  116. if (data.type === BlockEnum.Iteration) {
  117. const newIterationStartNode = getIterationStartNode(newNode.id);
  118. (newNode.data as IterationNodeType).start_node_id = newIterationStartNode.id;
  119. (newNode.data as IterationNodeType)._children = [newIterationStartNode.id]
  120. return {
  121. newNode,
  122. newIterationStartNode,
  123. }
  124. }
  125. return {
  126. newNode,
  127. }
  128. }
  129. export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
  130. const hasIterationNode = nodes.some(node => node.data.type === BlockEnum.Iteration)
  131. if (!hasIterationNode) {
  132. return {
  133. nodes,
  134. edges,
  135. }
  136. }
  137. const nodesMap = nodes.reduce((prev, next) => {
  138. prev[next.id] = next
  139. return prev
  140. }, {} as Record<string, Node>)
  141. const iterationNodesWithStartNode = []
  142. const iterationNodesWithoutStartNode = []
  143. for (let i = 0; i < nodes.length; i++) {
  144. const currentNode = nodes[i] as Node<IterationNodeType>
  145. if (currentNode.data.type === BlockEnum.Iteration) {
  146. if (currentNode.data.start_node_id) {
  147. if (nodesMap[currentNode.data.start_node_id]?.type !== CUSTOM_ITERATION_START_NODE)
  148. iterationNodesWithStartNode.push(currentNode)
  149. }
  150. else {
  151. iterationNodesWithoutStartNode.push(currentNode)
  152. }
  153. }
  154. }
  155. const newIterationStartNodesMap = {} as Record<string, Node>
  156. const newIterationStartNodes = [...iterationNodesWithStartNode, ...iterationNodesWithoutStartNode].map((iterationNode, index) => {
  157. const newNode = getIterationStartNode(iterationNode.id)
  158. newNode.id = newNode.id + index
  159. newIterationStartNodesMap[iterationNode.id] = newNode
  160. return newNode
  161. })
  162. const newEdges = iterationNodesWithStartNode.map((iterationNode) => {
  163. const newNode = newIterationStartNodesMap[iterationNode.id]
  164. const startNode = nodesMap[iterationNode.data.start_node_id]
  165. const source = newNode.id
  166. const sourceHandle = 'source'
  167. const target = startNode.id
  168. const targetHandle = 'target'
  169. return {
  170. id: `${source}-${sourceHandle}-${target}-${targetHandle}`,
  171. type: 'custom',
  172. source,
  173. sourceHandle,
  174. target,
  175. targetHandle,
  176. data: {
  177. sourceType: newNode.data.type,
  178. targetType: startNode.data.type,
  179. isInIteration: true,
  180. iteration_id: startNode.parentId,
  181. _connectedNodeIsSelected: true,
  182. },
  183. zIndex: ITERATION_CHILDREN_Z_INDEX,
  184. }
  185. })
  186. nodes.forEach((node) => {
  187. if (node.data.type === BlockEnum.Iteration && newIterationStartNodesMap[node.id])
  188. (node.data as IterationNodeType).start_node_id = newIterationStartNodesMap[node.id].id
  189. })
  190. return {
  191. nodes: [...nodes, ...newIterationStartNodes],
  192. edges: [...edges, ...newEdges],
  193. }
  194. }
  195. export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
  196. const { nodes, edges } = preprocessNodesAndEdges(cloneDeep(originNodes), cloneDeep(originEdges))
  197. const firstNode = nodes[0]
  198. if (!firstNode?.position) {
  199. nodes.forEach((node, index) => {
  200. node.position = {
  201. x: START_INITIAL_POSITION.x + index * NODE_WIDTH_X_OFFSET,
  202. y: START_INITIAL_POSITION.y,
  203. }
  204. })
  205. }
  206. const iterationNodeMap = nodes.reduce((acc, node) => {
  207. if (node.parentId) {
  208. if (acc[node.parentId])
  209. acc[node.parentId].push(node.id)
  210. else
  211. acc[node.parentId] = [node.id]
  212. }
  213. return acc
  214. }, {} as Record<string, string[]>)
  215. return nodes.map((node) => {
  216. if (!node.type)
  217. node.type = CUSTOM_NODE
  218. const connectedEdges = getConnectedEdges([node], edges)
  219. node.data._connectedSourceHandleIds = connectedEdges.filter(edge => edge.source === node.id).map(edge => edge.sourceHandle || 'source')
  220. node.data._connectedTargetHandleIds = connectedEdges.filter(edge => edge.target === node.id).map(edge => edge.targetHandle || 'target')
  221. if (node.data.type === BlockEnum.IfElse) {
  222. const nodeData = node.data as IfElseNodeType
  223. if (!nodeData.cases && nodeData.logical_operator && nodeData.conditions) {
  224. (node.data as IfElseNodeType).cases = [
  225. {
  226. case_id: 'true',
  227. logical_operator: nodeData.logical_operator,
  228. conditions: nodeData.conditions,
  229. },
  230. ]
  231. }
  232. node.data._targetBranches = branchNameCorrect([
  233. ...(node.data as IfElseNodeType).cases.map(item => ({ id: item.case_id, name: '' })),
  234. { id: 'false', name: '' },
  235. ])
  236. }
  237. if (node.data.type === BlockEnum.QuestionClassifier) {
  238. node.data._targetBranches = (node.data as QuestionClassifierNodeType).classes.map((topic) => {
  239. return topic
  240. })
  241. }
  242. if (node.data.type === BlockEnum.Iteration) {
  243. const iterationNodeData = node.data as IterationNodeType
  244. iterationNodeData._children = iterationNodeMap[node.id] || []
  245. iterationNodeData.is_parallel = iterationNodeData.is_parallel || false
  246. iterationNodeData.parallel_nums = iterationNodeData.parallel_nums || 10
  247. iterationNodeData.error_handle_mode = iterationNodeData.error_handle_mode || ErrorHandleMode.Terminated
  248. }
  249. return node
  250. })
  251. }
  252. export const initialEdges = (originEdges: Edge[], originNodes: Node[]) => {
  253. const { nodes, edges } = preprocessNodesAndEdges(cloneDeep(originNodes), cloneDeep(originEdges))
  254. let selectedNode: Node | null = null
  255. const nodesMap = nodes.reduce((acc, node) => {
  256. acc[node.id] = node
  257. if (node.data?.selected)
  258. selectedNode = node
  259. return acc
  260. }, {} as Record<string, Node>)
  261. const cycleEdges = getCycleEdges(nodes, edges)
  262. return edges.filter((edge) => {
  263. return !cycleEdges.find(cycEdge => cycEdge.source === edge.source && cycEdge.target === edge.target)
  264. }).map((edge) => {
  265. edge.type = 'custom'
  266. if (!edge.sourceHandle)
  267. edge.sourceHandle = 'source'
  268. if (!edge.targetHandle)
  269. edge.targetHandle = 'target'
  270. if (!edge.data?.sourceType && edge.source && nodesMap[edge.source]) {
  271. edge.data = {
  272. ...edge.data,
  273. sourceType: nodesMap[edge.source].data.type!,
  274. } as any
  275. }
  276. if (!edge.data?.targetType && edge.target && nodesMap[edge.target]) {
  277. edge.data = {
  278. ...edge.data,
  279. targetType: nodesMap[edge.target].data.type!,
  280. } as any
  281. }
  282. if (selectedNode) {
  283. edge.data = {
  284. ...edge.data,
  285. _connectedNodeIsSelected: edge.source === selectedNode.id || edge.target === selectedNode.id,
  286. } as any
  287. }
  288. return edge
  289. })
  290. }
  291. export const getLayoutByDagre = (originNodes: Node[], originEdges: Edge[]) => {
  292. const dagreGraph = new dagre.graphlib.Graph()
  293. dagreGraph.setDefaultEdgeLabel(() => ({}))
  294. const nodes = cloneDeep(originNodes).filter(node => !node.parentId && node.type === CUSTOM_NODE)
  295. const edges = cloneDeep(originEdges).filter(edge => !edge.data?.isInIteration)
  296. dagreGraph.setGraph({
  297. rankdir: 'LR',
  298. align: 'UL',
  299. nodesep: 40,
  300. ranksep: 60,
  301. ranker: 'tight-tree',
  302. marginx: 30,
  303. marginy: 200,
  304. })
  305. nodes.forEach((node) => {
  306. dagreGraph.setNode(node.id, {
  307. width: node.width!,
  308. height: node.height!,
  309. })
  310. })
  311. edges.forEach((edge) => {
  312. dagreGraph.setEdge(edge.source, edge.target)
  313. })
  314. dagre.layout(dagreGraph)
  315. return dagreGraph
  316. }
  317. export const canRunBySingle = (nodeType: BlockEnum) => {
  318. return nodeType === BlockEnum.LLM
  319. || nodeType === BlockEnum.KnowledgeRetrieval
  320. || nodeType === BlockEnum.Code
  321. || nodeType === BlockEnum.TemplateTransform
  322. || nodeType === BlockEnum.QuestionClassifier
  323. || nodeType === BlockEnum.HttpRequest
  324. || nodeType === BlockEnum.Tool
  325. || nodeType === BlockEnum.ParameterExtractor
  326. || nodeType === BlockEnum.Iteration
  327. }
  328. type ConnectedSourceOrTargetNodesChange = {
  329. type: string
  330. edge: Edge
  331. }[]
  332. export const getNodesConnectedSourceOrTargetHandleIdsMap = (changes: ConnectedSourceOrTargetNodesChange, nodes: Node[]) => {
  333. const nodesConnectedSourceOrTargetHandleIdsMap = {} as Record<string, any>
  334. changes.forEach((change) => {
  335. const {
  336. edge,
  337. type,
  338. } = change
  339. const sourceNode = nodes.find(node => node.id === edge.source)!
  340. if (sourceNode) {
  341. nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id] = nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id] || {
  342. _connectedSourceHandleIds: [...(sourceNode?.data._connectedSourceHandleIds || [])],
  343. _connectedTargetHandleIds: [...(sourceNode?.data._connectedTargetHandleIds || [])],
  344. }
  345. }
  346. const targetNode = nodes.find(node => node.id === edge.target)!
  347. if (targetNode) {
  348. nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id] = nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id] || {
  349. _connectedSourceHandleIds: [...(targetNode?.data._connectedSourceHandleIds || [])],
  350. _connectedTargetHandleIds: [...(targetNode?.data._connectedTargetHandleIds || [])],
  351. }
  352. }
  353. if (sourceNode) {
  354. if (type === 'remove') {
  355. const index = nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds.findIndex((handleId: string) => handleId === edge.sourceHandle)
  356. nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds.splice(index, 1)
  357. }
  358. if (type === 'add')
  359. nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds.push(edge.sourceHandle || 'source')
  360. }
  361. if (targetNode) {
  362. if (type === 'remove') {
  363. const index = nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds.findIndex((handleId: string) => handleId === edge.targetHandle)
  364. nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds.splice(index, 1)
  365. }
  366. if (type === 'add')
  367. nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds.push(edge.targetHandle || 'target')
  368. }
  369. })
  370. return nodesConnectedSourceOrTargetHandleIdsMap
  371. }
  372. export const genNewNodeTitleFromOld = (oldTitle: string) => {
  373. const regex = /^(.+?)\s*\((\d+)\)\s*$/
  374. const match = oldTitle.match(regex)
  375. if (match) {
  376. const title = match[1]
  377. const num = parseInt(match[2], 10)
  378. return `${title} (${num + 1})`
  379. }
  380. else {
  381. return `${oldTitle} (1)`
  382. }
  383. }
  384. export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => {
  385. const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
  386. if (!startNode) {
  387. return {
  388. validNodes: [],
  389. maxDepth: 0,
  390. }
  391. }
  392. const list: Node[] = [startNode]
  393. let maxDepth = 1
  394. const traverse = (root: Node, depth: number) => {
  395. if (depth > maxDepth)
  396. maxDepth = depth
  397. const outgoers = getOutgoers(root, nodes, edges)
  398. if (outgoers.length) {
  399. outgoers.forEach((outgoer) => {
  400. list.push(outgoer)
  401. if (outgoer.data.type === BlockEnum.Iteration)
  402. list.push(...nodes.filter(node => node.parentId === outgoer.id))
  403. traverse(outgoer, depth + 1)
  404. })
  405. }
  406. else {
  407. list.push(root)
  408. if (root.data.type === BlockEnum.Iteration)
  409. list.push(...nodes.filter(node => node.parentId === root.id))
  410. }
  411. }
  412. traverse(startNode, maxDepth)
  413. return {
  414. validNodes: uniqBy(list, 'id'),
  415. maxDepth,
  416. }
  417. }
  418. export const getToolCheckParams = (
  419. toolData: ToolNodeType,
  420. buildInTools: ToolWithProvider[],
  421. customTools: ToolWithProvider[],
  422. workflowTools: ToolWithProvider[],
  423. language: string,
  424. ) => {
  425. const { provider_id, provider_type, tool_name } = toolData
  426. const isBuiltIn = provider_type === CollectionType.builtIn
  427. const currentTools = provider_type === CollectionType.builtIn ? buildInTools : provider_type === CollectionType.custom ? customTools : workflowTools
  428. const currCollection = currentTools.find(item => item.id === provider_id)
  429. const currTool = currCollection?.tools.find(tool => tool.name === tool_name)
  430. const formSchemas = currTool ? toolParametersToFormSchemas(currTool.parameters) : []
  431. const toolInputVarSchema = formSchemas.filter((item: any) => item.form === 'llm')
  432. const toolSettingSchema = formSchemas.filter((item: any) => item.form !== 'llm')
  433. return {
  434. toolInputsSchema: (() => {
  435. const formInputs: InputVar[] = []
  436. toolInputVarSchema.forEach((item: any) => {
  437. formInputs.push({
  438. label: item.label[language] || item.label.en_US,
  439. variable: item.variable,
  440. type: item.type,
  441. required: item.required,
  442. })
  443. })
  444. return formInputs
  445. })(),
  446. notAuthed: isBuiltIn && !!currCollection?.allow_delete && !currCollection?.is_team_authorization,
  447. toolSettingSchema,
  448. language,
  449. }
  450. }
  451. export const changeNodesAndEdgesId = (nodes: Node[], edges: Edge[]) => {
  452. const idMap = nodes.reduce((acc, node) => {
  453. acc[node.id] = uuid4()
  454. return acc
  455. }, {} as Record<string, string>)
  456. const newNodes = nodes.map((node) => {
  457. return {
  458. ...node,
  459. id: idMap[node.id],
  460. }
  461. })
  462. const newEdges = edges.map((edge) => {
  463. return {
  464. ...edge,
  465. source: idMap[edge.source],
  466. target: idMap[edge.target],
  467. }
  468. })
  469. return [newNodes, newEdges] as [Node[], Edge[]]
  470. }
  471. export const isMac = () => {
  472. return navigator.userAgent.toUpperCase().includes('MAC')
  473. }
  474. const specialKeysNameMap: Record<string, string | undefined> = {
  475. ctrl: '⌘',
  476. alt: '⌥',
  477. }
  478. export const getKeyboardKeyNameBySystem = (key: string) => {
  479. if (isMac())
  480. return specialKeysNameMap[key] || key
  481. return key
  482. }
  483. const specialKeysCodeMap: Record<string, string | undefined> = {
  484. ctrl: 'meta',
  485. }
  486. export const getKeyboardKeyCodeBySystem = (key: string) => {
  487. if (isMac())
  488. return specialKeysCodeMap[key] || key
  489. return key
  490. }
  491. export const getTopLeftNodePosition = (nodes: Node[]) => {
  492. let minX = Infinity
  493. let minY = Infinity
  494. nodes.forEach((node) => {
  495. if (node.position.x < minX)
  496. minX = node.position.x
  497. if (node.position.y < minY)
  498. minY = node.position.y
  499. })
  500. return {
  501. x: minX,
  502. y: minY,
  503. }
  504. }
  505. export const isEventTargetInputArea = (target: HTMLElement) => {
  506. if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')
  507. return true
  508. if (target.contentEditable === 'true')
  509. return true
  510. }
  511. export const variableTransformer = (v: ValueSelector | string) => {
  512. if (typeof v === 'string')
  513. return v.replace(/^{{#|#}}$/g, '').split('.')
  514. return `{{#${v.join('.')}#}}`
  515. }
  516. type ParallelInfoItem = {
  517. parallelNodeId: string
  518. depth: number
  519. isBranch?: boolean
  520. }
  521. type NodeParallelInfo = {
  522. parallelNodeId: string
  523. edgeHandleId: string
  524. depth: number
  525. }
  526. type NodeHandle = {
  527. node: Node
  528. handle: string
  529. }
  530. type NodeStreamInfo = {
  531. upstreamNodes: Set<string>
  532. downstreamEdges: Set<string>
  533. }
  534. export const getParallelInfo = (nodes: Node[], edges: Edge[], parentNodeId?: string) => {
  535. let startNode
  536. if (parentNodeId) {
  537. const parentNode = nodes.find(node => node.id === parentNodeId)
  538. if (!parentNode)
  539. throw new Error('Parent node not found')
  540. startNode = nodes.find(node => node.id === (parentNode.data as IterationNodeType).start_node_id)
  541. }
  542. else {
  543. startNode = nodes.find(node => node.data.type === BlockEnum.Start)
  544. }
  545. if (!startNode)
  546. throw new Error('Start node not found')
  547. const parallelList = [] as ParallelInfoItem[]
  548. const nextNodeHandles = [{ node: startNode, handle: 'source' }]
  549. let hasAbnormalEdges = false
  550. const traverse = (firstNodeHandle: NodeHandle) => {
  551. const nodeEdgesSet = {} as Record<string, Set<string>>
  552. const totalEdgesSet = new Set<string>()
  553. const nextHandles = [firstNodeHandle]
  554. const streamInfo = {} as Record<string, NodeStreamInfo>
  555. const parallelListItem = {
  556. parallelNodeId: '',
  557. depth: 0,
  558. } as ParallelInfoItem
  559. const nodeParallelInfoMap = {} as Record<string, NodeParallelInfo>
  560. nodeParallelInfoMap[firstNodeHandle.node.id] = {
  561. parallelNodeId: '',
  562. edgeHandleId: '',
  563. depth: 0,
  564. }
  565. while (nextHandles.length) {
  566. const currentNodeHandle = nextHandles.shift()!
  567. const { node: currentNode, handle: currentHandle = 'source' } = currentNodeHandle
  568. const currentNodeHandleKey = currentNode.id
  569. const connectedEdges = edges.filter(edge => edge.source === currentNode.id && edge.sourceHandle === currentHandle)
  570. const connectedEdgesLength = connectedEdges.length
  571. const outgoers = nodes.filter(node => connectedEdges.some(edge => edge.target === node.id))
  572. const incomers = getIncomers(currentNode, nodes, edges)
  573. if (!streamInfo[currentNodeHandleKey]) {
  574. streamInfo[currentNodeHandleKey] = {
  575. upstreamNodes: new Set<string>(),
  576. downstreamEdges: new Set<string>(),
  577. }
  578. }
  579. if (nodeEdgesSet[currentNodeHandleKey]?.size > 0 && incomers.length > 1) {
  580. const newSet = new Set<string>()
  581. for (const item of totalEdgesSet) {
  582. if (!streamInfo[currentNodeHandleKey].downstreamEdges.has(item))
  583. newSet.add(item)
  584. }
  585. if (isEqual(nodeEdgesSet[currentNodeHandleKey], newSet)) {
  586. parallelListItem.depth = nodeParallelInfoMap[currentNode.id].depth
  587. nextNodeHandles.push({ node: currentNode, handle: currentHandle })
  588. break
  589. }
  590. }
  591. if (nodeParallelInfoMap[currentNode.id].depth > parallelListItem.depth)
  592. parallelListItem.depth = nodeParallelInfoMap[currentNode.id].depth
  593. outgoers.forEach((outgoer) => {
  594. const outgoerConnectedEdges = getConnectedEdges([outgoer], edges).filter(edge => edge.source === outgoer.id)
  595. const sourceEdgesGroup = groupBy(outgoerConnectedEdges, 'sourceHandle')
  596. const incomers = getIncomers(outgoer, nodes, edges)
  597. if (outgoers.length > 1 && incomers.length > 1)
  598. hasAbnormalEdges = true
  599. Object.keys(sourceEdgesGroup).forEach((sourceHandle) => {
  600. nextHandles.push({ node: outgoer, handle: sourceHandle })
  601. })
  602. if (!outgoerConnectedEdges.length)
  603. nextHandles.push({ node: outgoer, handle: 'source' })
  604. const outgoerKey = outgoer.id
  605. if (!nodeEdgesSet[outgoerKey])
  606. nodeEdgesSet[outgoerKey] = new Set<string>()
  607. if (nodeEdgesSet[currentNodeHandleKey]) {
  608. for (const item of nodeEdgesSet[currentNodeHandleKey])
  609. nodeEdgesSet[outgoerKey].add(item)
  610. }
  611. if (!streamInfo[outgoerKey]) {
  612. streamInfo[outgoerKey] = {
  613. upstreamNodes: new Set<string>(),
  614. downstreamEdges: new Set<string>(),
  615. }
  616. }
  617. if (!nodeParallelInfoMap[outgoer.id]) {
  618. nodeParallelInfoMap[outgoer.id] = {
  619. ...nodeParallelInfoMap[currentNode.id],
  620. }
  621. }
  622. if (connectedEdgesLength > 1) {
  623. const edge = connectedEdges.find(edge => edge.target === outgoer.id)!
  624. nodeEdgesSet[outgoerKey].add(edge.id)
  625. totalEdgesSet.add(edge.id)
  626. streamInfo[currentNodeHandleKey].downstreamEdges.add(edge.id)
  627. streamInfo[outgoerKey].upstreamNodes.add(currentNodeHandleKey)
  628. for (const item of streamInfo[currentNodeHandleKey].upstreamNodes)
  629. streamInfo[item].downstreamEdges.add(edge.id)
  630. if (!parallelListItem.parallelNodeId)
  631. parallelListItem.parallelNodeId = currentNode.id
  632. const prevDepth = nodeParallelInfoMap[currentNode.id].depth + 1
  633. const currentDepth = nodeParallelInfoMap[outgoer.id].depth
  634. nodeParallelInfoMap[outgoer.id].depth = Math.max(prevDepth, currentDepth)
  635. }
  636. else {
  637. for (const item of streamInfo[currentNodeHandleKey].upstreamNodes)
  638. streamInfo[outgoerKey].upstreamNodes.add(item)
  639. nodeParallelInfoMap[outgoer.id].depth = nodeParallelInfoMap[currentNode.id].depth
  640. }
  641. })
  642. }
  643. parallelList.push(parallelListItem)
  644. }
  645. while (nextNodeHandles.length) {
  646. const nodeHandle = nextNodeHandles.shift()!
  647. traverse(nodeHandle)
  648. }
  649. return {
  650. parallelList,
  651. hasAbnormalEdges,
  652. }
  653. }