feat: workflow new nodes (#4683)

Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: Patryk Garstecki <patryk20120@yahoo.pl>
Co-authored-by: Sebastian.W <thiner@gmail.com>
Co-authored-by: 呆萌闷油瓶 <253605712@qq.com>
Co-authored-by: takatost <takatost@users.noreply.github.com>
Co-authored-by: rechardwang <wh_goodjob@163.com>
Co-authored-by: Nite Knite <nkCoding@gmail.com>
Co-authored-by: Chenhe Gu <guchenhe@gmail.com>
Co-authored-by: Joshua <138381132+joshua20231026@users.noreply.github.com>
Co-authored-by: Weaxs <459312872@qq.com>
Co-authored-by: Ikko Eltociear Ashimine <eltociear@gmail.com>
Co-authored-by: leejoo0 <81673835+leejoo0@users.noreply.github.com>
Co-authored-by: JzoNg <jzongcode@gmail.com>
Co-authored-by: sino <sino2322@gmail.com>
Co-authored-by: Vikey Chen <vikeytk@gmail.com>
Co-authored-by: wanghl <Wang-HL@users.noreply.github.com>
Co-authored-by: Haolin Wang-汪皓临 <haolin.wang@atlaslovestravel.com>
Co-authored-by: Zixuan Cheng <61724187+Theysua@users.noreply.github.com>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Co-authored-by: Bowen Liang <bowenliang@apache.org>
Co-authored-by: Bowen Liang <liangbowen@gf.com.cn>
Co-authored-by: fanghongtai <42790567+fanghongtai@users.noreply.github.com>
Co-authored-by: wxfanghongtai <wxfanghongtai@gf.com.cn>
Co-authored-by: Matri <qjp@bithuman.io>
Co-authored-by: Benjamin <benjaminx@gmail.com>
This commit is contained in:
zxhlyh
2024-05-27 21:57:08 +08:00
committed by GitHub
parent 444fdb79dc
commit 45deaee762
210 changed files with 9951 additions and 2223 deletions

View File

@@ -0,0 +1,128 @@
import {
memo,
useCallback,
} from 'react'
import produce from 'immer'
import cn from 'classnames'
import { useStoreApi } from 'reactflow'
import { useTranslation } from 'react-i18next'
import {
generateNewNode,
} from '../../utils'
import {
useAvailableBlocks,
useNodesReadOnly,
} from '../../hooks'
import { NODES_INITIAL_DATA } from '../../constants'
import InsertBlock from './insert-block'
import type { IterationNodeType } from './types'
import BlockSelector from '@/app/components/workflow/block-selector'
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
import { IterationStart } from '@/app/components/base/icons/src/vender/workflow'
import type {
OnSelectBlock,
} from '@/app/components/workflow/types'
import {
BlockEnum,
} from '@/app/components/workflow/types'
import TooltipPlus from '@/app/components/base/tooltip-plus'
type AddBlockProps = {
iterationNodeId: string
iterationNodeData: IterationNodeType
}
const AddBlock = ({
iterationNodeId,
iterationNodeData,
}: AddBlockProps) => {
const { t } = useTranslation()
const store = useStoreApi()
const { nodesReadOnly } = useNodesReadOnly()
const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, true)
const { availablePrevBlocks } = useAvailableBlocks(iterationNodeData.startNodeType, true)
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
const {
getNodes,
setNodes,
} = store.getState()
const nodes = getNodes()
const nodesWithSameType = nodes.filter(node => node.data.type === type)
const newNode = generateNewNode({
data: {
...NODES_INITIAL_DATA[type],
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${type}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${type}`),
...(toolDefaultValue || {}),
isIterationStart: true,
isInIteration: true,
iteration_id: iterationNodeId,
},
position: {
x: 117,
y: 85,
},
zIndex: 1001,
parentId: iterationNodeId,
extent: 'parent',
})
const newNodes = produce(nodes, (draft) => {
draft.forEach((node) => {
if (node.id === iterationNodeId) {
node.data._children = [newNode.id]
node.data.start_node_id = newNode.id
node.data.startNodeType = newNode.data.type
}
})
draft.push(newNode)
})
setNodes(newNodes)
}, [store, t, iterationNodeId])
const renderTriggerElement = useCallback((open: boolean) => {
return (
<div className={cn(
'relative inline-flex items-center px-3 h-8 rounded-lg border-[0.5px] border-gray-50 bg-white shadow-xs cursor-pointer hover:bg-gray-200 text-[13px] font-medium text-gray-700',
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
open && '!bg-gray-50',
)}>
<Plus className='mr-1 w-4 h-4' />
{t('workflow.common.addBlock')}
</div>
)
}, [nodesReadOnly, t])
return (
<div className='absolute top-12 left-6 flex items-center h-8 z-10'>
<TooltipPlus popupContent={t('workflow.blocks.iteration-start')}>
<div className='flex items-center justify-center w-6 h-6 rounded-full border-[0.5px] border-black/[0.02] shadow-md bg-primary-500'>
<IterationStart className='w-4 h-4 text-white' />
</div>
</TooltipPlus>
<div className='group/insert relative w-16 h-0.5 bg-gray-300'>
{
iterationNodeData.startNodeType && (
<InsertBlock
startNodeId={iterationNodeData.start_node_id}
availableBlocksTypes={availablePrevBlocks}
/>
)
}
<div className='absolute right-0 top-1/2 -translate-y-1/2 w-0.5 h-2 bg-primary-500'></div>
</div>
{
!iterationNodeData.startNodeType && (
<BlockSelector
disabled={nodesReadOnly}
onSelect={handleSelect}
trigger={renderTriggerElement}
triggerInnerClassName='inline-flex'
popupClassName='!min-w-[256px]'
availableBlocksTypes={availableNextBlocks}
/>
)
}
</div>
)
}
export default memo(AddBlock)

View File

@@ -0,0 +1,39 @@
import { BlockEnum } from '../../types'
import type { NodeDefault } from '../../types'
import type { IterationNodeType } from './types'
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
const i18nPrefix = 'workflow'
const nodeDefault: NodeDefault<IterationNodeType> = {
defaultValue: {
start_node_id: '',
iterator_selector: [],
output_selector: [],
},
getAvailablePrevNodes(isChatMode: boolean) {
const nodes = isChatMode
? ALL_CHAT_AVAILABLE_BLOCKS
: ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End)
return nodes
},
getAvailableNextNodes(isChatMode: boolean) {
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
return nodes
},
checkValid(payload: IterationNodeType, t: any) {
let errorMessages = ''
if (!errorMessages && (!payload.iterator_selector || payload.iterator_selector.length === 0))
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.nodes.iteration.input`) })
if (!errorMessages && (!payload.output_selector || payload.output_selector.length === 0))
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.nodes.iteration.output`) })
return {
isValid: !errorMessages,
errorMessage: errorMessages,
}
},
}
export default nodeDefault

View File

@@ -0,0 +1,61 @@
import {
memo,
useCallback,
useState,
} from 'react'
import cn from 'classnames'
import { useNodesInteractions } from '../../hooks'
import type {
BlockEnum,
OnSelectBlock,
} from '../../types'
import BlockSelector from '../../block-selector'
type InsertBlockProps = {
startNodeId: string
availableBlocksTypes: BlockEnum[]
}
const InsertBlock = ({
startNodeId,
availableBlocksTypes,
}: InsertBlockProps) => {
const [open, setOpen] = useState(false)
const { handleNodeAdd } = useNodesInteractions()
const handleOpenChange = useCallback((v: boolean) => {
setOpen(v)
}, [])
const handleInsert = useCallback<OnSelectBlock>((nodeType, toolDefaultValue) => {
handleNodeAdd(
{
nodeType,
toolDefaultValue,
},
{
nextNodeId: startNodeId,
nextNodeTargetHandle: 'target',
},
)
}, [startNodeId, handleNodeAdd])
return (
<div
className={cn(
'nopan nodrag',
'hidden group-hover/insert:block absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2',
open && '!block',
)}
>
<BlockSelector
open={open}
onOpenChange={handleOpenChange}
asChild
onSelect={handleInsert}
availableBlocksTypes={availableBlocksTypes}
triggerClassName={() => 'hover:scale-125 transition-all'}
/>
</div>
)
}
export default memo(InsertBlock)

View File

@@ -0,0 +1,49 @@
import type { FC } from 'react'
import {
memo,
useEffect,
} from 'react'
import {
Background,
useNodesInitialized,
useViewport,
} from 'reactflow'
import cn from 'classnames'
import { useNodeIterationInteractions } from './use-interactions'
import type { IterationNodeType } from './types'
import AddBlock from './add-block'
import type { NodeProps } from '@/app/components/workflow/types'
const Node: FC<NodeProps<IterationNodeType>> = ({
id,
data,
}) => {
const { zoom } = useViewport()
const nodesInitialized = useNodesInitialized()
const { handleNodeIterationRerender } = useNodeIterationInteractions()
useEffect(() => {
if (nodesInitialized)
handleNodeIterationRerender(id)
}, [nodesInitialized, id, handleNodeIterationRerender])
return (
<div className={cn(
'relative min-w-[258px] min-h-[118px] w-full h-full rounded-2xl bg-[#F0F2F7]/90',
)}>
<Background
id={`iteration-background-${id}`}
className='rounded-2xl !z-0'
gap={[14 / zoom, 14 / zoom]}
size={2 / zoom}
color='#E4E5E7'
/>
<AddBlock
iterationNodeId={id}
iterationNodeData={data}
/>
</div>
)
}
export default memo(Node)

View File

@@ -0,0 +1,136 @@
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
import Split from '../_base/components/split'
import ResultPanel from '../../run/result-panel'
import IterationResultPanel from '../../run/iteration-result-panel'
import type { IterationNodeType } from './types'
import useConfig from './use-config'
import { InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
import { ArrowNarrowRight } from '@/app/components/base/icons/src/vender/line/arrows'
const i18nPrefix = 'workflow.nodes.iteration'
const Panel: FC<NodePanelProps<IterationNodeType>> = ({
id,
data,
}) => {
const { t } = useTranslation()
const {
readOnly,
inputs,
filterInputVar,
handleInputChange,
childrenNodeVars,
iterationChildrenNodes,
handleOutputVarChange,
isShowSingleRun,
hideSingleRun,
isShowIterationDetail,
backToSingleRun,
showIterationDetail,
hideIterationDetail,
runningStatus,
handleRun,
handleStop,
runResult,
inputVarValues,
setInputVarValues,
usedOutVars,
iterator,
setIterator,
iteratorInputKey,
iterationRunResult,
} = useConfig(id, data)
return (
<div className='mt-2'>
<div className='px-4 pb-4 space-y-4'>
<Field
title={t(`${i18nPrefix}.input`)}
>
<VarReferencePicker
readonly={readOnly}
nodeId={id}
isShowNodeName
value={inputs.iterator_selector || []}
onChange={handleInputChange}
filterVar={filterInputVar}
/>
</Field>
</div>
<Split />
<div className='mt-2 px-4 pb-4 space-y-4'>
<Field
title={t(`${i18nPrefix}.output`)}
operations={(
<div className='flex items-center h-[18px] px-1 border border-black/8 rounded-[5px] text-xs font-medium text-gray-500 capitalize'>Array</div>
)}
>
<VarReferencePicker
readonly={readOnly}
nodeId={id}
isShowNodeName
value={inputs.output_selector || []}
onChange={handleOutputVarChange}
availableNodes={iterationChildrenNodes}
availableVars={childrenNodeVars}
/>
</Field>
</div>
{isShowSingleRun && (
<BeforeRunForm
nodeName={inputs.title}
onHide={hideSingleRun}
forms={[
{
inputs: [...usedOutVars],
values: inputVarValues,
onChange: setInputVarValues,
},
{
label: t(`${i18nPrefix}.input`)!,
inputs: [{
label: '',
variable: iteratorInputKey,
type: InputVarType.iterator,
required: false,
}],
values: { [iteratorInputKey]: iterator },
onChange: keyValue => setIterator((keyValue as any)[iteratorInputKey]),
},
]}
runningStatus={runningStatus}
onRun={handleRun}
onStop={handleStop}
result={
<div className='mt-3'>
<div className='px-4'>
<div className='flex items-center h-[34px] justify-between px-3 bg-gray-100 border-[0.5px] border-gray-200 rounded-lg cursor-pointer' onClick={showIterationDetail}>
<div className='leading-[18px] text-[13px] font-medium text-gray-700'>{t(`${i18nPrefix}.iteration`, { count: iterationRunResult.length })}</div>
<ArrowNarrowRight className='w-3.5 h-3.5 text-gray-500' />
</div>
<Split className='mt-3' />
</div>
<ResultPanel {...runResult} showSteps={false} />
</div>
}
/>
)}
{isShowIterationDetail && (
<IterationResultPanel
onBack={backToSingleRun}
onHide={hideIterationDetail}
list={iterationRunResult}
/>
)}
</div>
)
}
export default React.memo(Panel)

View File

@@ -0,0 +1,15 @@
import type {
BlockEnum,
CommonNodeType,
ValueSelector,
VarType,
} from '@/app/components/workflow/types'
export type IterationNodeType = CommonNodeType & {
startNodeType?: BlockEnum
start_node_id: string // start node id in the iteration
iteration_id?: string
iterator_selector: ValueSelector
output_selector: ValueSelector
output_type: VarType // output type.
}

View File

@@ -0,0 +1,215 @@
import { useCallback } from 'react'
import produce from 'immer'
import { useBoolean } from 'ahooks'
import {
useIsChatMode,
useIsNodeInIteration,
useNodesReadOnly,
useWorkflow,
} from '../../hooks'
import { VarType } from '../../types'
import type { ValueSelector, Var } from '../../types'
import useNodeCrud from '../_base/hooks/use-node-crud'
import { getNodeInfoById, getNodeUsedVarPassToServerKey, getNodeUsedVars, isSystemVar, toNodeOutputVars } from '../_base/components/variable/utils'
import useOneStepRun from '../_base/hooks/use-one-step-run'
import type { IterationNodeType } from './types'
import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
const DELIMITER = '@@@@@'
const useConfig = (id: string, payload: IterationNodeType) => {
const { nodesReadOnly: readOnly } = useNodesReadOnly()
const { isNodeInIteration } = useIsNodeInIteration(id)
const isChatMode = useIsChatMode()
const { inputs, setInputs } = useNodeCrud<IterationNodeType>(id, payload)
const filterInputVar = useCallback((varPayload: Var) => {
return [VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject].includes(varPayload.type)
}, [])
const handleInputChange = useCallback((input: ValueSelector | string) => {
const newInputs = produce(inputs, (draft) => {
draft.iterator_selector = input as ValueSelector || []
})
setInputs(newInputs)
}, [inputs, setInputs])
// output
const { getIterationNodeChildren, getBeforeNodesInSameBranch } = useWorkflow()
const beforeNodes = getBeforeNodesInSameBranch(id)
const iterationChildrenNodes = getIterationNodeChildren(id)
const canChooseVarNodes = [...beforeNodes, ...iterationChildrenNodes]
const childrenNodeVars = toNodeOutputVars(iterationChildrenNodes, isChatMode)
const handleOutputVarChange = useCallback((output: ValueSelector | string, _varKindType: VarKindType, varInfo?: Var) => {
const newInputs = produce(inputs, (draft) => {
draft.output_selector = output as ValueSelector || []
const outputItemType = varInfo?.type || VarType.string
draft.output_type = ({
[VarType.string]: VarType.arrayString,
[VarType.number]: VarType.arrayNumber,
[VarType.object]: VarType.arrayObject,
} as Record<VarType, VarType>)[outputItemType] || VarType.arrayString
})
setInputs(newInputs)
}, [inputs, setInputs])
// single run
const iteratorInputKey = `${id}.input_selector`
const {
isShowSingleRun,
showSingleRun,
hideSingleRun,
toVarInputs,
runningStatus,
handleRun: doHandleRun,
handleStop,
runInputData,
setRunInputData,
runResult,
iterationRunResult,
} = useOneStepRun<IterationNodeType>({
id,
data: inputs,
iteratorInputKey,
defaultRunInputData: {
[iteratorInputKey]: [''],
},
})
const [isShowIterationDetail, {
setTrue: doShowIterationDetail,
setFalse: doHideIterationDetail,
}] = useBoolean(false)
const hideIterationDetail = useCallback(() => {
hideSingleRun()
doHideIterationDetail()
}, [doHideIterationDetail, hideSingleRun])
const showIterationDetail = useCallback(() => {
doShowIterationDetail()
}, [doShowIterationDetail])
const backToSingleRun = useCallback(() => {
hideIterationDetail()
showSingleRun()
}, [hideIterationDetail, showSingleRun])
const { usedOutVars, allVarObject } = (() => {
const vars: ValueSelector[] = []
const varObjs: Record<string, boolean> = {}
const allVarObject: Record<string, {
inSingleRunPassedKey: string
}> = {}
iterationChildrenNodes.forEach((node) => {
const nodeVars = getNodeUsedVars(node).filter(item => item && item.length > 0)
nodeVars.forEach((varSelector) => {
if (varSelector[0] === id) { // skip iteration node itself variable: item, index
return
}
const isInIteration = isNodeInIteration(varSelector[0])
if (isInIteration) // not pass iteration inner variable
return
const varSectorStr = varSelector.join('.')
if (!varObjs[varSectorStr]) {
varObjs[varSectorStr] = true
vars.push(varSelector)
}
let passToServerKeys = getNodeUsedVarPassToServerKey(node, varSelector)
if (typeof passToServerKeys === 'string')
passToServerKeys = [passToServerKeys]
passToServerKeys.forEach((key: string, index: number) => {
allVarObject[[varSectorStr, node.id, index].join(DELIMITER)] = {
inSingleRunPassedKey: key,
}
})
})
})
const res = toVarInputs(vars.map((item) => {
const varInfo = getNodeInfoById(canChooseVarNodes, item[0])
return {
label: {
nodeType: varInfo?.data.type,
nodeName: varInfo?.data.title || canChooseVarNodes[0]?.data.title, // default start node title
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
},
variable: `${item.join('.')}`,
value_selector: item,
}
}))
return {
usedOutVars: res,
allVarObject,
}
})()
const handleRun = useCallback((data: Record<string, any>) => {
const formattedData: Record<string, any> = {}
Object.keys(allVarObject).forEach((key) => {
const [varSectorStr, nodeId] = key.split(DELIMITER)
formattedData[`${nodeId}.${allVarObject[key].inSingleRunPassedKey}`] = data[varSectorStr]
})
formattedData[iteratorInputKey] = data[iteratorInputKey]
doHandleRun(formattedData)
}, [allVarObject, doHandleRun, iteratorInputKey])
const inputVarValues = (() => {
const vars: Record<string, any> = {}
Object.keys(runInputData)
.filter(key => ![iteratorInputKey].includes(key))
.forEach((key) => {
vars[key] = runInputData[key]
})
return vars
})()
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
const newVars = {
...newPayload,
[iteratorInputKey]: runInputData[iteratorInputKey],
}
setRunInputData(newVars)
}, [iteratorInputKey, runInputData, setRunInputData])
const iterator = runInputData[iteratorInputKey]
const setIterator = useCallback((newIterator: string[]) => {
setRunInputData({
...runInputData,
[iteratorInputKey]: newIterator,
})
}, [iteratorInputKey, runInputData, setRunInputData])
return {
readOnly,
inputs,
filterInputVar,
handleInputChange,
childrenNodeVars,
iterationChildrenNodes,
handleOutputVarChange,
isShowSingleRun,
showSingleRun,
hideSingleRun,
isShowIterationDetail,
showIterationDetail,
hideIterationDetail,
backToSingleRun,
runningStatus,
handleRun,
handleStop,
runResult,
inputVarValues,
setInputVarValues,
usedOutVars,
iterator,
setIterator,
iteratorInputKey,
iterationRunResult,
}
}
export default useConfig

View File

@@ -0,0 +1,142 @@
import { useCallback } from 'react'
import produce from 'immer'
import { useTranslation } from 'react-i18next'
import { useStoreApi } from 'reactflow'
import type {
BlockEnum,
Node,
} from '../../types'
import { generateNewNode } from '../../utils'
import {
ITERATION_PADDING,
NODES_INITIAL_DATA,
} from '../../constants'
export const useNodeIterationInteractions = () => {
const { t } = useTranslation()
const store = useStoreApi()
const handleNodeIterationRerender = useCallback((nodeId: string) => {
const {
getNodes,
setNodes,
} = store.getState()
const nodes = getNodes()
const currentNode = nodes.find(n => n.id === nodeId)!
const childrenNodes = nodes.filter(n => n.parentId === nodeId)
let rightNode: Node
let bottomNode: Node
childrenNodes.forEach((n) => {
if (rightNode) {
if (n.position.x + n.width! > rightNode.position.x + rightNode.width!)
rightNode = n
}
else {
rightNode = n
}
if (bottomNode) {
if (n.position.y + n.height! > bottomNode.position.y + bottomNode.height!)
bottomNode = n
}
else {
bottomNode = n
}
})
const widthShouldExtend = rightNode! && currentNode.width! < rightNode.position.x + rightNode.width!
const heightShouldExtend = bottomNode! && currentNode.height! < bottomNode.position.y + bottomNode.height!
if (widthShouldExtend || heightShouldExtend) {
const newNodes = produce(nodes, (draft) => {
draft.forEach((n) => {
if (n.id === nodeId) {
if (widthShouldExtend) {
n.data.width = rightNode.position.x + rightNode.width! + ITERATION_PADDING.right
n.width = rightNode.position.x + rightNode.width! + ITERATION_PADDING.right
}
if (heightShouldExtend) {
n.data.height = bottomNode.position.y + bottomNode.height! + ITERATION_PADDING.bottom
n.height = bottomNode.position.y + bottomNode.height! + ITERATION_PADDING.bottom
}
}
})
})
setNodes(newNodes)
}
}, [store])
const handleNodeIterationChildDrag = useCallback((node: Node) => {
const { getNodes } = store.getState()
const nodes = getNodes()
const restrictPosition: { x?: number; y?: number } = { x: undefined, y: undefined }
if (node.data.isInIteration) {
const parentNode = nodes.find(n => n.id === node.parentId)
if (parentNode) {
if (node.position.y < ITERATION_PADDING.top)
restrictPosition.y = ITERATION_PADDING.top
if (node.position.x < ITERATION_PADDING.left)
restrictPosition.x = ITERATION_PADDING.left
if (node.position.x + node.width! > parentNode!.width! - ITERATION_PADDING.right)
restrictPosition.x = parentNode!.width! - ITERATION_PADDING.right - node.width!
if (node.position.y + node.height! > parentNode!.height! - ITERATION_PADDING.bottom)
restrictPosition.y = parentNode!.height! - ITERATION_PADDING.bottom - node.height!
}
}
return {
restrictPosition,
}
}, [store])
const handleNodeIterationChildSizeChange = useCallback((nodeId: string) => {
const { getNodes } = store.getState()
const nodes = getNodes()
const currentNode = nodes.find(n => n.id === nodeId)!
const parentId = currentNode.parentId
if (parentId)
handleNodeIterationRerender(parentId)
}, [store, handleNodeIterationRerender])
const handleNodeIterationChildrenCopy = useCallback((nodeId: string, newNodeId: string) => {
const { getNodes } = store.getState()
const nodes = getNodes()
const childrenNodes = nodes.filter(n => n.parentId === nodeId)
return childrenNodes.map((child, index) => {
const childNodeType = child.data.type as BlockEnum
const nodesWithSameType = nodes.filter(node => node.data.type === childNodeType)
const newNode = generateNewNode({
data: {
...NODES_INITIAL_DATA[childNodeType],
...child.data,
selected: false,
_isBundled: false,
_connectedSourceHandleIds: [],
_connectedTargetHandleIds: [],
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${childNodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${childNodeType}`),
},
position: child.position,
positionAbsolute: child.positionAbsolute,
parentId: newNodeId,
extent: child.extent,
zIndex: child.zIndex,
})
newNode.id = `${newNodeId}${newNode.id + index}`
return newNode
})
}, [store, t])
return {
handleNodeIterationRerender,
handleNodeIterationChildDrag,
handleNodeIterationChildSizeChange,
handleNodeIterationChildrenCopy,
}
}