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:
@@ -12,3 +12,4 @@ export * from './use-workflow-interactions'
|
||||
export * from './use-selection-interactions'
|
||||
export * from './use-panel-interactions'
|
||||
export * from './use-workflow-start-run'
|
||||
export * from './use-nodes-layout'
|
||||
|
@@ -29,6 +29,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
const isChatMode = useIsChatMode()
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
const workflowTools = useStore(s => s.workflowTools)
|
||||
|
||||
const needWarningNodes = useMemo(() => {
|
||||
const list = []
|
||||
@@ -41,14 +42,16 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
|
||||
if (node.data.type === BlockEnum.Tool) {
|
||||
const { provider_type } = node.data
|
||||
const isBuiltIn = provider_type === CollectionType.builtIn
|
||||
|
||||
moreDataForCheckValid = getToolCheckParams(node.data as ToolNodeType, buildInTools, customTools, language)
|
||||
if (isBuiltIn)
|
||||
moreDataForCheckValid = getToolCheckParams(node.data as ToolNodeType, buildInTools, customTools, workflowTools, language)
|
||||
if (provider_type === CollectionType.builtIn)
|
||||
toolIcon = buildInTools.find(tool => tool.id === node.data.provider_id)?.icon
|
||||
|
||||
if (!isBuiltIn)
|
||||
if (provider_type === CollectionType.custom)
|
||||
toolIcon = customTools.find(tool => tool.id === node.data.provider_id)?.icon
|
||||
|
||||
if (provider_type === CollectionType.workflow)
|
||||
toolIcon = workflowTools.find(tool => tool.id === node.data.provider_id)?.icon
|
||||
}
|
||||
const { errorMessage } = nodesExtraData[node.data.type].checkValid(node.data, t, moreDataForCheckValid)
|
||||
|
||||
@@ -83,7 +86,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
}
|
||||
|
||||
return list
|
||||
}, [t, nodes, edges, nodesExtraData, buildInTools, customTools, language, isChatMode])
|
||||
}, [t, nodes, edges, nodesExtraData, buildInTools, customTools, workflowTools, language, isChatMode])
|
||||
|
||||
return needWarningNodes
|
||||
}
|
||||
@@ -93,6 +96,7 @@ export const useChecklistBeforePublish = () => {
|
||||
const language = useGetLanguage()
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
const workflowTools = useStore(s => s.workflowTools)
|
||||
const { notify } = useToastContext()
|
||||
const isChatMode = useIsChatMode()
|
||||
const store = useStoreApi()
|
||||
@@ -118,7 +122,7 @@ export const useChecklistBeforePublish = () => {
|
||||
const node = nodes[i]
|
||||
let moreDataForCheckValid
|
||||
if (node.data.type === BlockEnum.Tool)
|
||||
moreDataForCheckValid = getToolCheckParams(node.data as ToolNodeType, buildInTools, customTools, language)
|
||||
moreDataForCheckValid = getToolCheckParams(node.data as ToolNodeType, buildInTools, customTools, workflowTools, language)
|
||||
|
||||
const { errorMessage } = nodesExtraData[node.data.type as BlockEnum].checkValid(node.data, t, moreDataForCheckValid)
|
||||
|
||||
@@ -144,7 +148,7 @@ export const useChecklistBeforePublish = () => {
|
||||
}
|
||||
|
||||
return true
|
||||
}, [nodesExtraData, notify, t, store, isChatMode, buildInTools, customTools, language])
|
||||
}, [nodesExtraData, notify, t, store, isChatMode, buildInTools, customTools, workflowTools, language])
|
||||
|
||||
return {
|
||||
handleCheckBeforePublish,
|
||||
|
@@ -5,14 +5,11 @@ import type {
|
||||
OnEdgesChange,
|
||||
} from 'reactflow'
|
||||
import {
|
||||
getConnectedEdges,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import type {
|
||||
Edge,
|
||||
Node,
|
||||
} from '../types'
|
||||
import { BlockEnum } from '../types'
|
||||
import { getNodesConnectedSourceOrTargetHandleIdsMap } from '../utils'
|
||||
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
||||
import { useNodesReadOnly } from './use-workflow'
|
||||
@@ -146,61 +143,6 @@ export const useEdgesInteractions = () => {
|
||||
setEdges(newEdges)
|
||||
}, [store, getNodesReadOnly])
|
||||
|
||||
const handleVariableAssignerEdgesChange = useCallback((nodeId: string, variables: any) => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const newEdgesTargetHandleIds = variables.map((item: any) => item[0])
|
||||
const connectedEdges = getConnectedEdges([{ id: nodeId } as Node], edges).filter(edge => edge.target === nodeId)
|
||||
const needDeleteEdges = connectedEdges.filter(edge => !newEdgesTargetHandleIds.includes(edge.targetHandle))
|
||||
const needAddEdgesTargetHandleIds = newEdgesTargetHandleIds.filter((targetHandle: string) => !connectedEdges.some(edge => edge.targetHandle === targetHandle))
|
||||
const needAddEdges = needAddEdgesTargetHandleIds.map((targetHandle: string) => {
|
||||
return {
|
||||
id: `${targetHandle}-${nodeId}`,
|
||||
type: 'custom',
|
||||
source: targetHandle,
|
||||
sourceHandle: 'source',
|
||||
target: nodeId,
|
||||
targetHandle,
|
||||
data: {
|
||||
sourceType: nodes.find(node => node.id === targetHandle)?.data.type,
|
||||
targetType: BlockEnum.VariableAssigner,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
||||
[
|
||||
...needDeleteEdges.map(edge => ({ type: 'remove', edge })),
|
||||
...needAddEdges.map((edge: Edge) => ({ type: 'add', edge })),
|
||||
],
|
||||
nodes,
|
||||
)
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((node) => {
|
||||
if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
|
||||
node.data = {
|
||||
...node.data,
|
||||
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
const filtered = draft.filter(edge => !needDeleteEdges.map(needDeleteEdge => needDeleteEdge.id).includes(edge.id))
|
||||
|
||||
filtered.push(...needAddEdges)
|
||||
|
||||
return filtered
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}, [store])
|
||||
|
||||
const handleEdgeCancelRunningStatus = useCallback(() => {
|
||||
const {
|
||||
edges,
|
||||
@@ -221,7 +163,6 @@ export const useEdgesInteractions = () => {
|
||||
handleEdgeDeleteByDeleteBranch,
|
||||
handleEdgeDelete,
|
||||
handleEdgesChange,
|
||||
handleVariableAssignerEdgesChange,
|
||||
handleEdgeCancelRunningStatus,
|
||||
}
|
||||
}
|
||||
|
114
web/app/components/workflow/hooks/use-helpline.ts
Normal file
114
web/app/components/workflow/hooks/use-helpline.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import type { Node } from '../types'
|
||||
import { useWorkflowStore } from '../store'
|
||||
|
||||
export const useHelpline = () => {
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
const handleSetHelpline = useCallback((node: Node) => {
|
||||
const { getNodes } = store.getState()
|
||||
const nodes = getNodes()
|
||||
const {
|
||||
setHelpLineHorizontal,
|
||||
setHelpLineVertical,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (node.data.isInIteration) {
|
||||
return {
|
||||
showHorizontalHelpLineNodes: [],
|
||||
showVerticalHelpLineNodes: [],
|
||||
}
|
||||
}
|
||||
const showHorizontalHelpLineNodes = nodes.filter((n) => {
|
||||
if (n.id === node.id)
|
||||
return false
|
||||
|
||||
if (n.data.isInIteration)
|
||||
return false
|
||||
|
||||
const nY = Math.ceil(n.position.y)
|
||||
const nodeY = Math.ceil(node.position.y)
|
||||
|
||||
if (nY - nodeY < 5 && nY - nodeY > -5)
|
||||
return true
|
||||
|
||||
return false
|
||||
}).sort((a, b) => a.position.x - b.position.x)
|
||||
|
||||
const showHorizontalHelpLineNodesLength = showHorizontalHelpLineNodes.length
|
||||
if (showHorizontalHelpLineNodesLength > 0) {
|
||||
const first = showHorizontalHelpLineNodes[0]
|
||||
const last = showHorizontalHelpLineNodes[showHorizontalHelpLineNodesLength - 1]
|
||||
|
||||
const helpLine = {
|
||||
top: first.position.y,
|
||||
left: first.position.x,
|
||||
width: last.position.x + last.width! - first.position.x,
|
||||
}
|
||||
|
||||
if (node.position.x < first.position.x) {
|
||||
helpLine.left = node.position.x
|
||||
helpLine.width = first.position.x + first.width! - node.position.x
|
||||
}
|
||||
|
||||
if (node.position.x > last.position.x)
|
||||
helpLine.width = node.position.x + node.width! - first.position.x
|
||||
|
||||
setHelpLineHorizontal(helpLine)
|
||||
}
|
||||
else {
|
||||
setHelpLineHorizontal()
|
||||
}
|
||||
|
||||
const showVerticalHelpLineNodes = nodes.filter((n) => {
|
||||
if (n.id === node.id)
|
||||
return false
|
||||
if (n.data.isInIteration)
|
||||
return false
|
||||
|
||||
const nX = Math.ceil(n.position.x)
|
||||
const nodeX = Math.ceil(node.position.x)
|
||||
|
||||
if (nX - nodeX < 5 && nX - nodeX > -5)
|
||||
return true
|
||||
|
||||
return false
|
||||
}).sort((a, b) => a.position.x - b.position.x)
|
||||
const showVerticalHelpLineNodesLength = showVerticalHelpLineNodes.length
|
||||
|
||||
if (showVerticalHelpLineNodesLength > 0) {
|
||||
const first = showVerticalHelpLineNodes[0]
|
||||
const last = showVerticalHelpLineNodes[showVerticalHelpLineNodesLength - 1]
|
||||
|
||||
const helpLine = {
|
||||
top: first.position.y,
|
||||
left: first.position.x,
|
||||
height: last.position.y + last.height! - first.position.y,
|
||||
}
|
||||
|
||||
if (node.position.y < first.position.y) {
|
||||
helpLine.top = node.position.y
|
||||
helpLine.height = first.position.y + first.height! - node.position.y
|
||||
}
|
||||
|
||||
if (node.position.y > last.position.y)
|
||||
helpLine.height = node.position.y + node.height! - first.position.y
|
||||
|
||||
setHelpLineVertical(helpLine)
|
||||
}
|
||||
else {
|
||||
setHelpLineVertical()
|
||||
}
|
||||
|
||||
return {
|
||||
showHorizontalHelpLineNodes,
|
||||
showVerticalHelpLineNodes,
|
||||
}
|
||||
}, [store, workflowStore])
|
||||
|
||||
return {
|
||||
handleSetHelpline,
|
||||
}
|
||||
}
|
@@ -22,7 +22,7 @@ export const useNodeDataUpdate = () => {
|
||||
const newNodes = produce(getNodes(), (draft) => {
|
||||
const currentNode = draft.find(node => node.id === id)!
|
||||
|
||||
currentNode.data = { ...currentNode.data, ...data }
|
||||
currentNode.data = { ...currentNode?.data, ...data }
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}, [store])
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import produce from 'immer'
|
||||
import type { BlockEnum } from '../types'
|
||||
import { BlockEnum } from '../types'
|
||||
import {
|
||||
NODES_EXTRA_DATA,
|
||||
NODES_INITIAL_DATA,
|
||||
@@ -30,3 +30,33 @@ export const useNodesExtraData = () => {
|
||||
})
|
||||
}), [t, isChatMode])
|
||||
}
|
||||
|
||||
export const useAvailableBlocks = (nodeType?: BlockEnum, isInIteration?: boolean) => {
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const availablePrevBlocks = useMemo(() => {
|
||||
if (!nodeType)
|
||||
return []
|
||||
return nodesExtraData[nodeType].availablePrevNodes || []
|
||||
}, [nodeType, nodesExtraData])
|
||||
|
||||
const availableNextBlocks = useMemo(() => {
|
||||
if (!nodeType)
|
||||
return []
|
||||
return nodesExtraData[nodeType].availableNextNodes || []
|
||||
}, [nodeType, nodesExtraData])
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
availablePrevBlocks: availablePrevBlocks.filter((nType) => {
|
||||
if (isInIteration && (nType === BlockEnum.Iteration || nType === BlockEnum.End))
|
||||
return false
|
||||
return true
|
||||
}),
|
||||
availableNextBlocks: availableNextBlocks.filter((nType) => {
|
||||
if (isInIteration && (nType === BlockEnum.Iteration || nType === BlockEnum.End))
|
||||
return false
|
||||
return true
|
||||
}),
|
||||
}
|
||||
}, [isInIteration, availablePrevBlocks, availableNextBlocks])
|
||||
}
|
||||
|
@@ -3,11 +3,12 @@ import { useCallback, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import produce from 'immer'
|
||||
import type {
|
||||
HandleType,
|
||||
NodeDragHandler,
|
||||
NodeMouseHandler,
|
||||
OnConnect,
|
||||
OnConnectEnd,
|
||||
OnConnectStart,
|
||||
ResizeParamsWithDirection,
|
||||
} from 'reactflow'
|
||||
import {
|
||||
getConnectedEdges,
|
||||
@@ -24,8 +25,11 @@ import type {
|
||||
import { BlockEnum } from '../types'
|
||||
import { useWorkflowStore } from '../store'
|
||||
import {
|
||||
ITERATION_CHILDREN_Z_INDEX,
|
||||
ITERATION_PADDING,
|
||||
NODES_INITIAL_DATA,
|
||||
NODE_WIDTH_X_OFFSET,
|
||||
X_OFFSET,
|
||||
Y_OFFSET,
|
||||
} from '../constants'
|
||||
import {
|
||||
@@ -33,8 +37,11 @@ import {
|
||||
getNodesConnectedSourceOrTargetHandleIdsMap,
|
||||
getTopLeftNodePosition,
|
||||
} from '../utils'
|
||||
import { useNodesExtraData } from './use-nodes-data'
|
||||
import type { IterationNodeType } from '../nodes/iteration/types'
|
||||
import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types'
|
||||
import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions'
|
||||
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
||||
import { useHelpline } from './use-helpline'
|
||||
import {
|
||||
useNodesReadOnly,
|
||||
useWorkflow,
|
||||
@@ -45,15 +52,17 @@ export const useNodesInteractions = () => {
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const reactflow = useReactFlow()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const {
|
||||
getAfterNodesInSameBranch,
|
||||
getTreeLeafNodes,
|
||||
} = useWorkflow()
|
||||
const { getNodesReadOnly } = useNodesReadOnly()
|
||||
const { handleSetHelpline } = useHelpline()
|
||||
const {
|
||||
handleNodeIterationChildDrag,
|
||||
handleNodeIterationChildrenCopy,
|
||||
} = useNodeIterationInteractions()
|
||||
const dragNodeStartPosition = useRef({ x: 0, y: 0 } as { x: number; y: number })
|
||||
const connectingNodeRef = useRef<{ nodeId: string; handleType: HandleType } | null>(null)
|
||||
|
||||
const handleNodeDragStart = useCallback<NodeDragHandler>((_, node) => {
|
||||
workflowStore.setState({ nodeAnimation: false })
|
||||
@@ -61,6 +70,9 @@ export const useNodesInteractions = () => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
if (node.data.isIterationStart)
|
||||
return
|
||||
|
||||
dragNodeStartPosition.current = { x: node.position.x, y: node.position.y }
|
||||
}, [workflowStore, getNodesReadOnly])
|
||||
|
||||
@@ -68,104 +80,46 @@ export const useNodesInteractions = () => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
if (node.data.isIterationStart)
|
||||
return
|
||||
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
const {
|
||||
setHelpLineHorizontal,
|
||||
setHelpLineVertical,
|
||||
} = workflowStore.getState()
|
||||
e.stopPropagation()
|
||||
|
||||
const nodes = getNodes()
|
||||
|
||||
const showHorizontalHelpLineNodes = nodes.filter((n) => {
|
||||
if (n.id === node.id)
|
||||
return false
|
||||
const { restrictPosition } = handleNodeIterationChildDrag(node)
|
||||
|
||||
const nY = Math.ceil(n.position.y)
|
||||
const nodeY = Math.ceil(node.position.y)
|
||||
|
||||
if (nY - nodeY < 5 && nY - nodeY > -5)
|
||||
return true
|
||||
|
||||
return false
|
||||
}).sort((a, b) => a.position.x - b.position.x)
|
||||
const {
|
||||
showHorizontalHelpLineNodes,
|
||||
showVerticalHelpLineNodes,
|
||||
} = handleSetHelpline(node)
|
||||
const showHorizontalHelpLineNodesLength = showHorizontalHelpLineNodes.length
|
||||
if (showHorizontalHelpLineNodesLength > 0) {
|
||||
const first = showHorizontalHelpLineNodes[0]
|
||||
const last = showHorizontalHelpLineNodes[showHorizontalHelpLineNodesLength - 1]
|
||||
|
||||
const helpLine = {
|
||||
top: first.position.y,
|
||||
left: first.position.x,
|
||||
width: last.position.x + last.width! - first.position.x,
|
||||
}
|
||||
|
||||
if (node.position.x < first.position.x) {
|
||||
helpLine.left = node.position.x
|
||||
helpLine.width = first.position.x + first.width! - node.position.x
|
||||
}
|
||||
|
||||
if (node.position.x > last.position.x)
|
||||
helpLine.width = node.position.x + node.width! - first.position.x
|
||||
|
||||
setHelpLineHorizontal(helpLine)
|
||||
}
|
||||
else {
|
||||
setHelpLineHorizontal()
|
||||
}
|
||||
|
||||
const showVerticalHelpLineNodes = nodes.filter((n) => {
|
||||
if (n.id === node.id)
|
||||
return false
|
||||
|
||||
const nX = Math.ceil(n.position.x)
|
||||
const nodeX = Math.ceil(node.position.x)
|
||||
|
||||
if (nX - nodeX < 5 && nX - nodeX > -5)
|
||||
return true
|
||||
|
||||
return false
|
||||
}).sort((a, b) => a.position.x - b.position.x)
|
||||
const showVerticalHelpLineNodesLength = showVerticalHelpLineNodes.length
|
||||
|
||||
if (showVerticalHelpLineNodesLength > 0) {
|
||||
const first = showVerticalHelpLineNodes[0]
|
||||
const last = showVerticalHelpLineNodes[showVerticalHelpLineNodesLength - 1]
|
||||
|
||||
const helpLine = {
|
||||
top: first.position.y,
|
||||
left: first.position.x,
|
||||
height: last.position.y + last.height! - first.position.y,
|
||||
}
|
||||
|
||||
if (node.position.y < first.position.y) {
|
||||
helpLine.top = node.position.y
|
||||
helpLine.height = first.position.y + first.height! - node.position.y
|
||||
}
|
||||
|
||||
if (node.position.y > last.position.y)
|
||||
helpLine.height = node.position.y + node.height! - first.position.y
|
||||
|
||||
setHelpLineVertical(helpLine)
|
||||
}
|
||||
else {
|
||||
setHelpLineVertical()
|
||||
}
|
||||
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(n => n.id === node.id)!
|
||||
|
||||
currentNode.position = {
|
||||
x: showVerticalHelpLineNodesLength > 0 ? showVerticalHelpLineNodes[0].position.x : node.position.x,
|
||||
y: showHorizontalHelpLineNodesLength > 0 ? showHorizontalHelpLineNodes[0].position.y : node.position.y,
|
||||
}
|
||||
if (showVerticalHelpLineNodesLength > 0)
|
||||
currentNode.position.x = showVerticalHelpLineNodes[0].position.x
|
||||
else if (restrictPosition.x !== undefined)
|
||||
currentNode.position.x = restrictPosition.x
|
||||
else
|
||||
currentNode.position.x = node.position.x
|
||||
|
||||
if (showHorizontalHelpLineNodesLength > 0)
|
||||
currentNode.position.y = showHorizontalHelpLineNodes[0].position.y
|
||||
else if (restrictPosition.y !== undefined)
|
||||
currentNode.position.y = restrictPosition.y
|
||||
else
|
||||
currentNode.position.y = node.position.y
|
||||
})
|
||||
|
||||
setNodes(newNodes)
|
||||
}, [store, workflowStore, getNodesReadOnly])
|
||||
}, [store, getNodesReadOnly, handleSetHelpline, handleNodeIterationChildDrag])
|
||||
|
||||
const handleNodeDragStop = useCallback<NodeDragHandler>((_, node) => {
|
||||
const {
|
||||
@@ -195,20 +149,35 @@ export const useNodesInteractions = () => {
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const {
|
||||
connectingNodePayload,
|
||||
setEnteringNodePayload,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (connectingNodeRef.current && connectingNodeRef.current.nodeId !== node.id) {
|
||||
const connectingNode: Node = nodes.find(n => n.id === connectingNodeRef.current!.nodeId)!
|
||||
const handleType = connectingNodeRef.current.handleType
|
||||
const currentNodeIndex = nodes.findIndex(n => n.id === node.id)
|
||||
const availablePrevNodes = nodesExtraData[connectingNode.data.type].availablePrevNodes
|
||||
const availableNextNodes = nodesExtraData[connectingNode.data.type].availableNextNodes
|
||||
const availableNodes = handleType === 'source' ? availableNextNodes : [...availablePrevNodes, BlockEnum.Start]
|
||||
if (connectingNodePayload) {
|
||||
if (connectingNodePayload.nodeId === node.id)
|
||||
return
|
||||
const connectingNode: Node = nodes.find(n => n.id === connectingNodePayload.nodeId)!
|
||||
const sameLevel = connectingNode.parentId === node.parentId
|
||||
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
if (!availableNodes.includes(draft[currentNodeIndex].data.type))
|
||||
draft[currentNodeIndex].data._isInvalidConnection = true
|
||||
})
|
||||
setNodes(newNodes)
|
||||
if (sameLevel) {
|
||||
setEnteringNodePayload({
|
||||
nodeId: node.id,
|
||||
})
|
||||
const fromType = connectingNodePayload.handleType
|
||||
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((n) => {
|
||||
if (n.id === node.id && fromType === 'source' && (node.data.type === BlockEnum.VariableAssigner || node.data.type === BlockEnum.VariableAggregator)) {
|
||||
if (!node.data.advanced_settings?.group_enabled)
|
||||
n.data._isEntering = true
|
||||
}
|
||||
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)
|
||||
n.data._isEntering = true
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}
|
||||
}
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
const connectedEdges = getConnectedEdges([node], edges)
|
||||
@@ -220,12 +189,16 @@ export const useNodesInteractions = () => {
|
||||
})
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}, [store, nodesExtraData, getNodesReadOnly])
|
||||
}, [store, workflowStore, getNodesReadOnly])
|
||||
|
||||
const handleNodeLeave = useCallback<NodeMouseHandler>(() => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
const {
|
||||
setEnteringNodePayload,
|
||||
} = workflowStore.getState()
|
||||
setEnteringNodePayload(undefined)
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
@@ -234,7 +207,7 @@ export const useNodesInteractions = () => {
|
||||
} = store.getState()
|
||||
const newNodes = produce(getNodes(), (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.data._isInvalidConnection = false
|
||||
node.data._isEntering = false
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
@@ -244,7 +217,7 @@ export const useNodesInteractions = () => {
|
||||
})
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}, [store, getNodesReadOnly])
|
||||
}, [store, workflowStore, getNodesReadOnly])
|
||||
|
||||
const handleNodeSelect = useCallback((nodeId: string, cancelSelection?: boolean) => {
|
||||
const {
|
||||
@@ -315,16 +288,18 @@ export const useNodesInteractions = () => {
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const targetNode = nodes.find(node => node.id === target!)
|
||||
if (targetNode && targetNode?.data.type === BlockEnum.VariableAssigner) {
|
||||
const treeNodes = getTreeLeafNodes(target!)
|
||||
const sourceNode = nodes.find(node => node.id === source!)
|
||||
|
||||
if (targetNode?.parentId !== sourceNode?.parentId)
|
||||
return
|
||||
|
||||
if (targetNode?.data.isIterationStart)
|
||||
return
|
||||
|
||||
if (!treeNodes.find(treeNode => treeNode.id === source))
|
||||
return
|
||||
}
|
||||
const needDeleteEdges = edges.filter((edge) => {
|
||||
if (
|
||||
(edge.source === source && edge.sourceHandle === sourceHandle)
|
||||
|| (edge.target === target && edge.targetHandle === targetHandle)
|
||||
|| (edge.target === target && edge.targetHandle === targetHandle && targetNode?.data.type !== BlockEnum.VariableAssigner && targetNode?.data.type !== BlockEnum.VariableAggregator)
|
||||
)
|
||||
return true
|
||||
|
||||
@@ -332,7 +307,7 @@ export const useNodesInteractions = () => {
|
||||
})
|
||||
const needDeleteEdgesIds = needDeleteEdges.map(edge => edge.id)
|
||||
const newEdge = {
|
||||
id: `${source}-${target}`,
|
||||
id: `${source}-${sourceHandle}-${target}-${targetHandle}`,
|
||||
type: 'custom',
|
||||
source: source!,
|
||||
target: target!,
|
||||
@@ -341,7 +316,10 @@ export const useNodesInteractions = () => {
|
||||
data: {
|
||||
sourceType: nodes.find(node => node.id === source)!.data.type,
|
||||
targetType: nodes.find(node => node.id === target)!.data.type,
|
||||
isInIteration: !!targetNode?.parentId,
|
||||
iteration_id: targetNode?.parentId,
|
||||
},
|
||||
zIndex: targetNode?.parentId ? ITERATION_CHILDREN_Z_INDEX : 0,
|
||||
}
|
||||
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
||||
[
|
||||
@@ -370,20 +348,126 @@ export const useNodesInteractions = () => {
|
||||
})
|
||||
setEdges(newEdges)
|
||||
handleSyncWorkflowDraft()
|
||||
}, [store, handleSyncWorkflowDraft, getNodesReadOnly, getTreeLeafNodes])
|
||||
}, [store, handleSyncWorkflowDraft, getNodesReadOnly])
|
||||
|
||||
const handleNodeConnectStart = useCallback<OnConnectStart>((_, { nodeId, handleType, handleId }) => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
const handleNodeConnectStart = useCallback<OnConnectStart>((_, { nodeId, handleType }) => {
|
||||
if (nodeId && handleType) {
|
||||
connectingNodeRef.current = {
|
||||
nodeId,
|
||||
handleType,
|
||||
const { setConnectingNodePayload } = workflowStore.getState()
|
||||
const { getNodes } = store.getState()
|
||||
const node = getNodes().find(n => n.id === nodeId)!
|
||||
|
||||
if (!node.data.isIterationStart) {
|
||||
setConnectingNodePayload({
|
||||
nodeId,
|
||||
nodeType: node.data.type,
|
||||
handleType,
|
||||
handleId,
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
}, [store, workflowStore, getNodesReadOnly])
|
||||
|
||||
const handleNodeConnectEnd = useCallback(() => {
|
||||
connectingNodeRef.current = null
|
||||
}, [])
|
||||
const handleNodeConnectEnd = useCallback<OnConnectEnd>((e: any) => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
const {
|
||||
connectingNodePayload,
|
||||
setConnectingNodePayload,
|
||||
enteringNodePayload,
|
||||
setEnteringNodePayload,
|
||||
} = workflowStore.getState()
|
||||
if (connectingNodePayload && enteringNodePayload) {
|
||||
const {
|
||||
setShowAssignVariablePopup,
|
||||
hoveringAssignVariableGroupId,
|
||||
} = workflowStore.getState()
|
||||
const { screenToFlowPosition } = reactflow
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const fromHandleType = connectingNodePayload.handleType
|
||||
const fromHandleId = connectingNodePayload.handleId
|
||||
const fromNode = nodes.find(n => n.id === connectingNodePayload.nodeId)!
|
||||
const fromNodeParent = nodes.find(n => n.id === fromNode.parentId)
|
||||
const toNode = nodes.find(n => n.id === enteringNodePayload.nodeId)!
|
||||
const toParentNode = nodes.find(n => n.id === toNode.parentId)
|
||||
|
||||
if (fromNode.parentId !== toNode.parentId)
|
||||
return
|
||||
|
||||
const { x, y } = screenToFlowPosition({ x: e.x, y: e.y })
|
||||
|
||||
if (fromHandleType === 'source' && (toNode.data.type === BlockEnum.VariableAssigner || toNode.data.type === BlockEnum.VariableAggregator)) {
|
||||
const groupEnabled = toNode.data.advanced_settings?.group_enabled
|
||||
|
||||
if (
|
||||
(groupEnabled && hoveringAssignVariableGroupId)
|
||||
|| !groupEnabled
|
||||
) {
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((node) => {
|
||||
if (node.id === toNode.id) {
|
||||
node.data._showAddVariablePopup = true
|
||||
node.data._holdAddVariablePopup = true
|
||||
}
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
setShowAssignVariablePopup({
|
||||
nodeId: fromNode.id,
|
||||
nodeData: fromNode.data,
|
||||
variableAssignerNodeId: toNode.id,
|
||||
variableAssignerNodeData: toNode.data,
|
||||
variableAssignerNodeHandleId: hoveringAssignVariableGroupId || 'target',
|
||||
parentNode: toParentNode,
|
||||
x: x - toNode.positionAbsolute!.x,
|
||||
y: y - toNode.positionAbsolute!.y,
|
||||
})
|
||||
handleNodeConnect({
|
||||
source: fromNode.id,
|
||||
sourceHandle: fromHandleId,
|
||||
target: toNode.id,
|
||||
targetHandle: hoveringAssignVariableGroupId || 'target',
|
||||
})
|
||||
}
|
||||
}
|
||||
if (fromHandleType === 'target' && (fromNode.data.type === BlockEnum.VariableAssigner || fromNode.data.type === BlockEnum.VariableAggregator) && toNode.data.type !== BlockEnum.IfElse && toNode.data.type !== BlockEnum.QuestionClassifier) {
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((node) => {
|
||||
if (node.id === toNode.id) {
|
||||
node.data._showAddVariablePopup = true
|
||||
node.data._holdAddVariablePopup = true
|
||||
}
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
setShowAssignVariablePopup({
|
||||
nodeId: toNode.id,
|
||||
nodeData: toNode.data,
|
||||
variableAssignerNodeId: fromNode.id,
|
||||
variableAssignerNodeData: fromNode.data,
|
||||
variableAssignerNodeHandleId: fromHandleId || 'target',
|
||||
parentNode: fromNodeParent,
|
||||
x: x - toNode.positionAbsolute!.x,
|
||||
y: y - toNode.positionAbsolute!.y,
|
||||
})
|
||||
handleNodeConnect({
|
||||
source: toNode.id,
|
||||
sourceHandle: 'source',
|
||||
target: fromNode.id,
|
||||
targetHandle: fromHandleId,
|
||||
})
|
||||
}
|
||||
}
|
||||
setConnectingNodePayload(undefined)
|
||||
setEnteringNodePayload(undefined)
|
||||
}, [store, handleNodeConnect, getNodesReadOnly, workflowStore, reactflow])
|
||||
|
||||
const handleNodeDelete = useCallback((nodeId: string) => {
|
||||
if (getNodesReadOnly())
|
||||
@@ -398,8 +482,45 @@ export const useNodesInteractions = () => {
|
||||
|
||||
const nodes = getNodes()
|
||||
const currentNodeIndex = nodes.findIndex(node => node.id === nodeId)
|
||||
if (nodes[currentNodeIndex].data.type === BlockEnum.Start)
|
||||
const currentNode = nodes[currentNodeIndex]
|
||||
|
||||
if (!currentNode)
|
||||
return
|
||||
|
||||
if (currentNode.data.type === BlockEnum.Start)
|
||||
return
|
||||
|
||||
if (currentNode.data.type === BlockEnum.Iteration) {
|
||||
const iterationChildren = nodes.filter(node => node.parentId === currentNode.id)
|
||||
|
||||
if (iterationChildren.length) {
|
||||
if (currentNode.data._isBundled) {
|
||||
iterationChildren.forEach((child) => {
|
||||
handleNodeDelete(child.id)
|
||||
})
|
||||
return handleNodeDelete(nodeId)
|
||||
}
|
||||
else {
|
||||
const { setShowConfirm, showConfirm } = workflowStore.getState()
|
||||
|
||||
if (!showConfirm) {
|
||||
setShowConfirm({
|
||||
title: t('workflow.nodes.iteration.deleteTitle'),
|
||||
desc: t('workflow.nodes.iteration.deleteDesc') || '',
|
||||
onConfirm: () => {
|
||||
iterationChildren.forEach((child) => {
|
||||
handleNodeDelete(child.id)
|
||||
})
|
||||
handleNodeDelete(nodeId)
|
||||
handleSyncWorkflowDraft()
|
||||
setShowConfirm(undefined)
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const connectedEdges = getConnectedEdges([{ id: nodeId } as Node], edges)
|
||||
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(connectedEdges.map(edge => ({ type: 'remove', edge })), nodes)
|
||||
const newNodes = produce(nodes, (draft: Node[]) => {
|
||||
@@ -410,6 +531,15 @@ export const useNodesInteractions = () => {
|
||||
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
|
||||
}
|
||||
}
|
||||
|
||||
if (node.id === currentNode.parentId) {
|
||||
node.data._children = node.data._children?.filter(child => child !== nodeId)
|
||||
|
||||
if (currentNode.id === (node as Node<IterationNodeType>).data.start_node_id) {
|
||||
(node as Node<IterationNodeType>).data.start_node_id = '';
|
||||
(node as Node<IterationNodeType>).data.startNodeType = undefined
|
||||
}
|
||||
}
|
||||
})
|
||||
draft.splice(currentNodeIndex, 1)
|
||||
})
|
||||
@@ -419,7 +549,7 @@ export const useNodesInteractions = () => {
|
||||
})
|
||||
setEdges(newEdges)
|
||||
handleSyncWorkflowDraft()
|
||||
}, [store, handleSyncWorkflowDraft, getNodesReadOnly])
|
||||
}, [store, handleSyncWorkflowDraft, getNodesReadOnly, workflowStore, t])
|
||||
|
||||
const handleNodeAdd = useCallback<OnNodeAdd>((
|
||||
{
|
||||
@@ -438,9 +568,6 @@ export const useNodesInteractions = () => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
if (nodeType === BlockEnum.VariableAssigner)
|
||||
targetHandle = 'varNotSet'
|
||||
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
@@ -455,6 +582,8 @@ export const useNodesInteractions = () => {
|
||||
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`),
|
||||
...(toolDefaultValue || {}),
|
||||
selected: true,
|
||||
_showAddVariablePopup: (nodeType === BlockEnum.VariableAssigner || nodeType === BlockEnum.VariableAggregator) && !!prevNodeId,
|
||||
_holdAddVariablePopup: false,
|
||||
},
|
||||
position: {
|
||||
x: 0,
|
||||
@@ -470,12 +599,19 @@ export const useNodesInteractions = () => {
|
||||
newNode.data._connectedTargetHandleIds = [targetHandle]
|
||||
newNode.data._connectedSourceHandleIds = []
|
||||
newNode.position = {
|
||||
x: lastOutgoer ? lastOutgoer.position.x : prevNode.position.x + NODE_WIDTH_X_OFFSET,
|
||||
x: lastOutgoer ? lastOutgoer.position.x : prevNode.position.x + prevNode.width! + X_OFFSET,
|
||||
y: lastOutgoer ? lastOutgoer.position.y + lastOutgoer.height! + Y_OFFSET : prevNode.position.y,
|
||||
}
|
||||
newNode.parentId = prevNode.parentId
|
||||
newNode.extent = prevNode.extent
|
||||
if (prevNode.parentId) {
|
||||
newNode.data.isInIteration = true
|
||||
newNode.data.iteration_id = prevNode.parentId
|
||||
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
|
||||
}
|
||||
|
||||
const newEdge = {
|
||||
id: `${prevNodeId}-${newNode.id}`,
|
||||
const newEdge: Edge = {
|
||||
id: `${prevNodeId}-${prevNodeSourceHandle}-${newNode.id}-${targetHandle}`,
|
||||
type: 'custom',
|
||||
source: prevNodeId,
|
||||
sourceHandle: prevNodeSourceHandle,
|
||||
@@ -484,8 +620,11 @@ export const useNodesInteractions = () => {
|
||||
data: {
|
||||
sourceType: prevNode.data.type,
|
||||
targetType: newNode.data.type,
|
||||
isInIteration: !!prevNode.parentId,
|
||||
iteration_id: prevNode.parentId,
|
||||
_connectedNodeIsSelected: true,
|
||||
},
|
||||
zIndex: prevNode.parentId ? ITERATION_CHILDREN_Z_INDEX : 0,
|
||||
}
|
||||
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
||||
[
|
||||
@@ -503,10 +642,27 @@ export const useNodesInteractions = () => {
|
||||
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
|
||||
}
|
||||
}
|
||||
|
||||
if (node.data.type === BlockEnum.Iteration && prevNode.parentId === node.id)
|
||||
node.data._children?.push(newNode.id)
|
||||
})
|
||||
draft.push(newNode)
|
||||
})
|
||||
setNodes(newNodes)
|
||||
if (newNode.data.type === BlockEnum.VariableAssigner || newNode.data.type === BlockEnum.VariableAggregator) {
|
||||
const { setShowAssignVariablePopup } = workflowStore.getState()
|
||||
|
||||
setShowAssignVariablePopup({
|
||||
nodeId: prevNode.id,
|
||||
nodeData: prevNode.data,
|
||||
variableAssignerNodeId: newNode.id,
|
||||
variableAssignerNodeData: (newNode.data as VariableAssignerNodeType),
|
||||
variableAssignerNodeHandleId: targetHandle,
|
||||
parentNode: nodes.find(node => node.id === newNode.parentId),
|
||||
x: -25,
|
||||
y: 44,
|
||||
})
|
||||
}
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
draft.forEach((item) => {
|
||||
item.data = {
|
||||
@@ -528,12 +684,21 @@ export const useNodesInteractions = () => {
|
||||
x: nextNode.position.x,
|
||||
y: nextNode.position.y,
|
||||
}
|
||||
newNode.parentId = nextNode.parentId
|
||||
newNode.extent = nextNode.extent
|
||||
if (nextNode.parentId) {
|
||||
newNode.data.isInIteration = true
|
||||
newNode.data.iteration_id = nextNode.parentId
|
||||
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
|
||||
}
|
||||
if (nextNode.data.isIterationStart)
|
||||
newNode.data.isIterationStart = true
|
||||
|
||||
let newEdge
|
||||
|
||||
if ((nodeType !== BlockEnum.IfElse) && (nodeType !== BlockEnum.QuestionClassifier)) {
|
||||
newEdge = {
|
||||
id: `${newNode.id}-${nextNodeId}`,
|
||||
id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`,
|
||||
type: 'custom',
|
||||
source: newNode.id,
|
||||
sourceHandle,
|
||||
@@ -542,8 +707,11 @@ export const useNodesInteractions = () => {
|
||||
data: {
|
||||
sourceType: newNode.data.type,
|
||||
targetType: nextNode.data.type,
|
||||
isInIteration: !!nextNode.parentId,
|
||||
iteration_id: nextNode.parentId,
|
||||
_connectedNodeIsSelected: true,
|
||||
},
|
||||
zIndex: nextNode.parentId ? ITERATION_CHILDREN_Z_INDEX : 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,6 +740,17 @@ export const useNodesInteractions = () => {
|
||||
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
|
||||
}
|
||||
}
|
||||
|
||||
if (node.data.type === BlockEnum.Iteration && nextNode.parentId === node.id)
|
||||
node.data._children?.push(newNode.id)
|
||||
|
||||
if (node.data.type === BlockEnum.Iteration && node.data.start_node_id === nextNodeId) {
|
||||
node.data.start_node_id = newNode.id
|
||||
node.data.startNodeType = newNode.data.type
|
||||
}
|
||||
|
||||
if (node.id === nextNodeId && node.data.isIterationStart)
|
||||
node.data.isIterationStart = false
|
||||
})
|
||||
draft.push(newNode)
|
||||
})
|
||||
@@ -599,10 +778,17 @@ export const useNodesInteractions = () => {
|
||||
x: nextNode.position.x,
|
||||
y: nextNode.position.y,
|
||||
}
|
||||
newNode.parentId = prevNode.parentId
|
||||
newNode.extent = prevNode.extent
|
||||
if (prevNode.parentId) {
|
||||
newNode.data.isInIteration = true
|
||||
newNode.data.iteration_id = prevNode.parentId
|
||||
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
|
||||
}
|
||||
|
||||
const currentEdgeIndex = edges.findIndex(edge => edge.source === prevNodeId && edge.target === nextNodeId)
|
||||
const newPrevEdge = {
|
||||
id: `${prevNodeId}-${newNode.id}`,
|
||||
id: `${prevNodeId}-${prevNodeSourceHandle}-${newNode.id}-${targetHandle}`,
|
||||
type: 'custom',
|
||||
source: prevNodeId,
|
||||
sourceHandle: prevNodeSourceHandle,
|
||||
@@ -611,13 +797,16 @@ export const useNodesInteractions = () => {
|
||||
data: {
|
||||
sourceType: prevNode.data.type,
|
||||
targetType: newNode.data.type,
|
||||
isInIteration: !!prevNode.parentId,
|
||||
iteration_id: prevNode.parentId,
|
||||
_connectedNodeIsSelected: true,
|
||||
},
|
||||
zIndex: prevNode.parentId ? ITERATION_CHILDREN_Z_INDEX : 0,
|
||||
}
|
||||
let newNextEdge: Edge | null = null
|
||||
if (nodeType !== BlockEnum.IfElse && nodeType !== BlockEnum.QuestionClassifier) {
|
||||
newNextEdge = {
|
||||
id: `${newNode.id}-${nextNodeId}`,
|
||||
id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`,
|
||||
type: 'custom',
|
||||
source: newNode.id,
|
||||
sourceHandle,
|
||||
@@ -626,8 +815,11 @@ export const useNodesInteractions = () => {
|
||||
data: {
|
||||
sourceType: newNode.data.type,
|
||||
targetType: nextNode.data.type,
|
||||
isInIteration: !!nextNode.parentId,
|
||||
iteration_id: nextNode.parentId,
|
||||
_connectedNodeIsSelected: true,
|
||||
},
|
||||
zIndex: nextNode.parentId ? ITERATION_CHILDREN_Z_INDEX : 0,
|
||||
}
|
||||
}
|
||||
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
||||
@@ -653,10 +845,27 @@ export const useNodesInteractions = () => {
|
||||
}
|
||||
if (afterNodesInSameBranchIds.includes(node.id))
|
||||
node.position.x += NODE_WIDTH_X_OFFSET
|
||||
|
||||
if (node.data.type === BlockEnum.Iteration && prevNode.parentId === node.id)
|
||||
node.data._children?.push(newNode.id)
|
||||
})
|
||||
draft.push(newNode)
|
||||
})
|
||||
setNodes(newNodes)
|
||||
if (newNode.data.type === BlockEnum.VariableAssigner || newNode.data.type === BlockEnum.VariableAggregator) {
|
||||
const { setShowAssignVariablePopup } = workflowStore.getState()
|
||||
|
||||
setShowAssignVariablePopup({
|
||||
nodeId: prevNode.id,
|
||||
nodeData: prevNode.data,
|
||||
variableAssignerNodeId: newNode.id,
|
||||
variableAssignerNodeData: newNode.data as VariableAssignerNodeType,
|
||||
variableAssignerNodeHandleId: targetHandle,
|
||||
parentNode: nodes.find(node => node.id === newNode.parentId),
|
||||
x: -25,
|
||||
y: 44,
|
||||
})
|
||||
}
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
draft.splice(currentEdgeIndex, 1)
|
||||
draft.forEach((item) => {
|
||||
@@ -673,7 +882,7 @@ export const useNodesInteractions = () => {
|
||||
setEdges(newEdges)
|
||||
}
|
||||
handleSyncWorkflowDraft()
|
||||
}, [store, handleSyncWorkflowDraft, getAfterNodesInSameBranch, getNodesReadOnly, t])
|
||||
}, [store, workflowStore, handleSyncWorkflowDraft, getAfterNodesInSameBranch, getNodesReadOnly, t])
|
||||
|
||||
const handleNodeChange = useCallback((
|
||||
currentNodeId: string,
|
||||
@@ -702,11 +911,17 @@ export const useNodesInteractions = () => {
|
||||
_connectedSourceHandleIds: [],
|
||||
_connectedTargetHandleIds: [],
|
||||
selected: currentNode.data.selected,
|
||||
isInIteration: currentNode.data.isInIteration,
|
||||
iteration_id: currentNode.data.iteration_id,
|
||||
isIterationStart: currentNode.data.isIterationStart,
|
||||
},
|
||||
position: {
|
||||
x: currentNode.position.x,
|
||||
y: currentNode.position.y,
|
||||
},
|
||||
parentId: currentNode.parentId,
|
||||
extent: currentNode.extent,
|
||||
zIndex: currentNode.zIndex,
|
||||
})
|
||||
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
||||
[
|
||||
@@ -724,6 +939,14 @@ export const useNodesInteractions = () => {
|
||||
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
|
||||
}
|
||||
}
|
||||
if (node.id === currentNode.parentId && currentNode.data.isIterationStart) {
|
||||
node.data._children = [
|
||||
newCurrentNode.id,
|
||||
...(node.data._children || []),
|
||||
].filter(child => child !== currentNodeId)
|
||||
node.data.start_node_id = newCurrentNode.id
|
||||
node.data.startNodeType = newCurrentNode.data.type
|
||||
}
|
||||
})
|
||||
const index = draft.findIndex(node => node.id === currentNodeId)
|
||||
|
||||
@@ -801,7 +1024,7 @@ export const useNodesInteractions = () => {
|
||||
} = store.getState()
|
||||
|
||||
const nodes = getNodes()
|
||||
const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start)
|
||||
const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start && !node.data.isInIteration)
|
||||
|
||||
if (bundledNodes.length) {
|
||||
setClipboardElements(bundledNodes)
|
||||
@@ -860,51 +1083,43 @@ export const useNodesInteractions = () => {
|
||||
x: nodeToPaste.position.x + offsetX,
|
||||
y: nodeToPaste.position.y + offsetY,
|
||||
},
|
||||
extent: nodeToPaste.extent,
|
||||
zIndex: nodeToPaste.zIndex,
|
||||
})
|
||||
newNode.id = newNode.id + index
|
||||
|
||||
let newChildren: Node[] = []
|
||||
if (nodeToPaste.data.type === BlockEnum.Iteration) {
|
||||
newNode.data._children = [];
|
||||
(newNode.data as IterationNodeType).start_node_id = ''
|
||||
|
||||
newChildren = handleNodeIterationChildrenCopy(nodeToPaste.id, newNode.id)
|
||||
|
||||
newChildren.forEach((child) => {
|
||||
newNode.data._children?.push(child.id)
|
||||
if (child.data.isIterationStart)
|
||||
(newNode.data as IterationNodeType).start_node_id = child.id
|
||||
})
|
||||
}
|
||||
|
||||
nodesToPaste.push(newNode)
|
||||
|
||||
if (newChildren.length)
|
||||
nodesToPaste.push(...newChildren)
|
||||
})
|
||||
|
||||
setNodes([...nodes, ...nodesToPaste])
|
||||
handleSyncWorkflowDraft()
|
||||
}
|
||||
}, [t, getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, reactflow])
|
||||
}, [t, getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, reactflow, handleNodeIterationChildrenCopy])
|
||||
|
||||
const handleNodesDuplicate = useCallback(() => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
|
||||
const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start)
|
||||
|
||||
if (selectedNode) {
|
||||
const nodeType = selectedNode.data.type
|
||||
const nodesWithSameType = nodes.filter(node => node.data.type === nodeType)
|
||||
|
||||
const newNode = generateNewNode({
|
||||
data: {
|
||||
...NODES_INITIAL_DATA[nodeType as BlockEnum],
|
||||
...selectedNode.data,
|
||||
selected: false,
|
||||
_isBundled: false,
|
||||
_connectedSourceHandleIds: [],
|
||||
_connectedTargetHandleIds: [],
|
||||
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`),
|
||||
},
|
||||
position: {
|
||||
x: selectedNode.position.x + selectedNode.width! + 10,
|
||||
y: selectedNode.position.y,
|
||||
},
|
||||
})
|
||||
|
||||
setNodes([...nodes, newNode])
|
||||
}
|
||||
}, [store, t, getNodesReadOnly])
|
||||
handleNodesCopy()
|
||||
handleNodesPaste()
|
||||
}, [getNodesReadOnly, handleNodesCopy, handleNodesPaste])
|
||||
|
||||
const handleNodesDelete = useCallback(() => {
|
||||
if (getNodesReadOnly())
|
||||
@@ -928,6 +1143,7 @@ export const useNodesInteractions = () => {
|
||||
|
||||
if (bundledNodes.length) {
|
||||
bundledNodes.forEach(node => handleNodeDelete(node.id))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -941,6 +1157,61 @@ export const useNodesInteractions = () => {
|
||||
handleNodeDelete(selectedNode.id)
|
||||
}, [store, workflowStore, getNodesReadOnly, handleNodeDelete])
|
||||
|
||||
const handleNodeResize = useCallback((nodeId: string, params: ResizeParamsWithDirection) => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
const { x, y, width, height } = params
|
||||
|
||||
const nodes = getNodes()
|
||||
const currentNode = nodes.find(n => n.id === nodeId)!
|
||||
const childrenNodes = nodes.filter(n => currentNode.data._children?.includes(n.id))
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
if (rightNode! && bottomNode!) {
|
||||
if (width < rightNode!.position.x + rightNode.width! + ITERATION_PADDING.right)
|
||||
return
|
||||
if (height < bottomNode.position.y + bottomNode.height! + ITERATION_PADDING.bottom)
|
||||
return
|
||||
}
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((n) => {
|
||||
if (n.id === nodeId) {
|
||||
n.data.width = width
|
||||
n.data.height = height
|
||||
n.width = width
|
||||
n.height = height
|
||||
n.position.x = x
|
||||
n.position.y = y
|
||||
}
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
handleSyncWorkflowDraft()
|
||||
}, [store, getNodesReadOnly, handleSyncWorkflowDraft])
|
||||
|
||||
return {
|
||||
handleNodeDragStart,
|
||||
handleNodeDrag,
|
||||
@@ -962,5 +1233,6 @@ export const useNodesInteractions = () => {
|
||||
handleNodesPaste,
|
||||
handleNodesDuplicate,
|
||||
handleNodesDelete,
|
||||
handleNodeResize,
|
||||
}
|
||||
}
|
||||
|
96
web/app/components/workflow/hooks/use-nodes-layout.ts
Normal file
96
web/app/components/workflow/hooks/use-nodes-layout.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { useCallback } from 'react'
|
||||
import ELK from 'elkjs/lib/elk.bundled.js'
|
||||
import {
|
||||
useReactFlow,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type {
|
||||
Edge,
|
||||
Node,
|
||||
} from '../types'
|
||||
import { useWorkflowStore } from '../store'
|
||||
import { AUTO_LAYOUT_OFFSET } from '../constants'
|
||||
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
||||
|
||||
const layoutOptions = {
|
||||
'elk.algorithm': 'layered',
|
||||
'elk.direction': 'RIGHT',
|
||||
'elk.layered.spacing.nodeNodeBetweenLayers': '60',
|
||||
'elk.spacing.nodeNode': '40',
|
||||
'elk.layered.nodePlacement.strategy': 'SIMPLE',
|
||||
}
|
||||
|
||||
const elk = new ELK()
|
||||
|
||||
export const getLayoutedNodes = async (nodes: Node[], edges: Edge[]) => {
|
||||
const graph = {
|
||||
id: 'root',
|
||||
layoutOptions,
|
||||
children: nodes.map((n) => {
|
||||
return {
|
||||
...n,
|
||||
width: n.width ?? 150,
|
||||
height: n.height ?? 50,
|
||||
targetPosition: 'left',
|
||||
sourcePosition: 'right',
|
||||
}
|
||||
}),
|
||||
edges: cloneDeep(edges),
|
||||
}
|
||||
|
||||
const layoutedGraph = await elk.layout(graph as any)
|
||||
const layoutedNodes = nodes.map((node) => {
|
||||
const layoutedNode = layoutedGraph.children?.find(
|
||||
lgNode => lgNode.id === node.id,
|
||||
)
|
||||
|
||||
return {
|
||||
...node,
|
||||
position: {
|
||||
x: (layoutedNode?.x ?? 0) + AUTO_LAYOUT_OFFSET.x,
|
||||
y: (layoutedNode?.y ?? 0) + AUTO_LAYOUT_OFFSET.y,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
layoutedNodes,
|
||||
}
|
||||
}
|
||||
|
||||
export const useNodesLayout = () => {
|
||||
const store = useStoreApi()
|
||||
const reactflow = useReactFlow()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
|
||||
const handleNodesLayout = useCallback(async () => {
|
||||
workflowStore.setState({ nodeAnimation: true })
|
||||
const {
|
||||
getNodes,
|
||||
edges,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
const { setViewport } = reactflow
|
||||
const nodes = getNodes()
|
||||
const {
|
||||
layoutedNodes,
|
||||
} = await getLayoutedNodes(nodes, edges)
|
||||
|
||||
setNodes(layoutedNodes)
|
||||
const zoom = 0.7
|
||||
setViewport({
|
||||
x: 0,
|
||||
y: 0,
|
||||
zoom,
|
||||
})
|
||||
setTimeout(() => {
|
||||
handleSyncWorkflowDraft()
|
||||
})
|
||||
}, [store, reactflow, handleSyncWorkflowDraft, workflowStore])
|
||||
|
||||
return {
|
||||
handleNodesLayout,
|
||||
}
|
||||
}
|
@@ -85,6 +85,7 @@ export const useWorkflowRun = () => {
|
||||
const newNodes = produce(getNodes(), (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.data.selected = false
|
||||
node.data._runningStatus = undefined
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
@@ -95,6 +96,9 @@ export const useWorkflowRun = () => {
|
||||
onWorkflowFinished,
|
||||
onNodeStarted,
|
||||
onNodeFinished,
|
||||
onIterationStart,
|
||||
onIterationNext,
|
||||
onIterationFinish,
|
||||
onError,
|
||||
...restCallback
|
||||
} = callback || {}
|
||||
@@ -127,6 +131,9 @@ export const useWorkflowRun = () => {
|
||||
resultText: '',
|
||||
})
|
||||
|
||||
let isInIteration = false
|
||||
let iterationLength = 0
|
||||
|
||||
ssePost(
|
||||
url,
|
||||
{
|
||||
@@ -176,7 +183,7 @@ export const useWorkflowRun = () => {
|
||||
draft.result = {
|
||||
...draft.result,
|
||||
...data,
|
||||
}
|
||||
} as any
|
||||
}))
|
||||
|
||||
prevNodeId = ''
|
||||
@@ -213,39 +220,53 @@ export const useWorkflowRun = () => {
|
||||
setEdges,
|
||||
transform,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.tracing!.push({
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
} as any)
|
||||
}))
|
||||
if (isInIteration) {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
const iterations = tracing[tracing.length - 1]
|
||||
const currIteration = iterations.details![iterations.details!.length - 1]
|
||||
currIteration.push({
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
} as any)
|
||||
}))
|
||||
}
|
||||
else {
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.tracing!.push({
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
} as any)
|
||||
}))
|
||||
|
||||
const {
|
||||
setViewport,
|
||||
} = reactflow
|
||||
const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id)
|
||||
const currentNode = nodes[currentNodeIndex]
|
||||
const position = currentNode.position
|
||||
const zoom = transform[2]
|
||||
const {
|
||||
setViewport,
|
||||
} = reactflow
|
||||
const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id)
|
||||
const currentNode = nodes[currentNodeIndex]
|
||||
const position = currentNode.position
|
||||
const zoom = transform[2]
|
||||
|
||||
setViewport({
|
||||
x: (clientWidth - 400 - currentNode.width! * zoom) / 2 - position.x * zoom,
|
||||
y: (clientHeight - currentNode.height! * zoom) / 2 - position.y * zoom,
|
||||
zoom: transform[2],
|
||||
})
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
const edge = draft.find(edge => edge.target === data.node_id && edge.source === prevNodeId)
|
||||
|
||||
if (edge)
|
||||
edge.data = { ...edge.data, _runned: true } as any
|
||||
})
|
||||
setEdges(newEdges)
|
||||
if (!currentNode.parentId) {
|
||||
setViewport({
|
||||
x: (clientWidth - 400 - currentNode.width! * zoom) / 2 - position.x * zoom,
|
||||
y: (clientHeight - currentNode.height! * zoom) / 2 - position.y * zoom,
|
||||
zoom: transform[2],
|
||||
})
|
||||
}
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
const edge = draft.find(edge => edge.target === data.node_id && edge.source === prevNodeId)
|
||||
|
||||
if (edge)
|
||||
edge.data = { ...edge.data, _runned: true } as any
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}
|
||||
if (onNodeStarted)
|
||||
onNodeStarted(params)
|
||||
},
|
||||
@@ -259,31 +280,166 @@ export const useWorkflowRun = () => {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
if (isInIteration) {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
const iterations = tracing[tracing.length - 1]
|
||||
const currIteration = iterations.details![iterations.details!.length - 1]
|
||||
const nodeInfo = currIteration[currIteration.length - 1]
|
||||
|
||||
currIteration[currIteration.length - 1] = {
|
||||
...nodeInfo,
|
||||
...data,
|
||||
status: NodeRunningStatus.Succeeded,
|
||||
} as any
|
||||
}))
|
||||
}
|
||||
else {
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const currentIndex = draft.tracing!.findIndex(trace => trace.node_id === data.node_id)
|
||||
|
||||
if (currentIndex > -1 && draft.tracing) {
|
||||
draft.tracing[currentIndex] = {
|
||||
...(draft.tracing[currentIndex].extras
|
||||
? { extras: draft.tracing[currentIndex].extras }
|
||||
: {}),
|
||||
...data,
|
||||
} as any
|
||||
}
|
||||
}))
|
||||
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||
|
||||
currentNode.data._runningStatus = data.status as any
|
||||
})
|
||||
setNodes(newNodes)
|
||||
|
||||
prevNodeId = data.node_id
|
||||
}
|
||||
if (onNodeFinished)
|
||||
onNodeFinished(params)
|
||||
},
|
||||
onIterationStart: (params) => {
|
||||
const { data } = params
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
transform,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const currentIndex = draft.tracing!.findIndex(trace => trace.node_id === data.node_id)
|
||||
|
||||
if (currentIndex > -1 && draft.tracing) {
|
||||
draft.tracing[currentIndex] = {
|
||||
...(draft.tracing[currentIndex].extras
|
||||
? { extras: draft.tracing[currentIndex].extras }
|
||||
: {}),
|
||||
...data,
|
||||
} as any
|
||||
}
|
||||
draft.tracing!.push({
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
details: [],
|
||||
} as any)
|
||||
}))
|
||||
isInIteration = true
|
||||
iterationLength = data.metadata.iterator_length
|
||||
|
||||
const {
|
||||
setViewport,
|
||||
} = reactflow
|
||||
const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id)
|
||||
const currentNode = nodes[currentNodeIndex]
|
||||
const position = currentNode.position
|
||||
const zoom = transform[2]
|
||||
|
||||
if (!currentNode.parentId) {
|
||||
setViewport({
|
||||
x: (clientWidth - 400 - currentNode.width! * zoom) / 2 - position.x * zoom,
|
||||
y: (clientHeight - currentNode.height! * zoom) / 2 - position.y * zoom,
|
||||
zoom: transform[2],
|
||||
})
|
||||
}
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
|
||||
draft[currentNodeIndex].data._iterationLength = data.metadata.iterator_length
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
const edge = draft.find(edge => edge.target === data.node_id && edge.source === prevNodeId)
|
||||
|
||||
if (edge)
|
||||
edge.data = { ...edge.data, _runned: true } as any
|
||||
})
|
||||
setEdges(newEdges)
|
||||
|
||||
if (onIterationStart)
|
||||
onIterationStart(params)
|
||||
},
|
||||
onIterationNext: (params) => {
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
|
||||
const { data } = params
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const iteration = draft.tracing![draft.tracing!.length - 1]
|
||||
if (iteration.details!.length >= iterationLength)
|
||||
return
|
||||
|
||||
iteration.details!.push([])
|
||||
}))
|
||||
|
||||
const nodes = getNodes()
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||
|
||||
currentNode.data._iterationIndex = data.index > 0 ? data.index : 1
|
||||
})
|
||||
setNodes(newNodes)
|
||||
|
||||
if (onIterationNext)
|
||||
onIterationNext(params)
|
||||
},
|
||||
onIterationFinish: (params) => {
|
||||
const { data } = params
|
||||
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
tracing[tracing.length - 1] = {
|
||||
...tracing[tracing.length - 1],
|
||||
...data,
|
||||
status: NodeRunningStatus.Succeeded,
|
||||
} as any
|
||||
}))
|
||||
isInIteration = false
|
||||
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||
|
||||
currentNode.data._runningStatus = data.status as any
|
||||
currentNode.data._runningStatus = data.status
|
||||
})
|
||||
setNodes(newNodes)
|
||||
|
||||
prevNodeId = data.node_id
|
||||
|
||||
if (onNodeFinished)
|
||||
onNodeFinished(params)
|
||||
if (onIterationFinish)
|
||||
onIterationFinish(params)
|
||||
},
|
||||
onTextChunk: (params) => {
|
||||
const { data: { text } } = params
|
||||
|
@@ -21,6 +21,7 @@ import {
|
||||
getLayoutByDagre,
|
||||
} from '../utils'
|
||||
import type {
|
||||
Edge,
|
||||
Node,
|
||||
ValueSelector,
|
||||
} from '../types'
|
||||
@@ -33,7 +34,6 @@ import {
|
||||
useWorkflowStore,
|
||||
} from '../store'
|
||||
import {
|
||||
AUTO_LAYOUT_OFFSET,
|
||||
SUPPORT_OUTPUT_VARS_NODE,
|
||||
} from '../constants'
|
||||
import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
|
||||
@@ -51,8 +51,10 @@ import type { FetchWorkflowDraftResponse } from '@/types/workflow'
|
||||
import {
|
||||
fetchAllBuiltInTools,
|
||||
fetchAllCustomTools,
|
||||
fetchAllWorkflowTools,
|
||||
} from '@/service/tools'
|
||||
import I18n from '@/context/i18n'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
|
||||
export const useIsChatMode = () => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
@@ -83,13 +85,31 @@ export const useWorkflow = () => {
|
||||
const { setViewport } = reactflow
|
||||
const nodes = getNodes()
|
||||
const layout = getLayoutByDagre(nodes, edges)
|
||||
const rankMap = {} as Record<string, Node>
|
||||
|
||||
nodes.forEach((node) => {
|
||||
if (!node.parentId) {
|
||||
const rank = layout.node(node.id).rank!
|
||||
|
||||
if (!rankMap[rank]) {
|
||||
rankMap[rank] = node
|
||||
}
|
||||
else {
|
||||
if (rankMap[rank].position.y > node.position.y)
|
||||
rankMap[rank] = node
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((node) => {
|
||||
const nodeWithPosition = layout.node(node.id)
|
||||
node.position = {
|
||||
x: nodeWithPosition.x + AUTO_LAYOUT_OFFSET.x,
|
||||
y: nodeWithPosition.y + AUTO_LAYOUT_OFFSET.y,
|
||||
if (!node.parentId) {
|
||||
const nodeWithPosition = layout.node(node.id)
|
||||
|
||||
node.position = {
|
||||
x: nodeWithPosition.x - node.width! / 2,
|
||||
y: nodeWithPosition.y - node.height! / 2 + rankMap[nodeWithPosition.rank!].height! / 2,
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -111,7 +131,11 @@ export const useWorkflow = () => {
|
||||
edges,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
let startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
const currentNode = nodes.find(node => node.id === nodeId)
|
||||
|
||||
if (currentNode?.parentId)
|
||||
startNode = nodes.find(node => node.parentId === currentNode.parentId && node.data.isIterationStart)
|
||||
|
||||
if (!startNode)
|
||||
return []
|
||||
@@ -145,21 +169,31 @@ export const useWorkflow = () => {
|
||||
})
|
||||
}, [store])
|
||||
|
||||
const getBeforeNodesInSameBranch = useCallback((nodeId: string) => {
|
||||
const getBeforeNodesInSameBranch = useCallback((nodeId: string, newNodes?: Node[], newEdges?: Edge[]) => {
|
||||
const {
|
||||
getNodes,
|
||||
edges,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const nodes = newNodes || getNodes()
|
||||
const currentNode = nodes.find(node => node.id === nodeId)
|
||||
|
||||
const list: Node[] = []
|
||||
|
||||
if (!currentNode)
|
||||
return list
|
||||
|
||||
if (currentNode.parentId) {
|
||||
const parentNode = nodes.find(node => node.id === currentNode.parentId)
|
||||
if (parentNode) {
|
||||
const parentList = getBeforeNodesInSameBranch(parentNode.id)
|
||||
|
||||
list.push(...parentList)
|
||||
}
|
||||
}
|
||||
|
||||
const traverse = (root: Node, callback: (node: Node) => void) => {
|
||||
if (root) {
|
||||
const incomers = getIncomers(root, nodes, edges)
|
||||
const incomers = getIncomers(root, nodes, newEdges || edges)
|
||||
|
||||
if (incomers.length) {
|
||||
incomers.forEach((node) => {
|
||||
@@ -185,6 +219,21 @@ export const useWorkflow = () => {
|
||||
return []
|
||||
}, [store])
|
||||
|
||||
const getBeforeNodesInSameBranchIncludeParent = useCallback((nodeId: string, newNodes?: Node[], newEdges?: Edge[]) => {
|
||||
const nodes = getBeforeNodesInSameBranch(nodeId, newNodes, newEdges)
|
||||
const {
|
||||
getNodes,
|
||||
} = store.getState()
|
||||
const allNodes = getNodes()
|
||||
const node = allNodes.find(n => n.id === nodeId)
|
||||
const parentNodeId = node?.parentId
|
||||
const parentNode = allNodes.find(n => n.id === parentNodeId)
|
||||
if (parentNode)
|
||||
nodes.push(parentNode)
|
||||
|
||||
return nodes
|
||||
}, [getBeforeNodesInSameBranch, store])
|
||||
|
||||
const getAfterNodesInSameBranch = useCallback((nodeId: string) => {
|
||||
const {
|
||||
getNodes,
|
||||
@@ -227,11 +276,19 @@ export const useWorkflow = () => {
|
||||
return getIncomers(node, nodes, edges)
|
||||
}, [store])
|
||||
|
||||
const getIterationNodeChildren = useCallback((nodeId: string) => {
|
||||
const {
|
||||
getNodes,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
|
||||
return nodes.filter(node => node.parentId === nodeId)
|
||||
}, [store])
|
||||
|
||||
const handleOutVarRenameChange = useCallback((nodeId: string, oldValeSelector: ValueSelector, newVarSelector: ValueSelector) => {
|
||||
const { getNodes, setNodes } = store.getState()
|
||||
const afterNodes = getAfterNodesInSameBranch(nodeId)
|
||||
const effectNodes = findUsedVarNodes(oldValeSelector, afterNodes)
|
||||
// console.log(effectNodes)
|
||||
if (effectNodes.length > 0) {
|
||||
const newNodes = getNodes().map((node) => {
|
||||
if (effectNodes.find(n => n.id === node.id))
|
||||
@@ -285,9 +342,13 @@ export const useWorkflow = () => {
|
||||
const sourceNode: Node = nodes.find(node => node.id === source)!
|
||||
const targetNode: Node = nodes.find(node => node.id === target)!
|
||||
|
||||
if (targetNode.data.isIterationStart)
|
||||
return false
|
||||
|
||||
if (sourceNode && targetNode) {
|
||||
const sourceNodeAvailableNextNodes = nodesExtraData[sourceNode.data.type].availableNextNodes
|
||||
const targetNodeAvailablePrevNodes = [...nodesExtraData[targetNode.data.type].availablePrevNodes, BlockEnum.Start]
|
||||
|
||||
if (!sourceNodeAvailableNextNodes.includes(targetNode.data.type))
|
||||
return false
|
||||
|
||||
@@ -338,6 +399,7 @@ export const useWorkflow = () => {
|
||||
handleLayout,
|
||||
getTreeLeafNodes,
|
||||
getBeforeNodesInSameBranch,
|
||||
getBeforeNodesInSameBranchIncludeParent,
|
||||
getAfterNodesInSameBranch,
|
||||
handleOutVarRenameChange,
|
||||
isVarUsedInNodes,
|
||||
@@ -347,6 +409,7 @@ export const useWorkflow = () => {
|
||||
formatTimeFromNow,
|
||||
getNode,
|
||||
getBeforeNodeById,
|
||||
getIterationNodeChildren,
|
||||
enableShortcuts,
|
||||
disableShortcuts,
|
||||
}
|
||||
@@ -370,6 +433,13 @@ export const useFetchToolsData = () => {
|
||||
customTools: customTools || [],
|
||||
})
|
||||
}
|
||||
if (type === 'workflow') {
|
||||
const workflowTools = await fetchAllWorkflowTools()
|
||||
|
||||
workflowStore.setState({
|
||||
workflowTools: workflowTools || [],
|
||||
})
|
||||
}
|
||||
}, [workflowStore])
|
||||
|
||||
return {
|
||||
@@ -448,11 +518,14 @@ export const useWorkflowInit = () => {
|
||||
handleFetchPreloadData()
|
||||
handleFetchAllTools('builtin')
|
||||
handleFetchAllTools('custom')
|
||||
handleFetchAllTools('workflow')
|
||||
}, [handleFetchPreloadData, handleFetchAllTools])
|
||||
|
||||
useEffect(() => {
|
||||
if (data)
|
||||
if (data) {
|
||||
workflowStore.getState().setDraftUpdatedAt(data.updated_at)
|
||||
workflowStore.getState().setToolPublished(data.tool_published)
|
||||
}
|
||||
}, [data, workflowStore])
|
||||
|
||||
return {
|
||||
@@ -499,14 +572,42 @@ export const useNodesReadOnly = () => {
|
||||
export const useToolIcon = (data: Node['data']) => {
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
const workflowTools = useStore(s => s.workflowTools)
|
||||
const toolIcon = useMemo(() => {
|
||||
if (data.type === BlockEnum.Tool) {
|
||||
if (data.provider_type === 'builtin')
|
||||
return buildInTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.icon
|
||||
|
||||
return customTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.icon
|
||||
let targetTools = buildInTools
|
||||
if (data.provider_type === CollectionType.builtIn)
|
||||
targetTools = buildInTools
|
||||
else if (data.provider_type === CollectionType.custom)
|
||||
targetTools = customTools
|
||||
else
|
||||
targetTools = workflowTools
|
||||
return targetTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.icon
|
||||
}
|
||||
}, [data, buildInTools, customTools])
|
||||
}, [data, buildInTools, customTools, workflowTools])
|
||||
|
||||
return toolIcon
|
||||
}
|
||||
|
||||
export const useIsNodeInIteration = (iterationId: string) => {
|
||||
const store = useStoreApi()
|
||||
|
||||
const isNodeInIteration = useCallback((nodeId: string) => {
|
||||
const {
|
||||
getNodes,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const node = nodes.find(node => node.id === nodeId)
|
||||
|
||||
if (!node)
|
||||
return false
|
||||
|
||||
if (node.parentId === iterationId)
|
||||
return true
|
||||
|
||||
return false
|
||||
}, [iterationId, store])
|
||||
return {
|
||||
isNodeInIteration,
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user