utils.ts 25 KB

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