You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

use-nodes-interactions.ts 62KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974
  1. import type { MouseEvent } from 'react'
  2. import { useCallback, useRef, useState } 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 type { ToolDefaultValue } from '../block-selector/types'
  20. import type { Edge, Node, OnNodeAdd } from '../types'
  21. import { BlockEnum } from '../types'
  22. import { useWorkflowStore } from '../store'
  23. import {
  24. CUSTOM_EDGE,
  25. ITERATION_CHILDREN_Z_INDEX,
  26. ITERATION_PADDING,
  27. LOOP_CHILDREN_Z_INDEX,
  28. LOOP_PADDING,
  29. NODE_WIDTH_X_OFFSET,
  30. X_OFFSET,
  31. Y_OFFSET,
  32. } from '../constants'
  33. import {
  34. genNewNodeTitleFromOld,
  35. generateNewNode,
  36. getNestedNodePosition,
  37. getNodeCustomTypeByNodeDataType,
  38. getNodesConnectedSourceOrTargetHandleIdsMap,
  39. getTopLeftNodePosition,
  40. } from '../utils'
  41. import { CUSTOM_NOTE_NODE } from '../note-node/constants'
  42. import type { IterationNodeType } from '../nodes/iteration/types'
  43. import type { LoopNodeType } from '../nodes/loop/types'
  44. import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants'
  45. import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants'
  46. import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types'
  47. import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions'
  48. import { useNodeLoopInteractions } from '../nodes/loop/use-interactions'
  49. import { useWorkflowHistoryStore } from '../workflow-history-store'
  50. import { useNodesSyncDraft } from './use-nodes-sync-draft'
  51. import { useHelpline } from './use-helpline'
  52. import {
  53. useNodesReadOnly,
  54. useWorkflow,
  55. useWorkflowReadOnly,
  56. } from './use-workflow'
  57. import {
  58. WorkflowHistoryEvent,
  59. useWorkflowHistory,
  60. } from './use-workflow-history'
  61. import { useNodesMetaData } from './use-nodes-meta-data'
  62. import type { RAGPipelineVariables } from '@/models/pipeline'
  63. import useInspectVarsCrud from './use-inspect-vars-crud'
  64. import { getNodeUsedVars } from '../nodes/_base/components/variable/utils'
  65. export const useNodesInteractions = () => {
  66. const { t } = useTranslation()
  67. const store = useStoreApi()
  68. const workflowStore = useWorkflowStore()
  69. const reactflow = useReactFlow()
  70. const { store: workflowHistoryStore } = useWorkflowHistoryStore()
  71. const { handleSyncWorkflowDraft } = useNodesSyncDraft()
  72. const { getAfterNodesInSameBranch } = useWorkflow()
  73. const { getNodesReadOnly } = useNodesReadOnly()
  74. const { getWorkflowReadOnly } = useWorkflowReadOnly()
  75. const { handleSetHelpline } = useHelpline()
  76. const { handleNodeIterationChildDrag, handleNodeIterationChildrenCopy }
  77. = useNodeIterationInteractions()
  78. const { handleNodeLoopChildDrag, handleNodeLoopChildrenCopy }
  79. = useNodeLoopInteractions()
  80. const dragNodeStartPosition = useRef({ x: 0, y: 0 } as {
  81. x: number;
  82. y: number;
  83. })
  84. const { nodesMap: nodesMetaDataMap } = useNodesMetaData()
  85. const { saveStateToHistory, undo, redo } = useWorkflowHistory()
  86. const handleNodeDragStart = useCallback<NodeDragHandler>(
  87. (_, node) => {
  88. workflowStore.setState({ nodeAnimation: false })
  89. if (getNodesReadOnly()) return
  90. if (
  91. node.type === CUSTOM_ITERATION_START_NODE
  92. || node.type === CUSTOM_NOTE_NODE
  93. )
  94. return
  95. if (
  96. node.type === CUSTOM_LOOP_START_NODE
  97. || node.type === CUSTOM_NOTE_NODE
  98. )
  99. return
  100. dragNodeStartPosition.current = {
  101. x: node.position.x,
  102. y: node.position.y,
  103. }
  104. },
  105. [workflowStore, getNodesReadOnly],
  106. )
  107. const handleNodeDrag = useCallback<NodeDragHandler>(
  108. (e, node: Node) => {
  109. if (getNodesReadOnly()) return
  110. if (node.type === CUSTOM_ITERATION_START_NODE) return
  111. if (node.type === CUSTOM_LOOP_START_NODE) return
  112. const { getNodes, setNodes } = store.getState()
  113. e.stopPropagation()
  114. const nodes = getNodes()
  115. const { restrictPosition } = handleNodeIterationChildDrag(node)
  116. const { restrictPosition: restrictLoopPosition }
  117. = handleNodeLoopChildDrag(node)
  118. const { showHorizontalHelpLineNodes, showVerticalHelpLineNodes }
  119. = handleSetHelpline(node)
  120. const showHorizontalHelpLineNodesLength
  121. = showHorizontalHelpLineNodes.length
  122. const showVerticalHelpLineNodesLength = showVerticalHelpLineNodes.length
  123. const newNodes = produce(nodes, (draft) => {
  124. const currentNode = draft.find(n => n.id === node.id)!
  125. if (showVerticalHelpLineNodesLength > 0)
  126. currentNode.position.x = showVerticalHelpLineNodes[0].position.x
  127. else if (restrictPosition.x !== undefined)
  128. currentNode.position.x = restrictPosition.x
  129. else if (restrictLoopPosition.x !== undefined)
  130. currentNode.position.x = restrictLoopPosition.x
  131. else currentNode.position.x = node.position.x
  132. if (showHorizontalHelpLineNodesLength > 0)
  133. currentNode.position.y = showHorizontalHelpLineNodes[0].position.y
  134. else if (restrictPosition.y !== undefined)
  135. currentNode.position.y = restrictPosition.y
  136. else if (restrictLoopPosition.y !== undefined)
  137. currentNode.position.y = restrictLoopPosition.y
  138. else currentNode.position.y = node.position.y
  139. })
  140. setNodes(newNodes)
  141. },
  142. [
  143. getNodesReadOnly,
  144. store,
  145. handleNodeIterationChildDrag,
  146. handleNodeLoopChildDrag,
  147. handleSetHelpline,
  148. ],
  149. )
  150. const handleNodeDragStop = useCallback<NodeDragHandler>(
  151. (_, node) => {
  152. const { setHelpLineHorizontal, setHelpLineVertical }
  153. = workflowStore.getState()
  154. if (getNodesReadOnly()) return
  155. const { x, y } = dragNodeStartPosition.current
  156. if (!(x === node.position.x && y === node.position.y)) {
  157. setHelpLineHorizontal()
  158. setHelpLineVertical()
  159. handleSyncWorkflowDraft()
  160. if (x !== 0 && y !== 0) {
  161. // selecting a note will trigger a drag stop event with x and y as 0
  162. saveStateToHistory(WorkflowHistoryEvent.NodeDragStop, {
  163. nodeId: node.id,
  164. })
  165. }
  166. }
  167. },
  168. [
  169. workflowStore,
  170. getNodesReadOnly,
  171. saveStateToHistory,
  172. handleSyncWorkflowDraft,
  173. ],
  174. )
  175. const handleNodeEnter = useCallback<NodeMouseHandler>(
  176. (_, node) => {
  177. if (getNodesReadOnly()) return
  178. if (
  179. node.type === CUSTOM_NOTE_NODE
  180. || node.type === CUSTOM_ITERATION_START_NODE
  181. )
  182. return
  183. if (
  184. node.type === CUSTOM_LOOP_START_NODE
  185. || node.type === CUSTOM_NOTE_NODE
  186. )
  187. return
  188. const { getNodes, setNodes, edges, setEdges } = store.getState()
  189. const nodes = getNodes()
  190. const { connectingNodePayload, setEnteringNodePayload }
  191. = workflowStore.getState()
  192. if (connectingNodePayload) {
  193. if (connectingNodePayload.nodeId === node.id) return
  194. const connectingNode: Node = nodes.find(
  195. n => n.id === connectingNodePayload.nodeId,
  196. )!
  197. const sameLevel = connectingNode.parentId === node.parentId
  198. if (sameLevel) {
  199. setEnteringNodePayload({
  200. nodeId: node.id,
  201. nodeData: node.data as VariableAssignerNodeType,
  202. })
  203. const fromType = connectingNodePayload.handleType
  204. const newNodes = produce(nodes, (draft) => {
  205. draft.forEach((n) => {
  206. if (
  207. n.id === node.id
  208. && fromType === 'source'
  209. && (node.data.type === BlockEnum.VariableAssigner
  210. || node.data.type === BlockEnum.VariableAggregator)
  211. ) {
  212. if (!node.data.advanced_settings?.group_enabled)
  213. n.data._isEntering = true
  214. }
  215. if (
  216. n.id === node.id
  217. && fromType === 'target'
  218. && (connectingNode.data.type === BlockEnum.VariableAssigner
  219. || connectingNode.data.type === BlockEnum.VariableAggregator)
  220. && node.data.type !== BlockEnum.IfElse
  221. && node.data.type !== BlockEnum.QuestionClassifier
  222. )
  223. n.data._isEntering = true
  224. })
  225. })
  226. setNodes(newNodes)
  227. }
  228. }
  229. const newEdges = produce(edges, (draft) => {
  230. const connectedEdges = getConnectedEdges([node], edges)
  231. connectedEdges.forEach((edge) => {
  232. const currentEdge = draft.find(e => e.id === edge.id)
  233. if (currentEdge) currentEdge.data._connectedNodeIsHovering = true
  234. })
  235. })
  236. setEdges(newEdges)
  237. },
  238. [store, workflowStore, getNodesReadOnly],
  239. )
  240. const handleNodeLeave = useCallback<NodeMouseHandler>(
  241. (_, node) => {
  242. if (getNodesReadOnly()) return
  243. if (
  244. node.type === CUSTOM_NOTE_NODE
  245. || node.type === CUSTOM_ITERATION_START_NODE
  246. )
  247. return
  248. if (
  249. node.type === CUSTOM_NOTE_NODE
  250. || node.type === CUSTOM_LOOP_START_NODE
  251. )
  252. return
  253. const { setEnteringNodePayload } = workflowStore.getState()
  254. setEnteringNodePayload(undefined)
  255. const { getNodes, setNodes, edges, setEdges } = store.getState()
  256. const newNodes = produce(getNodes(), (draft) => {
  257. draft.forEach((node) => {
  258. node.data._isEntering = false
  259. })
  260. })
  261. setNodes(newNodes)
  262. const newEdges = produce(edges, (draft) => {
  263. draft.forEach((edge) => {
  264. edge.data._connectedNodeIsHovering = false
  265. })
  266. })
  267. setEdges(newEdges)
  268. },
  269. [store, workflowStore, getNodesReadOnly],
  270. )
  271. const handleNodeSelect = useCallback(
  272. (
  273. nodeId: string,
  274. cancelSelection?: boolean,
  275. initShowLastRunTab?: boolean,
  276. ) => {
  277. if (initShowLastRunTab)
  278. workflowStore.setState({ initShowLastRunTab: true })
  279. const { getNodes, setNodes, edges, setEdges } = store.getState()
  280. const nodes = getNodes()
  281. const selectedNode = nodes.find(node => node.data.selected)
  282. if (!cancelSelection && selectedNode?.id === nodeId) return
  283. const newNodes = produce(nodes, (draft) => {
  284. draft.forEach((node) => {
  285. if (node.id === nodeId) node.data.selected = !cancelSelection
  286. else node.data.selected = false
  287. })
  288. })
  289. setNodes(newNodes)
  290. const connectedEdges = getConnectedEdges(
  291. [{ id: nodeId } as Node],
  292. edges,
  293. ).map(edge => edge.id)
  294. const newEdges = produce(edges, (draft) => {
  295. draft.forEach((edge) => {
  296. if (connectedEdges.includes(edge.id)) {
  297. edge.data = {
  298. ...edge.data,
  299. _connectedNodeIsSelected: !cancelSelection,
  300. }
  301. }
  302. else {
  303. edge.data = {
  304. ...edge.data,
  305. _connectedNodeIsSelected: false,
  306. }
  307. }
  308. })
  309. })
  310. setEdges(newEdges)
  311. handleSyncWorkflowDraft()
  312. },
  313. [store, handleSyncWorkflowDraft],
  314. )
  315. const handleNodeClick = useCallback<NodeMouseHandler>(
  316. (_, node) => {
  317. if (node.type === CUSTOM_ITERATION_START_NODE) return
  318. if (node.type === CUSTOM_LOOP_START_NODE) return
  319. if (node.data.type === BlockEnum.DataSourceEmpty) return
  320. handleNodeSelect(node.id)
  321. },
  322. [handleNodeSelect],
  323. )
  324. const handleNodeConnect = useCallback<OnConnect>(
  325. ({ source, sourceHandle, target, targetHandle }) => {
  326. if (source === target) return
  327. if (getNodesReadOnly()) return
  328. const { getNodes, setNodes, edges, setEdges } = store.getState()
  329. const nodes = getNodes()
  330. const targetNode = nodes.find(node => node.id === target!)
  331. const sourceNode = nodes.find(node => node.id === source!)
  332. if (targetNode?.parentId !== sourceNode?.parentId) return
  333. if (
  334. sourceNode?.type === CUSTOM_NOTE_NODE
  335. || targetNode?.type === CUSTOM_NOTE_NODE
  336. )
  337. return
  338. if (
  339. edges.find(
  340. edge =>
  341. edge.source === source
  342. && edge.sourceHandle === sourceHandle
  343. && edge.target === target
  344. && edge.targetHandle === targetHandle,
  345. )
  346. )
  347. return
  348. const parendNode = nodes.find(node => node.id === targetNode?.parentId)
  349. const isInIteration
  350. = parendNode && parendNode.data.type === BlockEnum.Iteration
  351. const isInLoop = !!parendNode && parendNode.data.type === BlockEnum.Loop
  352. const newEdge = {
  353. id: `${source}-${sourceHandle}-${target}-${targetHandle}`,
  354. type: CUSTOM_EDGE,
  355. source: source!,
  356. target: target!,
  357. sourceHandle,
  358. targetHandle,
  359. data: {
  360. sourceType: nodes.find(node => node.id === source)!.data.type,
  361. targetType: nodes.find(node => node.id === target)!.data.type,
  362. isInIteration,
  363. iteration_id: isInIteration ? targetNode?.parentId : undefined,
  364. isInLoop,
  365. loop_id: isInLoop ? targetNode?.parentId : undefined,
  366. },
  367. zIndex: targetNode?.parentId
  368. ? isInIteration
  369. ? ITERATION_CHILDREN_Z_INDEX
  370. : LOOP_CHILDREN_Z_INDEX
  371. : 0,
  372. }
  373. const nodesConnectedSourceOrTargetHandleIdsMap
  374. = getNodesConnectedSourceOrTargetHandleIdsMap(
  375. [{ type: 'add', edge: newEdge }],
  376. nodes,
  377. )
  378. const newNodes = produce(nodes, (draft: Node[]) => {
  379. draft.forEach((node) => {
  380. if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
  381. node.data = {
  382. ...node.data,
  383. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  384. }
  385. }
  386. })
  387. })
  388. const newEdges = produce(edges, (draft) => {
  389. draft.push(newEdge)
  390. })
  391. setNodes(newNodes)
  392. setEdges(newEdges)
  393. handleSyncWorkflowDraft()
  394. saveStateToHistory(WorkflowHistoryEvent.NodeConnect, {
  395. nodeId: targetNode?.id,
  396. })
  397. },
  398. [
  399. getNodesReadOnly,
  400. store,
  401. workflowStore,
  402. handleSyncWorkflowDraft,
  403. saveStateToHistory,
  404. ],
  405. )
  406. const handleNodeConnectStart = useCallback<OnConnectStart>(
  407. (_, { nodeId, handleType, handleId }) => {
  408. if (getNodesReadOnly()) return
  409. if (nodeId && handleType) {
  410. const { setConnectingNodePayload } = workflowStore.getState()
  411. const { getNodes } = store.getState()
  412. const node = getNodes().find(n => n.id === nodeId)!
  413. if (node.type === CUSTOM_NOTE_NODE) return
  414. if (
  415. node.data.type === BlockEnum.VariableAggregator
  416. || node.data.type === BlockEnum.VariableAssigner
  417. )
  418. if (handleType === 'target') return
  419. setConnectingNodePayload({
  420. nodeId,
  421. nodeType: node.data.type,
  422. handleType,
  423. handleId,
  424. })
  425. }
  426. },
  427. [store, workflowStore, getNodesReadOnly],
  428. )
  429. const handleNodeConnectEnd = useCallback<OnConnectEnd>(
  430. (e: any) => {
  431. if (getNodesReadOnly()) return
  432. const {
  433. connectingNodePayload,
  434. setConnectingNodePayload,
  435. enteringNodePayload,
  436. setEnteringNodePayload,
  437. } = workflowStore.getState()
  438. if (connectingNodePayload && enteringNodePayload) {
  439. const { setShowAssignVariablePopup, hoveringAssignVariableGroupId }
  440. = workflowStore.getState()
  441. const { screenToFlowPosition } = reactflow
  442. const { getNodes, setNodes } = store.getState()
  443. const nodes = getNodes()
  444. const fromHandleType = connectingNodePayload.handleType
  445. const fromHandleId = connectingNodePayload.handleId
  446. const fromNode = nodes.find(
  447. n => n.id === connectingNodePayload.nodeId,
  448. )!
  449. const toNode = nodes.find(n => n.id === enteringNodePayload.nodeId)!
  450. const toParentNode = nodes.find(n => n.id === toNode.parentId)
  451. if (fromNode.parentId !== toNode.parentId) return
  452. const { x, y } = screenToFlowPosition({ x: e.x, y: e.y })
  453. if (
  454. fromHandleType === 'source'
  455. && (toNode.data.type === BlockEnum.VariableAssigner
  456. || toNode.data.type === BlockEnum.VariableAggregator)
  457. ) {
  458. const groupEnabled = toNode.data.advanced_settings?.group_enabled
  459. const firstGroupId = toNode.data.advanced_settings?.groups[0].groupId
  460. let handleId = 'target'
  461. if (groupEnabled) {
  462. if (hoveringAssignVariableGroupId)
  463. handleId = hoveringAssignVariableGroupId
  464. else handleId = firstGroupId
  465. }
  466. const newNodes = produce(nodes, (draft) => {
  467. draft.forEach((node) => {
  468. if (node.id === toNode.id) {
  469. node.data._showAddVariablePopup = true
  470. node.data._holdAddVariablePopup = true
  471. }
  472. })
  473. })
  474. setNodes(newNodes)
  475. setShowAssignVariablePopup({
  476. nodeId: fromNode.id,
  477. nodeData: fromNode.data,
  478. variableAssignerNodeId: toNode.id,
  479. variableAssignerNodeData: toNode.data,
  480. variableAssignerNodeHandleId: handleId,
  481. parentNode: toParentNode,
  482. x: x - toNode.positionAbsolute!.x,
  483. y: y - toNode.positionAbsolute!.y,
  484. })
  485. handleNodeConnect({
  486. source: fromNode.id,
  487. sourceHandle: fromHandleId,
  488. target: toNode.id,
  489. targetHandle: 'target',
  490. })
  491. }
  492. }
  493. setConnectingNodePayload(undefined)
  494. setEnteringNodePayload(undefined)
  495. },
  496. [store, handleNodeConnect, getNodesReadOnly, workflowStore, reactflow],
  497. )
  498. const { deleteNodeInspectorVars } = useInspectVarsCrud()
  499. const handleNodeDelete = useCallback(
  500. (nodeId: string) => {
  501. if (getNodesReadOnly()) return
  502. const { getNodes, setNodes, edges, setEdges } = store.getState()
  503. const nodes = getNodes()
  504. const currentNodeIndex = nodes.findIndex(node => node.id === nodeId)
  505. const currentNode = nodes[currentNodeIndex]
  506. if (!currentNode) return
  507. if (
  508. nodesMetaDataMap?.[currentNode.data.type as BlockEnum]?.metaData
  509. .isUndeletable
  510. )
  511. return
  512. deleteNodeInspectorVars(nodeId)
  513. if (currentNode.data.type === BlockEnum.Iteration) {
  514. const iterationChildren = nodes.filter(
  515. node => node.parentId === currentNode.id,
  516. )
  517. if (iterationChildren.length) {
  518. if (currentNode.data._isBundled) {
  519. iterationChildren.forEach((child) => {
  520. handleNodeDelete(child.id)
  521. })
  522. return handleNodeDelete(nodeId)
  523. }
  524. else {
  525. if (iterationChildren.length === 1) {
  526. handleNodeDelete(iterationChildren[0].id)
  527. handleNodeDelete(nodeId)
  528. return
  529. }
  530. const { setShowConfirm, showConfirm } = workflowStore.getState()
  531. if (!showConfirm) {
  532. setShowConfirm({
  533. title: t('workflow.nodes.iteration.deleteTitle'),
  534. desc: t('workflow.nodes.iteration.deleteDesc') || '',
  535. onConfirm: () => {
  536. iterationChildren.forEach((child) => {
  537. handleNodeDelete(child.id)
  538. })
  539. handleNodeDelete(nodeId)
  540. handleSyncWorkflowDraft()
  541. setShowConfirm(undefined)
  542. },
  543. })
  544. return
  545. }
  546. }
  547. }
  548. }
  549. if (currentNode.data.type === BlockEnum.Loop) {
  550. const loopChildren = nodes.filter(
  551. node => node.parentId === currentNode.id,
  552. )
  553. if (loopChildren.length) {
  554. if (currentNode.data._isBundled) {
  555. loopChildren.forEach((child) => {
  556. handleNodeDelete(child.id)
  557. })
  558. return handleNodeDelete(nodeId)
  559. }
  560. else {
  561. if (loopChildren.length === 1) {
  562. handleNodeDelete(loopChildren[0].id)
  563. handleNodeDelete(nodeId)
  564. return
  565. }
  566. const { setShowConfirm, showConfirm } = workflowStore.getState()
  567. if (!showConfirm) {
  568. setShowConfirm({
  569. title: t('workflow.nodes.loop.deleteTitle'),
  570. desc: t('workflow.nodes.loop.deleteDesc') || '',
  571. onConfirm: () => {
  572. loopChildren.forEach((child) => {
  573. handleNodeDelete(child.id)
  574. })
  575. handleNodeDelete(nodeId)
  576. handleSyncWorkflowDraft()
  577. setShowConfirm(undefined)
  578. },
  579. })
  580. return
  581. }
  582. }
  583. }
  584. }
  585. if (currentNode.data.type === BlockEnum.DataSource) {
  586. const { id } = currentNode
  587. const { ragPipelineVariables, setRagPipelineVariables }
  588. = workflowStore.getState()
  589. if (ragPipelineVariables && setRagPipelineVariables) {
  590. const newRagPipelineVariables: RAGPipelineVariables = []
  591. ragPipelineVariables.forEach((variable) => {
  592. if (variable.belong_to_node_id === id) return
  593. newRagPipelineVariables.push(variable)
  594. })
  595. setRagPipelineVariables(newRagPipelineVariables)
  596. }
  597. }
  598. const connectedEdges = getConnectedEdges([{ id: nodeId } as Node], edges)
  599. const nodesConnectedSourceOrTargetHandleIdsMap
  600. = getNodesConnectedSourceOrTargetHandleIdsMap(
  601. connectedEdges.map(edge => ({ type: 'remove', edge })),
  602. nodes,
  603. )
  604. const newNodes = produce(nodes, (draft: Node[]) => {
  605. draft.forEach((node) => {
  606. if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
  607. node.data = {
  608. ...node.data,
  609. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  610. }
  611. }
  612. if (node.id === currentNode.parentId) {
  613. node.data._children = node.data._children?.filter(
  614. child => child.nodeId !== nodeId,
  615. )
  616. }
  617. })
  618. draft.splice(currentNodeIndex, 1)
  619. })
  620. setNodes(newNodes)
  621. const newEdges = produce(edges, (draft) => {
  622. return draft.filter(
  623. edge =>
  624. !connectedEdges.find(
  625. connectedEdge => connectedEdge.id === edge.id,
  626. ),
  627. )
  628. })
  629. setEdges(newEdges)
  630. handleSyncWorkflowDraft()
  631. if (currentNode.type === CUSTOM_NOTE_NODE) {
  632. saveStateToHistory(WorkflowHistoryEvent.NoteDelete, {
  633. nodeId: currentNode.id,
  634. })
  635. }
  636. else {
  637. saveStateToHistory(WorkflowHistoryEvent.NodeDelete, {
  638. nodeId: currentNode.id,
  639. })
  640. }
  641. },
  642. [
  643. getNodesReadOnly,
  644. store,
  645. handleSyncWorkflowDraft,
  646. saveStateToHistory,
  647. workflowStore,
  648. t,
  649. nodesMetaDataMap,
  650. deleteNodeInspectorVars,
  651. ],
  652. )
  653. const handleNodeAdd = useCallback<OnNodeAdd>(
  654. (
  655. {
  656. nodeType,
  657. sourceHandle = 'source',
  658. targetHandle = 'target',
  659. toolDefaultValue,
  660. },
  661. { prevNodeId, prevNodeSourceHandle, nextNodeId, nextNodeTargetHandle },
  662. ) => {
  663. if (getNodesReadOnly()) return
  664. const { getNodes, setNodes, edges, setEdges } = store.getState()
  665. const nodes = getNodes()
  666. const nodesWithSameType = nodes.filter(
  667. node => node.data.type === nodeType,
  668. )
  669. const { defaultValue } = nodesMetaDataMap![nodeType]
  670. const { newNode, newIterationStartNode, newLoopStartNode }
  671. = generateNewNode({
  672. type: getNodeCustomTypeByNodeDataType(nodeType),
  673. data: {
  674. ...(defaultValue as any),
  675. title:
  676. nodesWithSameType.length > 0
  677. ? `${defaultValue.title} ${nodesWithSameType.length + 1}`
  678. : defaultValue.title,
  679. ...toolDefaultValue,
  680. selected: true,
  681. _showAddVariablePopup:
  682. (nodeType === BlockEnum.VariableAssigner
  683. || nodeType === BlockEnum.VariableAggregator)
  684. && !!prevNodeId,
  685. _holdAddVariablePopup: false,
  686. },
  687. position: {
  688. x: 0,
  689. y: 0,
  690. },
  691. })
  692. if (prevNodeId && !nextNodeId) {
  693. const prevNodeIndex = nodes.findIndex(node => node.id === prevNodeId)
  694. const prevNode = nodes[prevNodeIndex]
  695. const outgoers = getOutgoers(prevNode, nodes, edges).sort(
  696. (a, b) => a.position.y - b.position.y,
  697. )
  698. const lastOutgoer = outgoers[outgoers.length - 1]
  699. newNode.data._connectedTargetHandleIds
  700. = nodeType === BlockEnum.DataSource ? [] : [targetHandle]
  701. newNode.data._connectedSourceHandleIds = []
  702. newNode.position = {
  703. x: lastOutgoer
  704. ? lastOutgoer.position.x
  705. : prevNode.position.x + prevNode.width! + X_OFFSET,
  706. y: lastOutgoer
  707. ? lastOutgoer.position.y + lastOutgoer.height! + Y_OFFSET
  708. : prevNode.position.y,
  709. }
  710. newNode.parentId = prevNode.parentId
  711. newNode.extent = prevNode.extent
  712. const parentNode
  713. = nodes.find(node => node.id === prevNode.parentId) || null
  714. const isInIteration
  715. = !!parentNode && parentNode.data.type === BlockEnum.Iteration
  716. const isInLoop
  717. = !!parentNode && parentNode.data.type === BlockEnum.Loop
  718. if (prevNode.parentId) {
  719. newNode.data.isInIteration = isInIteration
  720. newNode.data.isInLoop = isInLoop
  721. if (isInIteration) {
  722. newNode.data.iteration_id = parentNode.id
  723. newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
  724. }
  725. if (isInLoop) {
  726. newNode.data.loop_id = parentNode.id
  727. newNode.zIndex = LOOP_CHILDREN_Z_INDEX
  728. }
  729. if (
  730. isInIteration
  731. && (newNode.data.type === BlockEnum.Answer
  732. || newNode.data.type === BlockEnum.Tool
  733. || newNode.data.type === BlockEnum.Assigner)
  734. ) {
  735. const iterNodeData: IterationNodeType = parentNode.data
  736. iterNodeData._isShowTips = true
  737. }
  738. if (
  739. isInLoop
  740. && (newNode.data.type === BlockEnum.Answer
  741. || newNode.data.type === BlockEnum.Tool
  742. || newNode.data.type === BlockEnum.Assigner)
  743. ) {
  744. const iterNodeData: IterationNodeType = parentNode.data
  745. iterNodeData._isShowTips = true
  746. }
  747. }
  748. let newEdge = null
  749. if (nodeType !== BlockEnum.DataSource) {
  750. newEdge = {
  751. id: `${prevNodeId}-${prevNodeSourceHandle}-${newNode.id}-${targetHandle}`,
  752. type: CUSTOM_EDGE,
  753. source: prevNodeId,
  754. sourceHandle: prevNodeSourceHandle,
  755. target: newNode.id,
  756. targetHandle,
  757. data: {
  758. sourceType: prevNode.data.type,
  759. targetType: newNode.data.type,
  760. isInIteration,
  761. isInLoop,
  762. iteration_id: isInIteration ? prevNode.parentId : undefined,
  763. loop_id: isInLoop ? prevNode.parentId : undefined,
  764. _connectedNodeIsSelected: true,
  765. },
  766. zIndex: prevNode.parentId
  767. ? isInIteration
  768. ? ITERATION_CHILDREN_Z_INDEX
  769. : LOOP_CHILDREN_Z_INDEX
  770. : 0,
  771. }
  772. }
  773. const nodesConnectedSourceOrTargetHandleIdsMap
  774. = getNodesConnectedSourceOrTargetHandleIdsMap(
  775. (newEdge ? [{ type: 'add', edge: newEdge }] : []),
  776. nodes,
  777. )
  778. const newNodes = produce(nodes, (draft: Node[]) => {
  779. draft.forEach((node) => {
  780. node.data.selected = false
  781. if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
  782. node.data = {
  783. ...node.data,
  784. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  785. }
  786. }
  787. if (
  788. node.data.type === BlockEnum.Iteration
  789. && prevNode.parentId === node.id
  790. ) {
  791. node.data._children?.push({
  792. nodeId: newNode.id,
  793. nodeType: newNode.data.type,
  794. })
  795. }
  796. if (
  797. node.data.type === BlockEnum.Loop
  798. && prevNode.parentId === node.id
  799. ) {
  800. node.data._children?.push({
  801. nodeId: newNode.id,
  802. nodeType: newNode.data.type,
  803. })
  804. }
  805. })
  806. draft.push(newNode)
  807. if (newIterationStartNode) draft.push(newIterationStartNode)
  808. if (newLoopStartNode) draft.push(newLoopStartNode)
  809. })
  810. if (
  811. newNode.data.type === BlockEnum.VariableAssigner
  812. || newNode.data.type === BlockEnum.VariableAggregator
  813. ) {
  814. const { setShowAssignVariablePopup } = workflowStore.getState()
  815. setShowAssignVariablePopup({
  816. nodeId: prevNode.id,
  817. nodeData: prevNode.data,
  818. variableAssignerNodeId: newNode.id,
  819. variableAssignerNodeData: newNode.data as VariableAssignerNodeType,
  820. variableAssignerNodeHandleId: targetHandle,
  821. parentNode: nodes.find(node => node.id === newNode.parentId),
  822. x: -25,
  823. y: 44,
  824. })
  825. }
  826. const newEdges = produce(edges, (draft) => {
  827. draft.forEach((item) => {
  828. item.data = {
  829. ...item.data,
  830. _connectedNodeIsSelected: false,
  831. }
  832. })
  833. if (newEdge) draft.push(newEdge)
  834. })
  835. setNodes(newNodes)
  836. setEdges(newEdges)
  837. }
  838. if (!prevNodeId && nextNodeId) {
  839. const nextNodeIndex = nodes.findIndex(node => node.id === nextNodeId)
  840. const nextNode = nodes[nextNodeIndex]!
  841. if (
  842. nodeType !== BlockEnum.IfElse
  843. && nodeType !== BlockEnum.QuestionClassifier
  844. )
  845. newNode.data._connectedSourceHandleIds = [sourceHandle]
  846. newNode.data._connectedTargetHandleIds = []
  847. newNode.position = {
  848. x: nextNode.position.x,
  849. y: nextNode.position.y,
  850. }
  851. newNode.parentId = nextNode.parentId
  852. newNode.extent = nextNode.extent
  853. const parentNode
  854. = nodes.find(node => node.id === nextNode.parentId) || null
  855. const isInIteration
  856. = !!parentNode && parentNode.data.type === BlockEnum.Iteration
  857. const isInLoop
  858. = !!parentNode && parentNode.data.type === BlockEnum.Loop
  859. if (parentNode && nextNode.parentId) {
  860. newNode.data.isInIteration = isInIteration
  861. newNode.data.isInLoop = isInLoop
  862. if (isInIteration) {
  863. newNode.data.iteration_id = parentNode.id
  864. newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
  865. }
  866. if (isInLoop) {
  867. newNode.data.loop_id = parentNode.id
  868. newNode.zIndex = LOOP_CHILDREN_Z_INDEX
  869. }
  870. }
  871. let newEdge
  872. if (
  873. nodeType !== BlockEnum.IfElse
  874. && nodeType !== BlockEnum.QuestionClassifier
  875. && nodeType !== BlockEnum.LoopEnd
  876. ) {
  877. newEdge = {
  878. id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`,
  879. type: CUSTOM_EDGE,
  880. source: newNode.id,
  881. sourceHandle,
  882. target: nextNodeId,
  883. targetHandle: nextNodeTargetHandle,
  884. data: {
  885. sourceType: newNode.data.type,
  886. targetType: nextNode.data.type,
  887. isInIteration,
  888. isInLoop,
  889. iteration_id: isInIteration ? nextNode.parentId : undefined,
  890. loop_id: isInLoop ? nextNode.parentId : undefined,
  891. _connectedNodeIsSelected: true,
  892. },
  893. zIndex: nextNode.parentId
  894. ? isInIteration
  895. ? ITERATION_CHILDREN_Z_INDEX
  896. : LOOP_CHILDREN_Z_INDEX
  897. : 0,
  898. }
  899. }
  900. let nodesConnectedSourceOrTargetHandleIdsMap: Record<string, any>
  901. if (newEdge) {
  902. nodesConnectedSourceOrTargetHandleIdsMap
  903. = getNodesConnectedSourceOrTargetHandleIdsMap(
  904. [{ type: 'add', edge: newEdge }],
  905. nodes,
  906. )
  907. }
  908. const afterNodesInSameBranch = getAfterNodesInSameBranch(nextNodeId!)
  909. const afterNodesInSameBranchIds = afterNodesInSameBranch.map(
  910. node => node.id,
  911. )
  912. const newNodes = produce(nodes, (draft) => {
  913. draft.forEach((node) => {
  914. node.data.selected = false
  915. if (afterNodesInSameBranchIds.includes(node.id))
  916. node.position.x += NODE_WIDTH_X_OFFSET
  917. if (nodesConnectedSourceOrTargetHandleIdsMap?.[node.id]) {
  918. node.data = {
  919. ...node.data,
  920. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  921. }
  922. }
  923. if (
  924. node.data.type === BlockEnum.Iteration
  925. && nextNode.parentId === node.id
  926. ) {
  927. node.data._children?.push({
  928. nodeId: newNode.id,
  929. nodeType: newNode.data.type,
  930. })
  931. }
  932. if (
  933. node.data.type === BlockEnum.Iteration
  934. && node.data.start_node_id === nextNodeId
  935. ) {
  936. node.data.start_node_id = newNode.id
  937. node.data.startNodeType = newNode.data.type
  938. }
  939. if (
  940. node.data.type === BlockEnum.Loop
  941. && nextNode.parentId === node.id
  942. ) {
  943. node.data._children?.push({
  944. nodeId: newNode.id,
  945. nodeType: newNode.data.type,
  946. })
  947. }
  948. if (
  949. node.data.type === BlockEnum.Loop
  950. && node.data.start_node_id === nextNodeId
  951. ) {
  952. node.data.start_node_id = newNode.id
  953. node.data.startNodeType = newNode.data.type
  954. }
  955. })
  956. draft.push(newNode)
  957. if (newIterationStartNode) draft.push(newIterationStartNode)
  958. if (newLoopStartNode) draft.push(newLoopStartNode)
  959. })
  960. if (newEdge) {
  961. const newEdges = produce(edges, (draft) => {
  962. draft.forEach((item) => {
  963. item.data = {
  964. ...item.data,
  965. _connectedNodeIsSelected: false,
  966. }
  967. })
  968. draft.push(newEdge)
  969. })
  970. setNodes(newNodes)
  971. setEdges(newEdges)
  972. }
  973. else {
  974. setNodes(newNodes)
  975. }
  976. }
  977. if (prevNodeId && nextNodeId) {
  978. const prevNode = nodes.find(node => node.id === prevNodeId)!
  979. const nextNode = nodes.find(node => node.id === nextNodeId)!
  980. newNode.data._connectedTargetHandleIds
  981. = nodeType === BlockEnum.DataSource ? [] : [targetHandle]
  982. newNode.data._connectedSourceHandleIds = [sourceHandle]
  983. newNode.position = {
  984. x: nextNode.position.x,
  985. y: nextNode.position.y,
  986. }
  987. newNode.parentId = prevNode.parentId
  988. newNode.extent = prevNode.extent
  989. const parentNode
  990. = nodes.find(node => node.id === prevNode.parentId) || null
  991. const isInIteration
  992. = !!parentNode && parentNode.data.type === BlockEnum.Iteration
  993. const isInLoop
  994. = !!parentNode && parentNode.data.type === BlockEnum.Loop
  995. if (parentNode && prevNode.parentId) {
  996. newNode.data.isInIteration = isInIteration
  997. newNode.data.isInLoop = isInLoop
  998. if (isInIteration) {
  999. newNode.data.iteration_id = parentNode.id
  1000. newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
  1001. }
  1002. if (isInLoop) {
  1003. newNode.data.loop_id = parentNode.id
  1004. newNode.zIndex = LOOP_CHILDREN_Z_INDEX
  1005. }
  1006. }
  1007. const currentEdgeIndex = edges.findIndex(
  1008. edge => edge.source === prevNodeId && edge.target === nextNodeId,
  1009. )
  1010. let newPrevEdge = null
  1011. if (nodeType !== BlockEnum.DataSource) {
  1012. newPrevEdge = {
  1013. id: `${prevNodeId}-${prevNodeSourceHandle}-${newNode.id}-${targetHandle}`,
  1014. type: CUSTOM_EDGE,
  1015. source: prevNodeId,
  1016. sourceHandle: prevNodeSourceHandle,
  1017. target: newNode.id,
  1018. targetHandle,
  1019. data: {
  1020. sourceType: prevNode.data.type,
  1021. targetType: newNode.data.type,
  1022. isInIteration,
  1023. isInLoop,
  1024. iteration_id: isInIteration ? prevNode.parentId : undefined,
  1025. loop_id: isInLoop ? prevNode.parentId : undefined,
  1026. _connectedNodeIsSelected: true,
  1027. },
  1028. zIndex: prevNode.parentId
  1029. ? isInIteration
  1030. ? ITERATION_CHILDREN_Z_INDEX
  1031. : LOOP_CHILDREN_Z_INDEX
  1032. : 0,
  1033. }
  1034. }
  1035. let newNextEdge: Edge | null = null
  1036. const nextNodeParentNode
  1037. = nodes.find(node => node.id === nextNode.parentId) || null
  1038. const isNextNodeInIteration
  1039. = !!nextNodeParentNode
  1040. && nextNodeParentNode.data.type === BlockEnum.Iteration
  1041. const isNextNodeInLoop
  1042. = !!nextNodeParentNode
  1043. && nextNodeParentNode.data.type === BlockEnum.Loop
  1044. if (
  1045. nodeType !== BlockEnum.IfElse
  1046. && nodeType !== BlockEnum.QuestionClassifier
  1047. && nodeType !== BlockEnum.LoopEnd
  1048. ) {
  1049. newNextEdge = {
  1050. id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`,
  1051. type: CUSTOM_EDGE,
  1052. source: newNode.id,
  1053. sourceHandle,
  1054. target: nextNodeId,
  1055. targetHandle: nextNodeTargetHandle,
  1056. data: {
  1057. sourceType: newNode.data.type,
  1058. targetType: nextNode.data.type,
  1059. isInIteration: isNextNodeInIteration,
  1060. isInLoop: isNextNodeInLoop,
  1061. iteration_id: isNextNodeInIteration
  1062. ? nextNode.parentId
  1063. : undefined,
  1064. loop_id: isNextNodeInLoop ? nextNode.parentId : undefined,
  1065. _connectedNodeIsSelected: true,
  1066. },
  1067. zIndex: nextNode.parentId
  1068. ? isNextNodeInIteration
  1069. ? ITERATION_CHILDREN_Z_INDEX
  1070. : LOOP_CHILDREN_Z_INDEX
  1071. : 0,
  1072. }
  1073. }
  1074. const nodesConnectedSourceOrTargetHandleIdsMap
  1075. = getNodesConnectedSourceOrTargetHandleIdsMap(
  1076. [
  1077. { type: 'remove', edge: edges[currentEdgeIndex] },
  1078. ...(newPrevEdge ? [{ type: 'add', edge: newPrevEdge }] : []),
  1079. ...(newNextEdge ? [{ type: 'add', edge: newNextEdge }] : []),
  1080. ],
  1081. [...nodes, newNode],
  1082. )
  1083. const afterNodesInSameBranch = getAfterNodesInSameBranch(nextNodeId!)
  1084. const afterNodesInSameBranchIds = afterNodesInSameBranch.map(
  1085. node => node.id,
  1086. )
  1087. const newNodes = produce(nodes, (draft) => {
  1088. draft.forEach((node) => {
  1089. node.data.selected = false
  1090. if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
  1091. node.data = {
  1092. ...node.data,
  1093. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  1094. }
  1095. }
  1096. if (afterNodesInSameBranchIds.includes(node.id))
  1097. node.position.x += NODE_WIDTH_X_OFFSET
  1098. if (
  1099. node.data.type === BlockEnum.Iteration
  1100. && prevNode.parentId === node.id
  1101. ) {
  1102. node.data._children?.push({
  1103. nodeId: newNode.id,
  1104. nodeType: newNode.data.type,
  1105. })
  1106. }
  1107. if (
  1108. node.data.type === BlockEnum.Loop
  1109. && prevNode.parentId === node.id
  1110. ) {
  1111. node.data._children?.push({
  1112. nodeId: newNode.id,
  1113. nodeType: newNode.data.type,
  1114. })
  1115. }
  1116. })
  1117. draft.push(newNode)
  1118. if (newIterationStartNode) draft.push(newIterationStartNode)
  1119. if (newLoopStartNode) draft.push(newLoopStartNode)
  1120. })
  1121. setNodes(newNodes)
  1122. if (
  1123. newNode.data.type === BlockEnum.VariableAssigner
  1124. || newNode.data.type === BlockEnum.VariableAggregator
  1125. ) {
  1126. const { setShowAssignVariablePopup } = workflowStore.getState()
  1127. setShowAssignVariablePopup({
  1128. nodeId: prevNode.id,
  1129. nodeData: prevNode.data,
  1130. variableAssignerNodeId: newNode.id,
  1131. variableAssignerNodeData: newNode.data as VariableAssignerNodeType,
  1132. variableAssignerNodeHandleId: targetHandle,
  1133. parentNode: nodes.find(node => node.id === newNode.parentId),
  1134. x: -25,
  1135. y: 44,
  1136. })
  1137. }
  1138. const newEdges = produce(edges, (draft) => {
  1139. draft.splice(currentEdgeIndex, 1)
  1140. draft.forEach((item) => {
  1141. item.data = {
  1142. ...item.data,
  1143. _connectedNodeIsSelected: false,
  1144. }
  1145. })
  1146. if (newPrevEdge) draft.push(newPrevEdge)
  1147. if (newNextEdge) draft.push(newNextEdge)
  1148. })
  1149. setEdges(newEdges)
  1150. }
  1151. handleSyncWorkflowDraft()
  1152. saveStateToHistory(WorkflowHistoryEvent.NodeAdd, { nodeId: newNode.id })
  1153. },
  1154. [
  1155. getNodesReadOnly,
  1156. store,
  1157. handleSyncWorkflowDraft,
  1158. saveStateToHistory,
  1159. workflowStore,
  1160. getAfterNodesInSameBranch,
  1161. nodesMetaDataMap,
  1162. ],
  1163. )
  1164. const handleNodeChange = useCallback(
  1165. (
  1166. currentNodeId: string,
  1167. nodeType: BlockEnum,
  1168. sourceHandle: string,
  1169. toolDefaultValue?: ToolDefaultValue,
  1170. ) => {
  1171. if (getNodesReadOnly()) return
  1172. const { getNodes, setNodes, edges, setEdges } = store.getState()
  1173. const nodes = getNodes()
  1174. const currentNode = nodes.find(node => node.id === currentNodeId)!
  1175. const connectedEdges = getConnectedEdges([currentNode], edges)
  1176. const nodesWithSameType = nodes.filter(
  1177. node => node.data.type === nodeType,
  1178. )
  1179. const { defaultValue } = nodesMetaDataMap![nodeType]
  1180. const {
  1181. newNode: newCurrentNode,
  1182. newIterationStartNode,
  1183. newLoopStartNode,
  1184. } = generateNewNode({
  1185. type: getNodeCustomTypeByNodeDataType(nodeType),
  1186. data: {
  1187. ...(defaultValue as any),
  1188. title:
  1189. nodesWithSameType.length > 0
  1190. ? `${defaultValue.title} ${nodesWithSameType.length + 1}`
  1191. : defaultValue.title,
  1192. ...toolDefaultValue,
  1193. _connectedSourceHandleIds: [],
  1194. _connectedTargetHandleIds: [],
  1195. selected: currentNode.data.selected,
  1196. isInIteration: currentNode.data.isInIteration,
  1197. isInLoop: currentNode.data.isInLoop,
  1198. iteration_id: currentNode.data.iteration_id,
  1199. loop_id: currentNode.data.loop_id,
  1200. },
  1201. position: {
  1202. x: currentNode.position.x,
  1203. y: currentNode.position.y,
  1204. },
  1205. parentId: currentNode.parentId,
  1206. extent: currentNode.extent,
  1207. zIndex: currentNode.zIndex,
  1208. })
  1209. const nodesConnectedSourceOrTargetHandleIdsMap
  1210. = getNodesConnectedSourceOrTargetHandleIdsMap(
  1211. connectedEdges.map(edge => ({ type: 'remove', edge })),
  1212. nodes,
  1213. )
  1214. const newNodes = produce(nodes, (draft) => {
  1215. draft.forEach((node) => {
  1216. node.data.selected = false
  1217. if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
  1218. node.data = {
  1219. ...node.data,
  1220. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  1221. }
  1222. }
  1223. })
  1224. const index = draft.findIndex(node => node.id === currentNodeId)
  1225. draft.splice(index, 1, newCurrentNode)
  1226. if (newIterationStartNode) draft.push(newIterationStartNode)
  1227. if (newLoopStartNode) draft.push(newLoopStartNode)
  1228. })
  1229. setNodes(newNodes)
  1230. const newEdges = produce(edges, (draft) => {
  1231. const filtered = draft.filter(
  1232. edge =>
  1233. !connectedEdges.find(
  1234. connectedEdge => connectedEdge.id === edge.id,
  1235. ),
  1236. )
  1237. return filtered
  1238. })
  1239. setEdges(newEdges)
  1240. handleSyncWorkflowDraft()
  1241. saveStateToHistory(WorkflowHistoryEvent.NodeChange, {
  1242. nodeId: currentNodeId,
  1243. })
  1244. },
  1245. [
  1246. getNodesReadOnly,
  1247. store,
  1248. handleSyncWorkflowDraft,
  1249. saveStateToHistory,
  1250. nodesMetaDataMap,
  1251. ],
  1252. )
  1253. const handleNodesCancelSelected = useCallback(() => {
  1254. const { getNodes, setNodes } = store.getState()
  1255. const nodes = getNodes()
  1256. const newNodes = produce(nodes, (draft) => {
  1257. draft.forEach((node) => {
  1258. node.data.selected = false
  1259. })
  1260. })
  1261. setNodes(newNodes)
  1262. }, [store])
  1263. const handleNodeContextMenu = useCallback(
  1264. (e: MouseEvent, node: Node) => {
  1265. if (
  1266. node.type === CUSTOM_NOTE_NODE
  1267. || node.type === CUSTOM_ITERATION_START_NODE
  1268. )
  1269. return
  1270. if (
  1271. node.type === CUSTOM_NOTE_NODE
  1272. || node.type === CUSTOM_LOOP_START_NODE
  1273. )
  1274. return
  1275. e.preventDefault()
  1276. const container = document.querySelector('#workflow-container')
  1277. const { x, y } = container!.getBoundingClientRect()
  1278. workflowStore.setState({
  1279. nodeMenu: {
  1280. top: e.clientY - y,
  1281. left: e.clientX - x,
  1282. nodeId: node.id,
  1283. },
  1284. })
  1285. handleNodeSelect(node.id)
  1286. },
  1287. [workflowStore, handleNodeSelect],
  1288. )
  1289. const handleNodesCopy = useCallback(
  1290. (nodeId?: string) => {
  1291. if (getNodesReadOnly()) return
  1292. const { setClipboardElements } = workflowStore.getState()
  1293. const { getNodes } = store.getState()
  1294. const nodes = getNodes()
  1295. if (nodeId) {
  1296. // If nodeId is provided, copy that specific node
  1297. const nodeToCopy = nodes.find(
  1298. node =>
  1299. node.id === nodeId
  1300. && node.data.type !== BlockEnum.Start
  1301. && node.type !== CUSTOM_ITERATION_START_NODE
  1302. && node.type !== CUSTOM_LOOP_START_NODE
  1303. && node.data.type !== BlockEnum.LoopEnd
  1304. && node.data.type !== BlockEnum.KnowledgeBase
  1305. && node.data.type !== BlockEnum.DataSourceEmpty,
  1306. )
  1307. if (nodeToCopy) setClipboardElements([nodeToCopy])
  1308. }
  1309. else {
  1310. // If no nodeId is provided, fall back to the current behavior
  1311. const bundledNodes = nodes.filter((node) => {
  1312. if (!node.data._isBundled) return false
  1313. const { metaData } = nodesMetaDataMap![node.data.type as BlockEnum]
  1314. if (metaData.isSingleton) return false
  1315. return !node.data.isInIteration && !node.data.isInLoop
  1316. })
  1317. if (bundledNodes.length) {
  1318. setClipboardElements(bundledNodes)
  1319. return
  1320. }
  1321. const selectedNode = nodes.find((node) => {
  1322. if (!node.data.selected) return false
  1323. const { metaData } = nodesMetaDataMap![node.data.type as BlockEnum]
  1324. return !metaData.isSingleton
  1325. })
  1326. if (selectedNode) setClipboardElements([selectedNode])
  1327. }
  1328. },
  1329. [getNodesReadOnly, store, workflowStore],
  1330. )
  1331. const handleNodesPaste = useCallback(() => {
  1332. if (getNodesReadOnly()) return
  1333. const { clipboardElements, mousePosition } = workflowStore.getState()
  1334. const { getNodes, setNodes, edges, setEdges } = store.getState()
  1335. const nodesToPaste: Node[] = []
  1336. const edgesToPaste: Edge[] = []
  1337. const nodes = getNodes()
  1338. if (clipboardElements.length) {
  1339. const { x, y } = getTopLeftNodePosition(clipboardElements)
  1340. const { screenToFlowPosition } = reactflow
  1341. const currentPosition = screenToFlowPosition({
  1342. x: mousePosition.pageX,
  1343. y: mousePosition.pageY,
  1344. })
  1345. const offsetX = currentPosition.x - x
  1346. const offsetY = currentPosition.y - y
  1347. let idMapping: Record<string, string> = {}
  1348. clipboardElements.forEach((nodeToPaste, index) => {
  1349. const nodeType = nodeToPaste.data.type
  1350. const { newNode, newIterationStartNode, newLoopStartNode }
  1351. = generateNewNode({
  1352. type: nodeToPaste.type,
  1353. data: {
  1354. ...nodesMetaDataMap![nodeType].defaultValue,
  1355. ...nodeToPaste.data,
  1356. selected: false,
  1357. _isBundled: false,
  1358. _connectedSourceHandleIds: [],
  1359. _connectedTargetHandleIds: [],
  1360. title: genNewNodeTitleFromOld(nodeToPaste.data.title),
  1361. },
  1362. position: {
  1363. x: nodeToPaste.position.x + offsetX,
  1364. y: nodeToPaste.position.y + offsetY,
  1365. },
  1366. extent: nodeToPaste.extent,
  1367. zIndex: nodeToPaste.zIndex,
  1368. })
  1369. newNode.id = newNode.id + index
  1370. // This new node is movable and can be placed anywhere
  1371. let newChildren: Node[] = []
  1372. if (nodeToPaste.data.type === BlockEnum.Iteration) {
  1373. newIterationStartNode!.parentId = newNode.id;
  1374. (newNode.data as IterationNodeType).start_node_id
  1375. = newIterationStartNode!.id
  1376. const oldIterationStartNode = nodes.find(
  1377. n =>
  1378. n.parentId === nodeToPaste.id
  1379. && n.type === CUSTOM_ITERATION_START_NODE,
  1380. )
  1381. idMapping[oldIterationStartNode!.id] = newIterationStartNode!.id
  1382. const { copyChildren, newIdMapping }
  1383. = handleNodeIterationChildrenCopy(
  1384. nodeToPaste.id,
  1385. newNode.id,
  1386. idMapping,
  1387. )
  1388. newChildren = copyChildren
  1389. idMapping = newIdMapping
  1390. newChildren.forEach((child) => {
  1391. newNode.data._children?.push({
  1392. nodeId: child.id,
  1393. nodeType: child.data.type,
  1394. })
  1395. })
  1396. newChildren.push(newIterationStartNode!)
  1397. }
  1398. else if (nodeToPaste.data.type === BlockEnum.Loop) {
  1399. newLoopStartNode!.parentId = newNode.id;
  1400. (newNode.data as LoopNodeType).start_node_id = newLoopStartNode!.id
  1401. newChildren = handleNodeLoopChildrenCopy(nodeToPaste.id, newNode.id)
  1402. newChildren.forEach((child) => {
  1403. newNode.data._children?.push({
  1404. nodeId: child.id,
  1405. nodeType: child.data.type,
  1406. })
  1407. })
  1408. newChildren.push(newLoopStartNode!)
  1409. }
  1410. else {
  1411. // single node paste
  1412. const selectedNode = nodes.find(node => node.selected)
  1413. if (selectedNode) {
  1414. const commonNestedDisallowPasteNodes = [
  1415. // end node only can be placed outermost layer
  1416. BlockEnum.End,
  1417. ]
  1418. // handle disallow paste node
  1419. if (commonNestedDisallowPasteNodes.includes(nodeToPaste.data.type))
  1420. return
  1421. // handle paste to nested block
  1422. if (selectedNode.data.type === BlockEnum.Iteration) {
  1423. newNode.data.isInIteration = true
  1424. newNode.data.iteration_id = selectedNode.data.iteration_id
  1425. newNode.parentId = selectedNode.id
  1426. newNode.positionAbsolute = {
  1427. x: newNode.position.x,
  1428. y: newNode.position.y,
  1429. }
  1430. // set position base on parent node
  1431. newNode.position = getNestedNodePosition(newNode, selectedNode)
  1432. }
  1433. else if (selectedNode.data.type === BlockEnum.Loop) {
  1434. newNode.data.isInLoop = true
  1435. newNode.data.loop_id = selectedNode.data.loop_id
  1436. newNode.parentId = selectedNode.id
  1437. newNode.positionAbsolute = {
  1438. x: newNode.position.x,
  1439. y: newNode.position.y,
  1440. }
  1441. // set position base on parent node
  1442. newNode.position = getNestedNodePosition(newNode, selectedNode)
  1443. }
  1444. }
  1445. }
  1446. nodesToPaste.push(newNode)
  1447. if (newChildren.length) nodesToPaste.push(...newChildren)
  1448. })
  1449. // only handle edge when paste nested block
  1450. edges.forEach((edge) => {
  1451. const sourceId = idMapping[edge.source]
  1452. const targetId = idMapping[edge.target]
  1453. if (sourceId && targetId) {
  1454. const newEdge: Edge = {
  1455. ...edge,
  1456. id: `${sourceId}-${edge.sourceHandle}-${targetId}-${edge.targetHandle}`,
  1457. source: sourceId,
  1458. target: targetId,
  1459. data: {
  1460. ...edge.data,
  1461. _connectedNodeIsSelected: false,
  1462. },
  1463. }
  1464. edgesToPaste.push(newEdge)
  1465. }
  1466. })
  1467. setNodes([...nodes, ...nodesToPaste])
  1468. setEdges([...edges, ...edgesToPaste])
  1469. saveStateToHistory(WorkflowHistoryEvent.NodePaste, {
  1470. nodeId: nodesToPaste?.[0]?.id,
  1471. })
  1472. handleSyncWorkflowDraft()
  1473. }
  1474. }, [
  1475. getNodesReadOnly,
  1476. workflowStore,
  1477. store,
  1478. reactflow,
  1479. saveStateToHistory,
  1480. handleSyncWorkflowDraft,
  1481. handleNodeIterationChildrenCopy,
  1482. handleNodeLoopChildrenCopy,
  1483. nodesMetaDataMap,
  1484. ])
  1485. const handleNodesDuplicate = useCallback(
  1486. (nodeId?: string) => {
  1487. if (getNodesReadOnly()) return
  1488. handleNodesCopy(nodeId)
  1489. handleNodesPaste()
  1490. },
  1491. [getNodesReadOnly, handleNodesCopy, handleNodesPaste],
  1492. )
  1493. const handleNodesDelete = useCallback(() => {
  1494. if (getNodesReadOnly()) return
  1495. const { getNodes, edges } = store.getState()
  1496. const nodes = getNodes()
  1497. const bundledNodes = nodes.filter(
  1498. node => node.data._isBundled && node.data.type !== BlockEnum.Start,
  1499. )
  1500. if (bundledNodes.length) {
  1501. bundledNodes.forEach(node => handleNodeDelete(node.id))
  1502. return
  1503. }
  1504. const edgeSelected = edges.some(edge => edge.selected)
  1505. if (edgeSelected) return
  1506. const selectedNode = nodes.find(
  1507. node => node.data.selected && node.data.type !== BlockEnum.Start,
  1508. )
  1509. if (selectedNode) handleNodeDelete(selectedNode.id)
  1510. }, [store, getNodesReadOnly, handleNodeDelete])
  1511. const handleNodeResize = useCallback(
  1512. (nodeId: string, params: ResizeParamsWithDirection) => {
  1513. if (getNodesReadOnly()) return
  1514. const { getNodes, setNodes } = store.getState()
  1515. const { x, y, width, height } = params
  1516. const nodes = getNodes()
  1517. const currentNode = nodes.find(n => n.id === nodeId)!
  1518. const childrenNodes = nodes.filter(n =>
  1519. currentNode.data._children?.find((c: any) => c.nodeId === n.id),
  1520. )
  1521. let rightNode: Node
  1522. let bottomNode: Node
  1523. childrenNodes.forEach((n) => {
  1524. if (rightNode) {
  1525. if (n.position.x + n.width! > rightNode.position.x + rightNode.width!)
  1526. rightNode = n
  1527. }
  1528. else {
  1529. rightNode = n
  1530. }
  1531. if (bottomNode) {
  1532. if (
  1533. n.position.y + n.height!
  1534. > bottomNode.position.y + bottomNode.height!
  1535. )
  1536. bottomNode = n
  1537. }
  1538. else {
  1539. bottomNode = n
  1540. }
  1541. })
  1542. if (rightNode! && bottomNode!) {
  1543. const parentNode = nodes.find(n => n.id === rightNode.parentId)
  1544. const paddingMap
  1545. = parentNode?.data.type === BlockEnum.Iteration
  1546. ? ITERATION_PADDING
  1547. : LOOP_PADDING
  1548. if (width < rightNode!.position.x + rightNode.width! + paddingMap.right)
  1549. return
  1550. if (
  1551. height
  1552. < bottomNode.position.y + bottomNode.height! + paddingMap.bottom
  1553. )
  1554. return
  1555. }
  1556. const newNodes = produce(nodes, (draft) => {
  1557. draft.forEach((n) => {
  1558. if (n.id === nodeId) {
  1559. n.data.width = width
  1560. n.data.height = height
  1561. n.width = width
  1562. n.height = height
  1563. n.position.x = x
  1564. n.position.y = y
  1565. }
  1566. })
  1567. })
  1568. setNodes(newNodes)
  1569. handleSyncWorkflowDraft()
  1570. saveStateToHistory(WorkflowHistoryEvent.NodeResize, { nodeId })
  1571. },
  1572. [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory],
  1573. )
  1574. const handleNodeDisconnect = useCallback(
  1575. (nodeId: string) => {
  1576. if (getNodesReadOnly()) return
  1577. const { getNodes, setNodes, edges, setEdges } = store.getState()
  1578. const nodes = getNodes()
  1579. const currentNode = nodes.find(node => node.id === nodeId)!
  1580. const connectedEdges = getConnectedEdges([currentNode], edges)
  1581. const nodesConnectedSourceOrTargetHandleIdsMap
  1582. = getNodesConnectedSourceOrTargetHandleIdsMap(
  1583. connectedEdges.map(edge => ({ type: 'remove', edge })),
  1584. nodes,
  1585. )
  1586. const newNodes = produce(nodes, (draft: Node[]) => {
  1587. draft.forEach((node) => {
  1588. if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
  1589. node.data = {
  1590. ...node.data,
  1591. ...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
  1592. }
  1593. }
  1594. })
  1595. })
  1596. setNodes(newNodes)
  1597. const newEdges = produce(edges, (draft) => {
  1598. return draft.filter(
  1599. edge =>
  1600. !connectedEdges.find(
  1601. connectedEdge => connectedEdge.id === edge.id,
  1602. ),
  1603. )
  1604. })
  1605. setEdges(newEdges)
  1606. handleSyncWorkflowDraft()
  1607. saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
  1608. },
  1609. [store, getNodesReadOnly, handleSyncWorkflowDraft, saveStateToHistory],
  1610. )
  1611. const handleHistoryBack = useCallback(() => {
  1612. if (getNodesReadOnly() || getWorkflowReadOnly()) return
  1613. const { setEdges, setNodes } = store.getState()
  1614. undo()
  1615. const { edges, nodes } = workflowHistoryStore.getState()
  1616. if (edges.length === 0 && nodes.length === 0) return
  1617. setEdges(edges)
  1618. setNodes(nodes)
  1619. }, [
  1620. store,
  1621. undo,
  1622. workflowHistoryStore,
  1623. getNodesReadOnly,
  1624. getWorkflowReadOnly,
  1625. ])
  1626. const handleHistoryForward = useCallback(() => {
  1627. if (getNodesReadOnly() || getWorkflowReadOnly()) return
  1628. const { setEdges, setNodes } = store.getState()
  1629. redo()
  1630. const { edges, nodes } = workflowHistoryStore.getState()
  1631. if (edges.length === 0 && nodes.length === 0) return
  1632. setEdges(edges)
  1633. setNodes(nodes)
  1634. }, [
  1635. redo,
  1636. store,
  1637. workflowHistoryStore,
  1638. getNodesReadOnly,
  1639. getWorkflowReadOnly,
  1640. ])
  1641. const [isDimming, setIsDimming] = useState(false)
  1642. /** Add opacity-30 to all nodes except the nodeId */
  1643. const dimOtherNodes = useCallback(() => {
  1644. if (isDimming) return
  1645. const { getNodes, setNodes, edges, setEdges } = store.getState()
  1646. const nodes = getNodes()
  1647. const selectedNode = nodes.find(n => n.data.selected)
  1648. if (!selectedNode) return
  1649. setIsDimming(true)
  1650. // const workflowNodes = useStore(s => s.getNodes())
  1651. const workflowNodes = nodes
  1652. const usedVars = getNodeUsedVars(selectedNode)
  1653. const dependencyNodes: Node[] = []
  1654. usedVars.forEach((valueSelector) => {
  1655. const node = workflowNodes.find(node => node.id === valueSelector?.[0])
  1656. if (node)
  1657. if (!dependencyNodes.includes(node)) dependencyNodes.push(node)
  1658. })
  1659. const outgoers = getOutgoers(selectedNode as Node, nodes as Node[], edges)
  1660. for (let currIdx = 0; currIdx < outgoers.length; currIdx++) {
  1661. const node = outgoers[currIdx]
  1662. const outgoersForNode = getOutgoers(node, nodes as Node[], edges)
  1663. outgoersForNode.forEach((item) => {
  1664. const existed = outgoers.some(v => v.id === item.id)
  1665. if (!existed) outgoers.push(item)
  1666. })
  1667. }
  1668. const dependentNodes: Node[] = []
  1669. outgoers.forEach((node) => {
  1670. const usedVars = getNodeUsedVars(node)
  1671. const used = usedVars.some(v => v?.[0] === selectedNode.id)
  1672. if (used) {
  1673. const existed = dependentNodes.some(v => v.id === node.id)
  1674. if (!existed) dependentNodes.push(node)
  1675. }
  1676. })
  1677. const dimNodes = [...dependencyNodes, ...dependentNodes, selectedNode]
  1678. const newNodes = produce(nodes, (draft) => {
  1679. draft.forEach((n) => {
  1680. const dimNode = dimNodes.find(v => v.id === n.id)
  1681. if (!dimNode) n.data._dimmed = true
  1682. })
  1683. })
  1684. setNodes(newNodes)
  1685. const tempEdges: Edge[] = []
  1686. dependencyNodes.forEach((n) => {
  1687. tempEdges.push({
  1688. id: `tmp_${n.id}-source-${selectedNode.id}-target`,
  1689. type: CUSTOM_EDGE,
  1690. source: n.id,
  1691. sourceHandle: 'source_tmp',
  1692. target: selectedNode.id,
  1693. targetHandle: 'target_tmp',
  1694. animated: true,
  1695. data: {
  1696. sourceType: n.data.type,
  1697. targetType: selectedNode.data.type,
  1698. _isTemp: true,
  1699. _connectedNodeIsHovering: true,
  1700. },
  1701. })
  1702. })
  1703. dependentNodes.forEach((n) => {
  1704. tempEdges.push({
  1705. id: `tmp_${selectedNode.id}-source-${n.id}-target`,
  1706. type: CUSTOM_EDGE,
  1707. source: selectedNode.id,
  1708. sourceHandle: 'source_tmp',
  1709. target: n.id,
  1710. targetHandle: 'target_tmp',
  1711. animated: true,
  1712. data: {
  1713. sourceType: selectedNode.data.type,
  1714. targetType: n.data.type,
  1715. _isTemp: true,
  1716. _connectedNodeIsHovering: true,
  1717. },
  1718. })
  1719. })
  1720. const newEdges = produce(edges, (draft) => {
  1721. draft.forEach((e) => {
  1722. e.data._dimmed = true
  1723. })
  1724. draft.push(...tempEdges)
  1725. })
  1726. setEdges(newEdges)
  1727. }, [isDimming, store])
  1728. /** Restore all nodes to full opacity */
  1729. const undimAllNodes = useCallback(() => {
  1730. const { getNodes, setNodes, edges, setEdges } = store.getState()
  1731. const nodes = getNodes()
  1732. setIsDimming(false)
  1733. const newNodes = produce(nodes, (draft) => {
  1734. draft.forEach((n) => {
  1735. n.data._dimmed = false
  1736. })
  1737. })
  1738. setNodes(newNodes)
  1739. const newEdges = produce(
  1740. edges.filter(e => !e.data._isTemp),
  1741. (draft) => {
  1742. draft.forEach((e) => {
  1743. e.data._dimmed = false
  1744. })
  1745. },
  1746. )
  1747. setEdges(newEdges)
  1748. }, [store])
  1749. return {
  1750. handleNodeDragStart,
  1751. handleNodeDrag,
  1752. handleNodeDragStop,
  1753. handleNodeEnter,
  1754. handleNodeLeave,
  1755. handleNodeSelect,
  1756. handleNodeClick,
  1757. handleNodeConnect,
  1758. handleNodeConnectStart,
  1759. handleNodeConnectEnd,
  1760. handleNodeDelete,
  1761. handleNodeChange,
  1762. handleNodeAdd,
  1763. handleNodesCancelSelected,
  1764. handleNodeContextMenu,
  1765. handleNodesCopy,
  1766. handleNodesPaste,
  1767. handleNodesDuplicate,
  1768. handleNodesDelete,
  1769. handleNodeResize,
  1770. handleNodeDisconnect,
  1771. handleHistoryBack,
  1772. handleHistoryForward,
  1773. dimOtherNodes,
  1774. undimAllNodes,
  1775. }
  1776. }