use-nodes-interactions.ts 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567
  1. import type { MouseEvent } from 'react'
  2. import { useCallback, useRef } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import produce from 'immer'
  5. import type {
  6. NodeDragHandler,
  7. NodeMouseHandler,
  8. OnConnect,
  9. OnConnectEnd,
  10. OnConnectStart,
  11. ResizeParamsWithDirection,
  12. } from 'reactflow'
  13. import {
  14. getConnectedEdges,
  15. getOutgoers,
  16. useReactFlow,
  17. useStoreApi,
  18. } from 'reactflow'
  19. import { unionBy } from 'lodash-es'
  20. import type { ToolDefaultValue } from '../block-selector/types'
  21. import type {
  22. Edge,
  23. Node,
  24. OnNodeAdd,
  25. } from '../types'
  26. import { BlockEnum } from '../types'
  27. import { useWorkflowStore } from '../store'
  28. import {
  29. CUSTOM_EDGE,
  30. ITERATION_CHILDREN_Z_INDEX,
  31. ITERATION_PADDING,
  32. LOOP_CHILDREN_Z_INDEX,
  33. LOOP_PADDING,
  34. NODES_INITIAL_DATA,
  35. NODE_WIDTH_X_OFFSET,
  36. X_OFFSET,
  37. Y_OFFSET,
  38. } from '../constants'
  39. import {
  40. genNewNodeTitleFromOld,
  41. generateNewNode,
  42. getNodesConnectedSourceOrTargetHandleIdsMap,
  43. getTopLeftNodePosition,
  44. } from '../utils'
  45. import { CUSTOM_NOTE_NODE } from '../note-node/constants'
  46. import type { IterationNodeType } from '../nodes/iteration/types'
  47. import type { LoopNodeType } from '../nodes/loop/types'
  48. import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants'
  49. import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants'
  50. import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types'
  51. import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions'
  52. import { useNodeLoopInteractions } from '../nodes/loop/use-interactions'
  53. import { useWorkflowHistoryStore } from '../workflow-history-store'
  54. import { useNodesSyncDraft } from './use-nodes-sync-draft'
  55. import { useHelpline } from './use-helpline'
  56. import {
  57. useNodesReadOnly,
  58. useWorkflow,
  59. useWorkflowReadOnly,
  60. } from './use-workflow'
  61. import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
  62. export const useNodesInteractions = () => {
  63. const { t } = useTranslation()
  64. const store = useStoreApi()
  65. const workflowStore = useWorkflowStore()
  66. const reactflow = useReactFlow()
  67. const { store: workflowHistoryStore } = useWorkflowHistoryStore()
  68. const { handleSyncWorkflowDraft } = useNodesSyncDraft()
  69. const {
  70. checkNestedParallelLimit,
  71. getAfterNodesInSameBranch,
  72. } = useWorkflow()
  73. const { getNodesReadOnly } = useNodesReadOnly()
  74. const { getWorkflowReadOnly } = useWorkflowReadOnly()
  75. const { handleSetHelpline } = useHelpline()
  76. const {
  77. handleNodeIterationChildDrag,
  78. handleNodeIterationChildrenCopy,
  79. } = useNodeIterationInteractions()
  80. const {
  81. handleNodeLoopChildDrag,
  82. handleNodeLoopChildrenCopy,
  83. } = useNodeLoopInteractions()
  84. const dragNodeStartPosition = useRef({ x: 0, y: 0 } as { x: number; y: number })
  85. const { saveStateToHistory, undo, redo } = useWorkflowHistory()
  86. const handleNodeDragStart = useCallback<NodeDragHandler>((_, node) => {
  87. workflowStore.setState({ nodeAnimation: false })
  88. if (getNodesReadOnly())
  89. return
  90. if (node.type === CUSTOM_ITERATION_START_NODE || node.type === CUSTOM_NOTE_NODE)
  91. return
  92. if (node.type === CUSTOM_LOOP_START_NODE || node.type === CUSTOM_NOTE_NODE)
  93. return
  94. dragNodeStartPosition.current = { x: node.position.x, y: node.position.y }
  95. }, [workflowStore, getNodesReadOnly])
  96. const handleNodeDrag = useCallback<NodeDragHandler>((e, node: Node) => {
  97. if (getNodesReadOnly())
  98. return
  99. if (node.type === CUSTOM_ITERATION_START_NODE)
  100. return
  101. if (node.type === CUSTOM_LOOP_START_NODE)
  102. return
  103. const {
  104. getNodes,
  105. setNodes,
  106. } = store.getState()
  107. e.stopPropagation()
  108. const nodes = getNodes()
  109. const { restrictPosition } = handleNodeIterationChildDrag(node)
  110. const { restrictPosition: restrictLoopPosition } = handleNodeLoopChildDrag(node)
  111. const {
  112. showHorizontalHelpLineNodes,
  113. showVerticalHelpLineNodes,
  114. } = handleSetHelpline(node)
  115. const showHorizontalHelpLineNodesLength = showHorizontalHelpLineNodes.length
  116. const showVerticalHelpLineNodesLength = showVerticalHelpLineNodes.length
  117. const newNodes = produce(nodes, (draft) => {
  118. const currentNode = draft.find(n => n.id === node.id)!
  119. if (showVerticalHelpLineNodesLength > 0)
  120. currentNode.position.x = showVerticalHelpLineNodes[0].position.x
  121. else if (restrictPosition.x !== undefined)
  122. currentNode.position.x = restrictPosition.x
  123. else if (restrictLoopPosition.x !== undefined)
  124. currentNode.position.x = restrictLoopPosition.x
  125. else
  126. currentNode.position.x = node.position.x
  127. if (showHorizontalHelpLineNodesLength > 0)
  128. currentNode.position.y = showHorizontalHelpLineNodes[0].position.y
  129. else if (restrictPosition.y !== undefined)
  130. currentNode.position.y = restrictPosition.y
  131. else if (restrictLoopPosition.y !== undefined)
  132. currentNode.position.y = restrictLoopPosition.y
  133. else
  134. currentNode.position.y = node.position.y
  135. })
  136. setNodes(newNodes)
  137. }, [getNodesReadOnly, store, handleNodeIterationChildDrag, handleNodeLoopChildDrag, handleSetHelpline])
  138. const handleNodeDragStop = useCallback<NodeDragHandler>((_, node) => {
  139. const {
  140. setHelpLineHorizontal,
  141. setHelpLineVertical,
  142. } = workflowStore.getState()
  143. if (getNodesReadOnly())
  144. return
  145. const { x, y } = dragNodeStartPosition.current
  146. if (!(x === node.position.x && y === node.position.y)) {
  147. setHelpLineHorizontal()
  148. setHelpLineVertical()
  149. handleSyncWorkflowDraft()
  150. if (x !== 0 && y !== 0) {
  151. // selecting a note will trigger a drag stop event with x and y as 0
  152. saveStateToHistory(WorkflowHistoryEvent.NodeDragStop)
  153. }
  154. }
  155. }, [workflowStore, getNodesReadOnly, saveStateToHistory, handleSyncWorkflowDraft])
  156. const handleNodeEnter = useCallback<NodeMouseHandler>((_, node) => {
  157. if (getNodesReadOnly())
  158. return
  159. if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE)
  160. return
  161. if (node.type === CUSTOM_LOOP_START_NODE || node.type === CUSTOM_NOTE_NODE)
  162. return
  163. const {
  164. getNodes,
  165. setNodes,
  166. edges,
  167. setEdges,
  168. } = store.getState()
  169. const nodes = getNodes()
  170. const {
  171. connectingNodePayload,
  172. setEnteringNodePayload,
  173. } = workflowStore.getState()
  174. if (connectingNodePayload) {
  175. if (connectingNodePayload.nodeId === node.id)
  176. return
  177. const connectingNode: Node = nodes.find(n => n.id === connectingNodePayload.nodeId)!
  178. const sameLevel = connectingNode.parentId === node.parentId
  179. if (sameLevel) {
  180. setEnteringNodePayload({
  181. nodeId: node.id,
  182. nodeData: node.data as VariableAssignerNodeType,
  183. })
  184. const fromType = connectingNodePayload.handleType
  185. const newNodes = produce(nodes, (draft) => {
  186. draft.forEach((n) => {
  187. if (n.id === node.id && fromType === 'source' && (node.data.type === BlockEnum.VariableAssigner || node.data.type === BlockEnum.VariableAggregator)) {
  188. if (!node.data.advanced_settings?.group_enabled)
  189. n.data._isEntering = true
  190. }
  191. if (n.id === node.id && fromType === 'target' && (connectingNode.data.type === BlockEnum.VariableAssigner || connectingNode.data.type === BlockEnum.VariableAggregator) && node.data.type !== BlockEnum.IfElse && node.data.type !== BlockEnum.QuestionClassifier)
  192. n.data._isEntering = true
  193. })
  194. })
  195. setNodes(newNodes)
  196. }
  197. }
  198. const newEdges = produce(edges, (draft) => {
  199. const connectedEdges = getConnectedEdges([node], edges)
  200. connectedEdges.forEach((edge) => {
  201. const currentEdge = draft.find(e => e.id === edge.id)
  202. if (currentEdge)
  203. currentEdge.data._connectedNodeIsHovering = true
  204. })
  205. })
  206. setEdges(newEdges)
  207. const connectedEdges = getConnectedEdges([node], edges).filter(edge => edge.target === node.id)
  208. const targetNodes: Node[] = []
  209. for (let i = 0; i < connectedEdges.length; i++) {
  210. const sourceConnectedEdges = getConnectedEdges([{ id: connectedEdges[i].source } as Node], edges).filter(edge => edge.source === connectedEdges[i].source && edge.sourceHandle === connectedEdges[i].sourceHandle)
  211. targetNodes.push(...sourceConnectedEdges.map(edge => nodes.find(n => n.id === edge.target)!))
  212. }
  213. const uniqTargetNodes = unionBy(targetNodes, 'id')
  214. if (uniqTargetNodes.length > 1) {
  215. const newNodes = produce(nodes, (draft) => {
  216. draft.forEach((n) => {
  217. if (uniqTargetNodes.some(targetNode => n.id === targetNode.id))
  218. n.data._inParallelHovering = true
  219. })
  220. })
  221. setNodes(newNodes)
  222. }
  223. }, [store, workflowStore, getNodesReadOnly])
  224. const handleNodeLeave = useCallback<NodeMouseHandler>((_, node) => {
  225. if (getNodesReadOnly())
  226. return
  227. if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE)
  228. return
  229. if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_LOOP_START_NODE)
  230. return
  231. const {
  232. setEnteringNodePayload,
  233. } = workflowStore.getState()
  234. setEnteringNodePayload(undefined)
  235. const {
  236. getNodes,
  237. setNodes,
  238. edges,
  239. setEdges,
  240. } = store.getState()
  241. const newNodes = produce(getNodes(), (draft) => {
  242. draft.forEach((node) => {
  243. node.data._isEntering = false
  244. node.data._inParallelHovering = false
  245. })
  246. })
  247. setNodes(newNodes)
  248. const newEdges = produce(edges, (draft) => {
  249. draft.forEach((edge) => {
  250. edge.data._connectedNodeIsHovering = false
  251. })
  252. })
  253. setEdges(newEdges)
  254. }, [store, workflowStore, getNodesReadOnly])
  255. const handleNodeSelect = useCallback((nodeId: string, cancelSelection?: boolean) => {
  256. const {
  257. getNodes,
  258. setNodes,
  259. edges,
  260. setEdges,
  261. } = store.getState()
  262. const nodes = getNodes()
  263. const selectedNode = nodes.find(node => node.data.selected)
  264. if (!cancelSelection && selectedNode?.id === nodeId)
  265. return
  266. const newNodes = produce(nodes, (draft) => {
  267. draft.forEach((node) => {
  268. if (node.id === nodeId)
  269. node.data.selected = !cancelSelection
  270. else
  271. node.data.selected = false
  272. })
  273. })
  274. setNodes(newNodes)
  275. const connectedEdges = getConnectedEdges([{ id: nodeId } as Node], edges).map(edge => edge.id)
  276. const newEdges = produce(edges, (draft) => {
  277. draft.forEach((edge) => {
  278. if (connectedEdges.includes(edge.id)) {
  279. edge.data = {
  280. ...edge.data,
  281. _connectedNodeIsSelected: !cancelSelection,
  282. }
  283. }
  284. else {
  285. edge.data = {
  286. ...edge.data,
  287. _connectedNodeIsSelected: false,
  288. }
  289. }
  290. })
  291. })
  292. setEdges(newEdges)
  293. handleSyncWorkflowDraft()
  294. }, [store, handleSyncWorkflowDraft])
  295. const handleNodeClick = useCallback<NodeMouseHandler>((_, node) => {
  296. if (node.type === CUSTOM_ITERATION_START_NODE)
  297. return
  298. if (node.type === CUSTOM_LOOP_START_NODE)
  299. return
  300. handleNodeSelect(node.id)
  301. }, [handleNodeSelect])
  302. const handleNodeConnect = useCallback<OnConnect>(({
  303. source,
  304. sourceHandle,
  305. target,
  306. targetHandle,
  307. }) => {
  308. if (source === target)
  309. return
  310. if (getNodesReadOnly())
  311. return
  312. const {
  313. getNodes,
  314. setNodes,
  315. edges,
  316. setEdges,
  317. } = store.getState()
  318. const nodes = getNodes()
  319. const targetNode = nodes.find(node => node.id === target!)
  320. const sourceNode = nodes.find(node => node.id === source!)
  321. if (targetNode?.parentId !== sourceNode?.parentId)
  322. return
  323. if (sourceNode?.type === CUSTOM_NOTE_NODE || targetNode?.type === CUSTOM_NOTE_NODE)
  324. return
  325. if (edges.find(edge => edge.source === source && edge.sourceHandle === sourceHandle && edge.target === target && edge.targetHandle === targetHandle))
  326. return
  327. const parendNode = nodes.find(node => node.id === targetNode?.parentId)
  328. const isInIteration = parendNode && parendNode.data.type === BlockEnum.Iteration
  329. const isInLoop = !!parendNode && parendNode.data.type === BlockEnum.Loop
  330. const newEdge = {
  331. id: `${source}-${sourceHandle}-${target}-${targetHandle}`,
  332. type: CUSTOM_EDGE,
  333. source: source!,
  334. target: target!,
  335. sourceHandle,
  336. targetHandle,
  337. data: {
  338. sourceType: nodes.find(node => node.id === source)!.data.type,
  339. targetType: nodes.find(node => node.id === target)!.data.type,
  340. isInIteration,
  341. iteration_id: isInIteration ? targetNode?.parentId : undefined,
  342. isInLoop,
  343. loop_id: isInLoop ? targetNode?.parentId : undefined,
  344. },
  345. zIndex: targetNode?.parentId ? (isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0,
  346. }
  347. const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
  348. [
  349. { type: 'add', edge: newEdge },
  350. ],
  351. nodes,
  352. )
  353. const newNodes = produce(nodes, (draft: Node[]) => {
  354. draft.forEach((node) => {
  355. if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
  356. node.data = {
  357. ...node.data,
  358. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  359. }
  360. }
  361. })
  362. })
  363. const newEdges = produce(edges, (draft) => {
  364. draft.push(newEdge)
  365. })
  366. if (checkNestedParallelLimit(newNodes, newEdges, targetNode?.parentId)) {
  367. setNodes(newNodes)
  368. setEdges(newEdges)
  369. handleSyncWorkflowDraft()
  370. saveStateToHistory(WorkflowHistoryEvent.NodeConnect)
  371. }
  372. else {
  373. const {
  374. setConnectingNodePayload,
  375. setEnteringNodePayload,
  376. } = workflowStore.getState()
  377. setConnectingNodePayload(undefined)
  378. setEnteringNodePayload(undefined)
  379. }
  380. }, [getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, saveStateToHistory, checkNestedParallelLimit])
  381. const handleNodeConnectStart = useCallback<OnConnectStart>((_, { nodeId, handleType, handleId }) => {
  382. if (getNodesReadOnly())
  383. return
  384. if (nodeId && handleType) {
  385. const { setConnectingNodePayload } = workflowStore.getState()
  386. const { getNodes } = store.getState()
  387. const node = getNodes().find(n => n.id === nodeId)!
  388. if (node.type === CUSTOM_NOTE_NODE)
  389. return
  390. if (node.data.type === BlockEnum.VariableAggregator || node.data.type === BlockEnum.VariableAssigner) {
  391. if (handleType === 'target')
  392. return
  393. }
  394. setConnectingNodePayload({
  395. nodeId,
  396. nodeType: node.data.type,
  397. handleType,
  398. handleId,
  399. })
  400. }
  401. }, [store, workflowStore, getNodesReadOnly])
  402. const handleNodeConnectEnd = useCallback<OnConnectEnd>((e: any) => {
  403. if (getNodesReadOnly())
  404. return
  405. const {
  406. connectingNodePayload,
  407. setConnectingNodePayload,
  408. enteringNodePayload,
  409. setEnteringNodePayload,
  410. } = workflowStore.getState()
  411. if (connectingNodePayload && enteringNodePayload) {
  412. const {
  413. setShowAssignVariablePopup,
  414. hoveringAssignVariableGroupId,
  415. } = workflowStore.getState()
  416. const { screenToFlowPosition } = reactflow
  417. const {
  418. getNodes,
  419. setNodes,
  420. } = store.getState()
  421. const nodes = getNodes()
  422. const fromHandleType = connectingNodePayload.handleType
  423. const fromHandleId = connectingNodePayload.handleId
  424. const fromNode = nodes.find(n => n.id === connectingNodePayload.nodeId)!
  425. const toNode = nodes.find(n => n.id === enteringNodePayload.nodeId)!
  426. const toParentNode = nodes.find(n => n.id === toNode.parentId)
  427. if (fromNode.parentId !== toNode.parentId)
  428. return
  429. const { x, y } = screenToFlowPosition({ x: e.x, y: e.y })
  430. if (fromHandleType === 'source' && (toNode.data.type === BlockEnum.VariableAssigner || toNode.data.type === BlockEnum.VariableAggregator)) {
  431. const groupEnabled = toNode.data.advanced_settings?.group_enabled
  432. const firstGroupId = toNode.data.advanced_settings?.groups[0].groupId
  433. let handleId = 'target'
  434. if (groupEnabled) {
  435. if (hoveringAssignVariableGroupId)
  436. handleId = hoveringAssignVariableGroupId
  437. else
  438. handleId = firstGroupId
  439. }
  440. const newNodes = produce(nodes, (draft) => {
  441. draft.forEach((node) => {
  442. if (node.id === toNode.id) {
  443. node.data._showAddVariablePopup = true
  444. node.data._holdAddVariablePopup = true
  445. }
  446. })
  447. })
  448. setNodes(newNodes)
  449. setShowAssignVariablePopup({
  450. nodeId: fromNode.id,
  451. nodeData: fromNode.data,
  452. variableAssignerNodeId: toNode.id,
  453. variableAssignerNodeData: toNode.data,
  454. variableAssignerNodeHandleId: handleId,
  455. parentNode: toParentNode,
  456. x: x - toNode.positionAbsolute!.x,
  457. y: y - toNode.positionAbsolute!.y,
  458. })
  459. handleNodeConnect({
  460. source: fromNode.id,
  461. sourceHandle: fromHandleId,
  462. target: toNode.id,
  463. targetHandle: 'target',
  464. })
  465. }
  466. }
  467. setConnectingNodePayload(undefined)
  468. setEnteringNodePayload(undefined)
  469. }, [store, handleNodeConnect, getNodesReadOnly, workflowStore, reactflow])
  470. const handleNodeDelete = useCallback((nodeId: string) => {
  471. if (getNodesReadOnly())
  472. return
  473. const {
  474. getNodes,
  475. setNodes,
  476. edges,
  477. setEdges,
  478. } = store.getState()
  479. const nodes = getNodes()
  480. const currentNodeIndex = nodes.findIndex(node => node.id === nodeId)
  481. const currentNode = nodes[currentNodeIndex]
  482. if (!currentNode)
  483. return
  484. if (currentNode.data.type === BlockEnum.Start)
  485. return
  486. if (currentNode.data.type === BlockEnum.Iteration) {
  487. const iterationChildren = nodes.filter(node => node.parentId === currentNode.id)
  488. if (iterationChildren.length) {
  489. if (currentNode.data._isBundled) {
  490. iterationChildren.forEach((child) => {
  491. handleNodeDelete(child.id)
  492. })
  493. return handleNodeDelete(nodeId)
  494. }
  495. else {
  496. if (iterationChildren.length === 1) {
  497. handleNodeDelete(iterationChildren[0].id)
  498. handleNodeDelete(nodeId)
  499. return
  500. }
  501. const { setShowConfirm, showConfirm } = workflowStore.getState()
  502. if (!showConfirm) {
  503. setShowConfirm({
  504. title: t('workflow.nodes.iteration.deleteTitle'),
  505. desc: t('workflow.nodes.iteration.deleteDesc') || '',
  506. onConfirm: () => {
  507. iterationChildren.forEach((child) => {
  508. handleNodeDelete(child.id)
  509. })
  510. handleNodeDelete(nodeId)
  511. handleSyncWorkflowDraft()
  512. setShowConfirm(undefined)
  513. },
  514. })
  515. return
  516. }
  517. }
  518. }
  519. }
  520. if (currentNode.data.type === BlockEnum.Loop) {
  521. const loopChildren = nodes.filter(node => node.parentId === currentNode.id)
  522. if (loopChildren.length) {
  523. if (currentNode.data._isBundled) {
  524. loopChildren.forEach((child) => {
  525. handleNodeDelete(child.id)
  526. })
  527. return handleNodeDelete(nodeId)
  528. }
  529. else {
  530. if (loopChildren.length === 1) {
  531. handleNodeDelete(loopChildren[0].id)
  532. handleNodeDelete(nodeId)
  533. return
  534. }
  535. const { setShowConfirm, showConfirm } = workflowStore.getState()
  536. if (!showConfirm) {
  537. setShowConfirm({
  538. title: t('workflow.nodes.loop.deleteTitle'),
  539. desc: t('workflow.nodes.loop.deleteDesc') || '',
  540. onConfirm: () => {
  541. loopChildren.forEach((child) => {
  542. handleNodeDelete(child.id)
  543. })
  544. handleNodeDelete(nodeId)
  545. handleSyncWorkflowDraft()
  546. setShowConfirm(undefined)
  547. },
  548. })
  549. return
  550. }
  551. }
  552. }
  553. }
  554. const connectedEdges = getConnectedEdges([{ id: nodeId } as Node], edges)
  555. const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(connectedEdges.map(edge => ({ type: 'remove', edge })), nodes)
  556. const newNodes = produce(nodes, (draft: Node[]) => {
  557. draft.forEach((node) => {
  558. if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
  559. node.data = {
  560. ...node.data,
  561. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  562. }
  563. }
  564. if (node.id === currentNode.parentId)
  565. node.data._children = node.data._children?.filter(child => child !== nodeId)
  566. })
  567. draft.splice(currentNodeIndex, 1)
  568. })
  569. setNodes(newNodes)
  570. const newEdges = produce(edges, (draft) => {
  571. return draft.filter(edge => !connectedEdges.find(connectedEdge => connectedEdge.id === edge.id))
  572. })
  573. setEdges(newEdges)
  574. handleSyncWorkflowDraft()
  575. if (currentNode.type === CUSTOM_NOTE_NODE)
  576. saveStateToHistory(WorkflowHistoryEvent.NoteDelete)
  577. else
  578. saveStateToHistory(WorkflowHistoryEvent.NodeDelete)
  579. }, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, t])
  580. const handleNodeAdd = useCallback<OnNodeAdd>((
  581. {
  582. nodeType,
  583. sourceHandle = 'source',
  584. targetHandle = 'target',
  585. toolDefaultValue,
  586. },
  587. {
  588. prevNodeId,
  589. prevNodeSourceHandle,
  590. nextNodeId,
  591. nextNodeTargetHandle,
  592. },
  593. ) => {
  594. if (getNodesReadOnly())
  595. return
  596. const {
  597. getNodes,
  598. setNodes,
  599. edges,
  600. setEdges,
  601. } = store.getState()
  602. const nodes = getNodes()
  603. const nodesWithSameType = nodes.filter(node => node.data.type === nodeType)
  604. const {
  605. newNode,
  606. newIterationStartNode,
  607. newLoopStartNode,
  608. } = generateNewNode({
  609. data: {
  610. ...NODES_INITIAL_DATA[nodeType],
  611. title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`),
  612. ...(toolDefaultValue || {}),
  613. selected: true,
  614. _showAddVariablePopup: (nodeType === BlockEnum.VariableAssigner || nodeType === BlockEnum.VariableAggregator) && !!prevNodeId,
  615. _holdAddVariablePopup: false,
  616. },
  617. position: {
  618. x: 0,
  619. y: 0,
  620. },
  621. })
  622. if (prevNodeId && !nextNodeId) {
  623. const prevNodeIndex = nodes.findIndex(node => node.id === prevNodeId)
  624. const prevNode = nodes[prevNodeIndex]
  625. const outgoers = getOutgoers(prevNode, nodes, edges).sort((a, b) => a.position.y - b.position.y)
  626. const lastOutgoer = outgoers[outgoers.length - 1]
  627. newNode.data._connectedTargetHandleIds = [targetHandle]
  628. newNode.data._connectedSourceHandleIds = []
  629. newNode.position = {
  630. x: lastOutgoer ? lastOutgoer.position.x : prevNode.position.x + prevNode.width! + X_OFFSET,
  631. y: lastOutgoer ? lastOutgoer.position.y + lastOutgoer.height! + Y_OFFSET : prevNode.position.y,
  632. }
  633. newNode.parentId = prevNode.parentId
  634. newNode.extent = prevNode.extent
  635. const parentNode = nodes.find(node => node.id === prevNode.parentId) || null
  636. const isInIteration = !!parentNode && parentNode.data.type === BlockEnum.Iteration
  637. const isInLoop = !!parentNode && parentNode.data.type === BlockEnum.Loop
  638. if (prevNode.parentId) {
  639. newNode.data.isInIteration = isInIteration
  640. newNode.data.isInLoop = isInLoop
  641. if (isInIteration) {
  642. newNode.data.iteration_id = parentNode.id
  643. newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
  644. }
  645. if (isInLoop) {
  646. newNode.data.loop_id = parentNode.id
  647. newNode.zIndex = LOOP_CHILDREN_Z_INDEX
  648. }
  649. if (isInIteration && (newNode.data.type === BlockEnum.Answer || newNode.data.type === BlockEnum.Tool || newNode.data.type === BlockEnum.Assigner)) {
  650. const iterNodeData: IterationNodeType = parentNode.data
  651. iterNodeData._isShowTips = true
  652. }
  653. if (isInLoop && (newNode.data.type === BlockEnum.Answer || newNode.data.type === BlockEnum.Tool || newNode.data.type === BlockEnum.Assigner)) {
  654. const iterNodeData: IterationNodeType = parentNode.data
  655. iterNodeData._isShowTips = true
  656. }
  657. }
  658. const newEdge: Edge = {
  659. id: `${prevNodeId}-${prevNodeSourceHandle}-${newNode.id}-${targetHandle}`,
  660. type: CUSTOM_EDGE,
  661. source: prevNodeId,
  662. sourceHandle: prevNodeSourceHandle,
  663. target: newNode.id,
  664. targetHandle,
  665. data: {
  666. sourceType: prevNode.data.type,
  667. targetType: newNode.data.type,
  668. isInIteration,
  669. isInLoop,
  670. iteration_id: isInIteration ? prevNode.parentId : undefined,
  671. loop_id: isInLoop ? prevNode.parentId : undefined,
  672. _connectedNodeIsSelected: true,
  673. },
  674. zIndex: prevNode.parentId ? (isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0,
  675. }
  676. const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
  677. [
  678. { type: 'add', edge: newEdge },
  679. ],
  680. nodes,
  681. )
  682. const newNodes = produce(nodes, (draft: Node[]) => {
  683. draft.forEach((node) => {
  684. node.data.selected = false
  685. if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
  686. node.data = {
  687. ...node.data,
  688. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  689. }
  690. }
  691. if (node.data.type === BlockEnum.Iteration && prevNode.parentId === node.id)
  692. node.data._children?.push(newNode.id)
  693. if (node.data.type === BlockEnum.Loop && prevNode.parentId === node.id)
  694. node.data._children?.push(newNode.id)
  695. })
  696. draft.push(newNode)
  697. if (newIterationStartNode)
  698. draft.push(newIterationStartNode)
  699. if (newLoopStartNode)
  700. draft.push(newLoopStartNode)
  701. })
  702. if (newNode.data.type === BlockEnum.VariableAssigner || newNode.data.type === BlockEnum.VariableAggregator) {
  703. const { setShowAssignVariablePopup } = workflowStore.getState()
  704. setShowAssignVariablePopup({
  705. nodeId: prevNode.id,
  706. nodeData: prevNode.data,
  707. variableAssignerNodeId: newNode.id,
  708. variableAssignerNodeData: (newNode.data as VariableAssignerNodeType),
  709. variableAssignerNodeHandleId: targetHandle,
  710. parentNode: nodes.find(node => node.id === newNode.parentId),
  711. x: -25,
  712. y: 44,
  713. })
  714. }
  715. const newEdges = produce(edges, (draft) => {
  716. draft.forEach((item) => {
  717. item.data = {
  718. ...item.data,
  719. _connectedNodeIsSelected: false,
  720. }
  721. })
  722. draft.push(newEdge)
  723. })
  724. if (checkNestedParallelLimit(newNodes, newEdges, prevNode.parentId)) {
  725. setNodes(newNodes)
  726. setEdges(newEdges)
  727. }
  728. else {
  729. return false
  730. }
  731. }
  732. if (!prevNodeId && nextNodeId) {
  733. const nextNodeIndex = nodes.findIndex(node => node.id === nextNodeId)
  734. const nextNode = nodes[nextNodeIndex]!
  735. if ((nodeType !== BlockEnum.IfElse) && (nodeType !== BlockEnum.QuestionClassifier))
  736. newNode.data._connectedSourceHandleIds = [sourceHandle]
  737. newNode.data._connectedTargetHandleIds = []
  738. newNode.position = {
  739. x: nextNode.position.x,
  740. y: nextNode.position.y,
  741. }
  742. newNode.parentId = nextNode.parentId
  743. newNode.extent = nextNode.extent
  744. const parentNode = nodes.find(node => node.id === nextNode.parentId) || null
  745. const isInIteration = !!parentNode && parentNode.data.type === BlockEnum.Iteration
  746. const isInLoop = !!parentNode && parentNode.data.type === BlockEnum.Loop
  747. if (parentNode && nextNode.parentId) {
  748. newNode.data.isInIteration = isInIteration
  749. newNode.data.isInLoop = isInLoop
  750. if (isInIteration) {
  751. newNode.data.iteration_id = parentNode.id
  752. newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
  753. }
  754. if (isInLoop) {
  755. newNode.data.loop_id = parentNode.id
  756. newNode.zIndex = LOOP_CHILDREN_Z_INDEX
  757. }
  758. }
  759. let newEdge
  760. if ((nodeType !== BlockEnum.IfElse) && (nodeType !== BlockEnum.QuestionClassifier)) {
  761. newEdge = {
  762. id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`,
  763. type: CUSTOM_EDGE,
  764. source: newNode.id,
  765. sourceHandle,
  766. target: nextNodeId,
  767. targetHandle: nextNodeTargetHandle,
  768. data: {
  769. sourceType: newNode.data.type,
  770. targetType: nextNode.data.type,
  771. isInIteration,
  772. isInLoop,
  773. iteration_id: isInIteration ? nextNode.parentId : undefined,
  774. loop_id: isInLoop ? nextNode.parentId : undefined,
  775. _connectedNodeIsSelected: true,
  776. },
  777. zIndex: nextNode.parentId ? (isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0,
  778. }
  779. }
  780. let nodesConnectedSourceOrTargetHandleIdsMap: Record<string, any>
  781. if (newEdge) {
  782. nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
  783. [
  784. { type: 'add', edge: newEdge },
  785. ],
  786. nodes,
  787. )
  788. }
  789. const afterNodesInSameBranch = getAfterNodesInSameBranch(nextNodeId!)
  790. const afterNodesInSameBranchIds = afterNodesInSameBranch.map(node => node.id)
  791. const newNodes = produce(nodes, (draft) => {
  792. draft.forEach((node) => {
  793. node.data.selected = false
  794. if (afterNodesInSameBranchIds.includes(node.id))
  795. node.position.x += NODE_WIDTH_X_OFFSET
  796. if (nodesConnectedSourceOrTargetHandleIdsMap?.[node.id]) {
  797. node.data = {
  798. ...node.data,
  799. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  800. }
  801. }
  802. if (node.data.type === BlockEnum.Iteration && nextNode.parentId === node.id)
  803. node.data._children?.push(newNode.id)
  804. if (node.data.type === BlockEnum.Iteration && node.data.start_node_id === nextNodeId) {
  805. node.data.start_node_id = newNode.id
  806. node.data.startNodeType = newNode.data.type
  807. }
  808. if (node.data.type === BlockEnum.Loop && nextNode.parentId === node.id)
  809. node.data._children?.push(newNode.id)
  810. if (node.data.type === BlockEnum.Loop && node.data.start_node_id === nextNodeId) {
  811. node.data.start_node_id = newNode.id
  812. node.data.startNodeType = newNode.data.type
  813. }
  814. })
  815. draft.push(newNode)
  816. if (newIterationStartNode)
  817. draft.push(newIterationStartNode)
  818. if (newLoopStartNode)
  819. draft.push(newLoopStartNode)
  820. })
  821. if (newEdge) {
  822. const newEdges = produce(edges, (draft) => {
  823. draft.forEach((item) => {
  824. item.data = {
  825. ...item.data,
  826. _connectedNodeIsSelected: false,
  827. }
  828. })
  829. draft.push(newEdge)
  830. })
  831. if (checkNestedParallelLimit(newNodes, newEdges, nextNode.parentId)) {
  832. setNodes(newNodes)
  833. setEdges(newEdges)
  834. }
  835. else {
  836. return false
  837. }
  838. }
  839. else {
  840. if (checkNestedParallelLimit(newNodes, edges))
  841. setNodes(newNodes)
  842. else
  843. return false
  844. }
  845. }
  846. if (prevNodeId && nextNodeId) {
  847. const prevNode = nodes.find(node => node.id === prevNodeId)!
  848. const nextNode = nodes.find(node => node.id === nextNodeId)!
  849. newNode.data._connectedTargetHandleIds = [targetHandle]
  850. newNode.data._connectedSourceHandleIds = [sourceHandle]
  851. newNode.position = {
  852. x: nextNode.position.x,
  853. y: nextNode.position.y,
  854. }
  855. newNode.parentId = prevNode.parentId
  856. newNode.extent = prevNode.extent
  857. const parentNode = nodes.find(node => node.id === prevNode.parentId) || null
  858. const isInIteration = !!parentNode && parentNode.data.type === BlockEnum.Iteration
  859. const isInLoop = !!parentNode && parentNode.data.type === BlockEnum.Loop
  860. if (parentNode && prevNode.parentId) {
  861. newNode.data.isInIteration = isInIteration
  862. newNode.data.isInLoop = isInLoop
  863. if (isInIteration) {
  864. newNode.data.iteration_id = parentNode.id
  865. newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
  866. }
  867. if (isInLoop) {
  868. newNode.data.loop_id = parentNode.id
  869. newNode.zIndex = LOOP_CHILDREN_Z_INDEX
  870. }
  871. }
  872. const currentEdgeIndex = edges.findIndex(edge => edge.source === prevNodeId && edge.target === nextNodeId)
  873. const newPrevEdge = {
  874. id: `${prevNodeId}-${prevNodeSourceHandle}-${newNode.id}-${targetHandle}`,
  875. type: CUSTOM_EDGE,
  876. source: prevNodeId,
  877. sourceHandle: prevNodeSourceHandle,
  878. target: newNode.id,
  879. targetHandle,
  880. data: {
  881. sourceType: prevNode.data.type,
  882. targetType: newNode.data.type,
  883. isInIteration,
  884. isInLoop,
  885. iteration_id: isInIteration ? prevNode.parentId : undefined,
  886. loop_id: isInLoop ? prevNode.parentId : undefined,
  887. _connectedNodeIsSelected: true,
  888. },
  889. zIndex: prevNode.parentId ? (isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0,
  890. }
  891. let newNextEdge: Edge | null = null
  892. const nextNodeParentNode = nodes.find(node => node.id === nextNode.parentId) || null
  893. const isNextNodeInIteration = !!nextNodeParentNode && nextNodeParentNode.data.type === BlockEnum.Iteration
  894. const isNextNodeInLoop = !!nextNodeParentNode && nextNodeParentNode.data.type === BlockEnum.Loop
  895. if (nodeType !== BlockEnum.IfElse && nodeType !== BlockEnum.QuestionClassifier) {
  896. newNextEdge = {
  897. id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`,
  898. type: CUSTOM_EDGE,
  899. source: newNode.id,
  900. sourceHandle,
  901. target: nextNodeId,
  902. targetHandle: nextNodeTargetHandle,
  903. data: {
  904. sourceType: newNode.data.type,
  905. targetType: nextNode.data.type,
  906. isInIteration: isNextNodeInIteration,
  907. isInLoop: isNextNodeInLoop,
  908. iteration_id: isNextNodeInIteration ? nextNode.parentId : undefined,
  909. loop_id: isNextNodeInLoop ? nextNode.parentId : undefined,
  910. _connectedNodeIsSelected: true,
  911. },
  912. zIndex: nextNode.parentId ? (isNextNodeInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0,
  913. }
  914. }
  915. const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
  916. [
  917. { type: 'remove', edge: edges[currentEdgeIndex] },
  918. { type: 'add', edge: newPrevEdge },
  919. ...(newNextEdge ? [{ type: 'add', edge: newNextEdge }] : []),
  920. ],
  921. [...nodes, newNode],
  922. )
  923. const afterNodesInSameBranch = getAfterNodesInSameBranch(nextNodeId!)
  924. const afterNodesInSameBranchIds = afterNodesInSameBranch.map(node => node.id)
  925. const newNodes = produce(nodes, (draft) => {
  926. draft.forEach((node) => {
  927. node.data.selected = false
  928. if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
  929. node.data = {
  930. ...node.data,
  931. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  932. }
  933. }
  934. if (afterNodesInSameBranchIds.includes(node.id))
  935. node.position.x += NODE_WIDTH_X_OFFSET
  936. if (node.data.type === BlockEnum.Iteration && prevNode.parentId === node.id)
  937. node.data._children?.push(newNode.id)
  938. if (node.data.type === BlockEnum.Loop && prevNode.parentId === node.id)
  939. node.data._children?.push(newNode.id)
  940. })
  941. draft.push(newNode)
  942. if (newIterationStartNode)
  943. draft.push(newIterationStartNode)
  944. if (newLoopStartNode)
  945. draft.push(newLoopStartNode)
  946. })
  947. setNodes(newNodes)
  948. if (newNode.data.type === BlockEnum.VariableAssigner || newNode.data.type === BlockEnum.VariableAggregator) {
  949. const { setShowAssignVariablePopup } = workflowStore.getState()
  950. setShowAssignVariablePopup({
  951. nodeId: prevNode.id,
  952. nodeData: prevNode.data,
  953. variableAssignerNodeId: newNode.id,
  954. variableAssignerNodeData: newNode.data as VariableAssignerNodeType,
  955. variableAssignerNodeHandleId: targetHandle,
  956. parentNode: nodes.find(node => node.id === newNode.parentId),
  957. x: -25,
  958. y: 44,
  959. })
  960. }
  961. const newEdges = produce(edges, (draft) => {
  962. draft.splice(currentEdgeIndex, 1)
  963. draft.forEach((item) => {
  964. item.data = {
  965. ...item.data,
  966. _connectedNodeIsSelected: false,
  967. }
  968. })
  969. draft.push(newPrevEdge)
  970. if (newNextEdge)
  971. draft.push(newNextEdge)
  972. })
  973. setEdges(newEdges)
  974. }
  975. handleSyncWorkflowDraft()
  976. saveStateToHistory(WorkflowHistoryEvent.NodeAdd)
  977. }, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, getAfterNodesInSameBranch, checkNestedParallelLimit])
  978. const handleNodeChange = useCallback((
  979. currentNodeId: string,
  980. nodeType: BlockEnum,
  981. sourceHandle: string,
  982. toolDefaultValue?: ToolDefaultValue,
  983. ) => {
  984. if (getNodesReadOnly())
  985. return
  986. const {
  987. getNodes,
  988. setNodes,
  989. edges,
  990. setEdges,
  991. } = store.getState()
  992. const nodes = getNodes()
  993. const currentNode = nodes.find(node => node.id === currentNodeId)!
  994. const connectedEdges = getConnectedEdges([currentNode], edges)
  995. const nodesWithSameType = nodes.filter(node => node.data.type === nodeType)
  996. const {
  997. newNode: newCurrentNode,
  998. newIterationStartNode,
  999. newLoopStartNode,
  1000. } = generateNewNode({
  1001. data: {
  1002. ...NODES_INITIAL_DATA[nodeType],
  1003. title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`),
  1004. ...(toolDefaultValue || {}),
  1005. _connectedSourceHandleIds: [],
  1006. _connectedTargetHandleIds: [],
  1007. selected: currentNode.data.selected,
  1008. isInIteration: currentNode.data.isInIteration,
  1009. isInLoop: currentNode.data.isInLoop,
  1010. iteration_id: currentNode.data.iteration_id,
  1011. loop_id: currentNode.data.loop_id,
  1012. },
  1013. position: {
  1014. x: currentNode.position.x,
  1015. y: currentNode.position.y,
  1016. },
  1017. parentId: currentNode.parentId,
  1018. extent: currentNode.extent,
  1019. zIndex: currentNode.zIndex,
  1020. })
  1021. const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
  1022. [
  1023. ...connectedEdges.map(edge => ({ type: 'remove', edge })),
  1024. ],
  1025. nodes,
  1026. )
  1027. const newNodes = produce(nodes, (draft) => {
  1028. draft.forEach((node) => {
  1029. node.data.selected = false
  1030. if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
  1031. node.data = {
  1032. ...node.data,
  1033. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  1034. }
  1035. }
  1036. })
  1037. const index = draft.findIndex(node => node.id === currentNodeId)
  1038. draft.splice(index, 1, newCurrentNode)
  1039. if (newIterationStartNode)
  1040. draft.push(newIterationStartNode)
  1041. if (newLoopStartNode)
  1042. draft.push(newLoopStartNode)
  1043. })
  1044. setNodes(newNodes)
  1045. const newEdges = produce(edges, (draft) => {
  1046. const filtered = draft.filter(edge => !connectedEdges.find(connectedEdge => connectedEdge.id === edge.id))
  1047. return filtered
  1048. })
  1049. setEdges(newEdges)
  1050. handleSyncWorkflowDraft()
  1051. saveStateToHistory(WorkflowHistoryEvent.NodeChange)
  1052. }, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory])
  1053. const handleNodeCancelRunningStatus = useCallback(() => {
  1054. const {
  1055. getNodes,
  1056. setNodes,
  1057. } = store.getState()
  1058. const nodes = getNodes()
  1059. const newNodes = produce(nodes, (draft) => {
  1060. draft.forEach((node) => {
  1061. node.data._runningStatus = undefined
  1062. node.data._waitingRun = false
  1063. })
  1064. })
  1065. setNodes(newNodes)
  1066. }, [store])
  1067. const handleNodesCancelSelected = useCallback(() => {
  1068. const {
  1069. getNodes,
  1070. setNodes,
  1071. } = store.getState()
  1072. const nodes = getNodes()
  1073. const newNodes = produce(nodes, (draft) => {
  1074. draft.forEach((node) => {
  1075. node.data.selected = false
  1076. })
  1077. })
  1078. setNodes(newNodes)
  1079. }, [store])
  1080. const handleNodeContextMenu = useCallback((e: MouseEvent, node: Node) => {
  1081. if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE)
  1082. return
  1083. if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_LOOP_START_NODE)
  1084. return
  1085. e.preventDefault()
  1086. const container = document.querySelector('#workflow-container')
  1087. const { x, y } = container!.getBoundingClientRect()
  1088. workflowStore.setState({
  1089. nodeMenu: {
  1090. top: e.clientY - y,
  1091. left: e.clientX - x,
  1092. nodeId: node.id,
  1093. },
  1094. })
  1095. handleNodeSelect(node.id)
  1096. }, [workflowStore, handleNodeSelect])
  1097. const handleNodesCopy = useCallback((nodeId?: string) => {
  1098. if (getNodesReadOnly())
  1099. return
  1100. const { setClipboardElements } = workflowStore.getState()
  1101. const {
  1102. getNodes,
  1103. } = store.getState()
  1104. const nodes = getNodes()
  1105. if (nodeId) {
  1106. // If nodeId is provided, copy that specific node
  1107. const nodeToCopy = nodes.find(node => node.id === nodeId && node.data.type !== BlockEnum.Start
  1108. && node.type !== CUSTOM_ITERATION_START_NODE && node.type !== CUSTOM_LOOP_START_NODE)
  1109. if (nodeToCopy)
  1110. setClipboardElements([nodeToCopy])
  1111. }
  1112. else {
  1113. // If no nodeId is provided, fall back to the current behavior
  1114. const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start
  1115. && !node.data.isInIteration && !node.data.isInLoop)
  1116. if (bundledNodes.length) {
  1117. setClipboardElements(bundledNodes)
  1118. return
  1119. }
  1120. const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start)
  1121. if (selectedNode)
  1122. setClipboardElements([selectedNode])
  1123. }
  1124. }, [getNodesReadOnly, store, workflowStore])
  1125. const handleNodesPaste = useCallback(() => {
  1126. if (getNodesReadOnly())
  1127. return
  1128. const {
  1129. clipboardElements,
  1130. mousePosition,
  1131. } = workflowStore.getState()
  1132. const {
  1133. getNodes,
  1134. setNodes,
  1135. edges,
  1136. setEdges,
  1137. } = store.getState()
  1138. const nodesToPaste: Node[] = []
  1139. const edgesToPaste: Edge[] = []
  1140. const nodes = getNodes()
  1141. if (clipboardElements.length) {
  1142. const { x, y } = getTopLeftNodePosition(clipboardElements)
  1143. const { screenToFlowPosition } = reactflow
  1144. const currentPosition = screenToFlowPosition({ x: mousePosition.pageX, y: mousePosition.pageY })
  1145. const offsetX = currentPosition.x - x
  1146. const offsetY = currentPosition.y - y
  1147. let idMapping: Record<string, string> = {}
  1148. clipboardElements.forEach((nodeToPaste, index) => {
  1149. const nodeType = nodeToPaste.data.type
  1150. const {
  1151. newNode,
  1152. newIterationStartNode,
  1153. newLoopStartNode,
  1154. } = generateNewNode({
  1155. type: nodeToPaste.type,
  1156. data: {
  1157. ...NODES_INITIAL_DATA[nodeType],
  1158. ...nodeToPaste.data,
  1159. selected: false,
  1160. _isBundled: false,
  1161. _connectedSourceHandleIds: [],
  1162. _connectedTargetHandleIds: [],
  1163. title: genNewNodeTitleFromOld(nodeToPaste.data.title),
  1164. },
  1165. position: {
  1166. x: nodeToPaste.position.x + offsetX,
  1167. y: nodeToPaste.position.y + offsetY,
  1168. },
  1169. extent: nodeToPaste.extent,
  1170. zIndex: nodeToPaste.zIndex,
  1171. })
  1172. newNode.id = newNode.id + index
  1173. // This new node is movable and can be placed anywhere
  1174. let newChildren: Node[] = []
  1175. if (nodeToPaste.data.type === BlockEnum.Iteration) {
  1176. newIterationStartNode!.parentId = newNode.id;
  1177. (newNode.data as IterationNodeType).start_node_id = newIterationStartNode!.id
  1178. const oldIterationStartNode = nodes
  1179. .find(n => n.parentId === nodeToPaste.id && n.type === CUSTOM_ITERATION_START_NODE)
  1180. idMapping[oldIterationStartNode!.id] = newIterationStartNode!.id
  1181. const { copyChildren, newIdMapping } = handleNodeIterationChildrenCopy(nodeToPaste.id, newNode.id, idMapping)
  1182. newChildren = copyChildren
  1183. idMapping = newIdMapping
  1184. newChildren.forEach((child) => {
  1185. newNode.data._children?.push(child.id)
  1186. })
  1187. newChildren.push(newIterationStartNode!)
  1188. }
  1189. if (nodeToPaste.data.type === BlockEnum.Loop) {
  1190. newLoopStartNode!.parentId = newNode.id;
  1191. (newNode.data as LoopNodeType).start_node_id = newLoopStartNode!.id
  1192. newChildren = handleNodeLoopChildrenCopy(nodeToPaste.id, newNode.id)
  1193. newChildren.forEach((child) => {
  1194. newNode.data._children?.push(child.id)
  1195. })
  1196. newChildren.push(newLoopStartNode!)
  1197. }
  1198. nodesToPaste.push(newNode)
  1199. if (newChildren.length)
  1200. nodesToPaste.push(...newChildren)
  1201. })
  1202. edges.forEach((edge) => {
  1203. const sourceId = idMapping[edge.source]
  1204. const targetId = idMapping[edge.target]
  1205. if (sourceId && targetId) {
  1206. const newEdge: Edge = {
  1207. ...edge,
  1208. id: `${sourceId}-${edge.sourceHandle}-${targetId}-${edge.targetHandle}`,
  1209. source: sourceId,
  1210. target: targetId,
  1211. data: {
  1212. ...edge.data,
  1213. _connectedNodeIsSelected: false,
  1214. },
  1215. }
  1216. edgesToPaste.push(newEdge)
  1217. }
  1218. })
  1219. setNodes([...nodes, ...nodesToPaste])
  1220. setEdges([...edges, ...edgesToPaste])
  1221. saveStateToHistory(WorkflowHistoryEvent.NodePaste)
  1222. handleSyncWorkflowDraft()
  1223. }
  1224. }, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy, handleNodeLoopChildrenCopy])
  1225. const handleNodesDuplicate = useCallback((nodeId?: string) => {
  1226. if (getNodesReadOnly())
  1227. return
  1228. handleNodesCopy(nodeId)
  1229. handleNodesPaste()
  1230. }, [getNodesReadOnly, handleNodesCopy, handleNodesPaste])
  1231. const handleNodesDelete = useCallback(() => {
  1232. if (getNodesReadOnly())
  1233. return
  1234. const {
  1235. getNodes,
  1236. edges,
  1237. } = store.getState()
  1238. const nodes = getNodes()
  1239. const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start)
  1240. if (bundledNodes.length) {
  1241. bundledNodes.forEach(node => handleNodeDelete(node.id))
  1242. return
  1243. }
  1244. const edgeSelected = edges.some(edge => edge.selected)
  1245. if (edgeSelected)
  1246. return
  1247. const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start)
  1248. if (selectedNode)
  1249. handleNodeDelete(selectedNode.id)
  1250. }, [store, getNodesReadOnly, handleNodeDelete])
  1251. const handleNodeResize = useCallback((nodeId: string, params: ResizeParamsWithDirection) => {
  1252. if (getNodesReadOnly())
  1253. return
  1254. const {
  1255. getNodes,
  1256. setNodes,
  1257. } = store.getState()
  1258. const { x, y, width, height } = params
  1259. const nodes = getNodes()
  1260. const currentNode = nodes.find(n => n.id === nodeId)!
  1261. const childrenNodes = nodes.filter(n => currentNode.data._children?.includes(n.id))
  1262. let rightNode: Node
  1263. let bottomNode: Node
  1264. childrenNodes.forEach((n) => {
  1265. if (rightNode) {
  1266. if (n.position.x + n.width! > rightNode.position.x + rightNode.width!)
  1267. rightNode = n
  1268. }
  1269. else {
  1270. rightNode = n
  1271. }
  1272. if (bottomNode) {
  1273. if (n.position.y + n.height! > bottomNode.position.y + bottomNode.height!)
  1274. bottomNode = n
  1275. }
  1276. else {
  1277. bottomNode = n
  1278. }
  1279. })
  1280. if (rightNode! && bottomNode!) {
  1281. const parentNode = nodes.find(n => n.id === rightNode.parentId)
  1282. const paddingMap = parentNode?.data.type === BlockEnum.Iteration ? ITERATION_PADDING : LOOP_PADDING
  1283. if (width < rightNode!.position.x + rightNode.width! + paddingMap.right)
  1284. return
  1285. if (height < bottomNode.position.y + bottomNode.height! + paddingMap.bottom)
  1286. return
  1287. }
  1288. const newNodes = produce(nodes, (draft) => {
  1289. draft.forEach((n) => {
  1290. if (n.id === nodeId) {
  1291. n.data.width = width
  1292. n.data.height = height
  1293. n.width = width
  1294. n.height = height
  1295. n.position.x = x
  1296. n.position.y = y
  1297. }
  1298. })
  1299. })
  1300. setNodes(newNodes)
  1301. handleSyncWorkflowDraft()
  1302. saveStateToHistory(WorkflowHistoryEvent.NodeResize)
  1303. }, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory])
  1304. const handleNodeDisconnect = useCallback((nodeId: string) => {
  1305. if (getNodesReadOnly())
  1306. return
  1307. const {
  1308. getNodes,
  1309. setNodes,
  1310. edges,
  1311. setEdges,
  1312. } = store.getState()
  1313. const nodes = getNodes()
  1314. const currentNode = nodes.find(node => node.id === nodeId)!
  1315. const connectedEdges = getConnectedEdges([currentNode], edges)
  1316. const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
  1317. connectedEdges.map(edge => ({ type: 'remove', edge })),
  1318. nodes,
  1319. )
  1320. const newNodes = produce(nodes, (draft: Node[]) => {
  1321. draft.forEach((node) => {
  1322. if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
  1323. node.data = {
  1324. ...node.data,
  1325. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  1326. }
  1327. }
  1328. })
  1329. })
  1330. setNodes(newNodes)
  1331. const newEdges = produce(edges, (draft) => {
  1332. return draft.filter(edge => !connectedEdges.find(connectedEdge => connectedEdge.id === edge.id))
  1333. })
  1334. setEdges(newEdges)
  1335. handleSyncWorkflowDraft()
  1336. saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
  1337. }, [store, getNodesReadOnly, handleSyncWorkflowDraft, saveStateToHistory])
  1338. const handleHistoryBack = useCallback(() => {
  1339. if (getNodesReadOnly() || getWorkflowReadOnly())
  1340. return
  1341. const { setEdges, setNodes } = store.getState()
  1342. undo()
  1343. const { edges, nodes } = workflowHistoryStore.getState()
  1344. if (edges.length === 0 && nodes.length === 0)
  1345. return
  1346. setEdges(edges)
  1347. setNodes(nodes)
  1348. }, [store, undo, workflowHistoryStore, getNodesReadOnly, getWorkflowReadOnly])
  1349. const handleHistoryForward = useCallback(() => {
  1350. if (getNodesReadOnly() || getWorkflowReadOnly())
  1351. return
  1352. const { setEdges, setNodes } = store.getState()
  1353. redo()
  1354. const { edges, nodes } = workflowHistoryStore.getState()
  1355. if (edges.length === 0 && nodes.length === 0)
  1356. return
  1357. setEdges(edges)
  1358. setNodes(nodes)
  1359. }, [redo, store, workflowHistoryStore, getNodesReadOnly, getWorkflowReadOnly])
  1360. return {
  1361. handleNodeDragStart,
  1362. handleNodeDrag,
  1363. handleNodeDragStop,
  1364. handleNodeEnter,
  1365. handleNodeLeave,
  1366. handleNodeSelect,
  1367. handleNodeClick,
  1368. handleNodeConnect,
  1369. handleNodeConnectStart,
  1370. handleNodeConnectEnd,
  1371. handleNodeDelete,
  1372. handleNodeChange,
  1373. handleNodeAdd,
  1374. handleNodeCancelRunningStatus,
  1375. handleNodesCancelSelected,
  1376. handleNodeContextMenu,
  1377. handleNodesCopy,
  1378. handleNodesPaste,
  1379. handleNodesDuplicate,
  1380. handleNodesDelete,
  1381. handleNodeResize,
  1382. handleNodeDisconnect,
  1383. handleHistoryBack,
  1384. handleHistoryForward,
  1385. }
  1386. }