utils.ts 24 KB

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