Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

use-nodes-interactions.ts 51KB

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