diff --git a/web/app/components/workflow/hooks/use-edges-interactions.ts b/web/app/components/workflow/hooks/use-edges-interactions.ts index 5dedadd2aa..b4f3afd8ad 100644 --- a/web/app/components/workflow/hooks/use-edges-interactions.ts +++ b/web/app/components/workflow/hooks/use-edges-interactions.ts @@ -4,9 +4,16 @@ import type { EdgeMouseHandler, OnEdgesChange, } from 'reactflow' -import { useStoreApi } from 'reactflow' +import { + getConnectedEdges, + useStoreApi, +} from 'reactflow' import { useStore } from '../store' -import type { Node } from '../types' +import type { + Edge, + Node, +} from '../types' +import { getNodesConnectedSourceOrTargetHandleIdsMap } from '../utils' import { useNodesSyncDraft } from './use-nodes-sync-draft' export const useEdgesInteractions = () => { @@ -140,11 +147,63 @@ export const useEdgesInteractions = () => { setEdges(newEdges) }, [store]) + 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, + } + }) + + 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]) + return { handleEdgeEnter, handleEdgeLeave, handleEdgeDeleteByDeleteBranch, handleEdgeDelete, handleEdgesChange, + handleVariableAssignerEdgesChange, } } diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index 3abed9caf9..b60d9c629f 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -12,10 +12,10 @@ import { } from 'reactflow' import type { ToolDefaultValue } from '../block-selector/types' import type { - BlockEnum, Node, OnNodeAdd, } from '../types' +import { BlockEnum } from '../types' import { useStore } from '../store' import { NODE_WIDTH_X_OFFSET, @@ -362,6 +362,9 @@ export const useNodesInteractions = () => { if (runningStatus) return + if (nodeType === BlockEnum.VariableAssigner) + targetHandle = 'varNotSet' + const { getNodes, setNodes, @@ -369,9 +372,11 @@ export const useNodesInteractions = () => { setEdges, } = store.getState() const nodes = getNodes() + const nodesWithSameType = nodes.filter(node => node.data.type === nodeType) const newNode = generateNewNode({ data: { ...nodesInitialData[nodeType], + title: nodesWithSameType.length > 0 ? `${nodesInitialData[nodeType].title} ${nodesWithSameType.length + 1}` : nodesInitialData[nodeType].title, ...(toolDefaultValue || {}), selected: true, }, @@ -386,6 +391,7 @@ export const useNodesInteractions = () => { const outgoers = getOutgoers(prevNode, nodes, edges).sort((a, b) => a.position.y - b.position.y) const lastOutgoer = outgoers[outgoers.length - 1] newNode.data._connectedTargetHandleIds = [targetHandle] + newNode.data._connectedSourceHandleIds = [] newNode.position = { x: lastOutgoer ? lastOutgoer.position.x : prevNode.position.x + NODE_WIDTH_X_OFFSET, y: lastOutgoer ? lastOutgoer.position.y + lastOutgoer.height! + Y_OFFSET : prevNode.position.y, @@ -418,6 +424,7 @@ export const useNodesInteractions = () => { const nextNodeIndex = nodes.findIndex(node => node.id === nextNodeId) const nextNode = nodes[nextNodeIndex]! newNode.data._connectedSourceHandleIds = [sourceHandle] + newNode.data._connectedTargetHandleIds = [] newNode.position = { x: nextNode.position.x, y: nextNode.position.y, @@ -534,10 +541,14 @@ export const useNodesInteractions = () => { const nodes = getNodes() const currentNode = nodes.find(node => node.id === currentNodeId)! const connectedEdges = getConnectedEdges([currentNode], edges) + const nodesWithSameType = nodes.filter(node => node.data.type === nodeType) const newCurrentNode = generateNewNode({ data: { ...nodesInitialData[nodeType], + title: nodesWithSameType.length > 0 ? `${nodesInitialData[nodeType].title} ${nodesWithSameType.length + 1}` : nodesInitialData[nodeType].title, ...(toolDefaultValue || {}), + _connectedSourceHandleIds: [], + _connectedTargetHandleIds: [], selected: currentNode.data.selected, }, position: { diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index 1757184750..62fe9ff409 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -45,10 +45,10 @@ export const useWorkflow = () => { edges, setNodes, } = store.getState() + const nodes = getNodes() + const layout = getLayoutByDagre(nodes, edges) - const layout = getLayoutByDagre(getNodes(), edges) - - const newNodes = produce(getNodes(), (draft) => { + const newNodes = produce(nodes, (draft) => { draft.forEach((node) => { const nodeWithPosition = layout.node(node.id) node.position = { @@ -168,11 +168,21 @@ export const useWorkflow = () => { return list }, [store]) + const getIncomersNodes = useCallback((currentNode: Node) => { + const { + getNodes, + edges, + } = store.getState() + + return getIncomers(currentNode, getNodes(), edges) + }, [store]) + return { handleLayout, getTreeLeafNodes, getBeforeNodesInSameBranch, getAfterNodesInSameBranch, + getIncomersNodes, } } diff --git a/web/app/components/workflow/nodes/variable-assigner/components/var-list/use-var-list.ts b/web/app/components/workflow/nodes/variable-assigner/components/var-list/use-var-list.ts index 84e278432f..1d8cd5ace9 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/var-list/use-var-list.ts +++ b/web/app/components/workflow/nodes/variable-assigner/components/var-list/use-var-list.ts @@ -2,21 +2,26 @@ import { useCallback } from 'react' import produce from 'immer' import type { VariableAssignerNodeType } from '../../types' import type { ValueSelector } from '@/app/components/workflow/types' +import { useEdgesInteractions } from '@/app/components/workflow/hooks' type Params = { + id: string inputs: VariableAssignerNodeType setInputs: (newInputs: VariableAssignerNodeType) => void } function useVarList({ + id, inputs, setInputs, }: Params) { + const { handleVariableAssignerEdgesChange } = useEdgesInteractions() const handleVarListChange = useCallback((newList: ValueSelector[]) => { const newInputs = produce(inputs, (draft) => { draft.variables = newList }) + handleVariableAssignerEdgesChange(id, newList) setInputs(newInputs) - }, [inputs, setInputs]) + }, [inputs, setInputs, id, handleVariableAssignerEdgesChange]) const handleAddVariable = useCallback(() => { const newInputs = produce(inputs, (draft) => { diff --git a/web/app/components/workflow/nodes/variable-assigner/node.tsx b/web/app/components/workflow/nodes/variable-assigner/node.tsx index 4166c81b4a..2e482f1c6f 100644 --- a/web/app/components/workflow/nodes/variable-assigner/node.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/node.tsx @@ -13,8 +13,9 @@ const i18nPrefix = 'workflow.nodes.variableAssigner' const Node: FC> = (props) => { const { t } = useTranslation() const { data } = props - const { variables, output_type } = data + const { variables: originVariables, output_type } = data + const variables = originVariables.filter(item => item.length > 0) // TODO: get var type through node and value const getVarType = () => { return 'string' @@ -25,8 +26,13 @@ const Node: FC> = (props) => {
{t(`${i18nPrefix}.title`)}
{ variables.length === 0 && ( -
+
{t(`${i18nPrefix}.varNotSet`)} +
) } @@ -36,12 +42,13 @@ const Node: FC> = (props) => { {variables.map((item, index) => { const node = getNodeInfoById([], item[0]) // TODO: can not get all nodes const varName = item[item.length - 1] + return (
diff --git a/web/app/components/workflow/nodes/variable-assigner/use-config.ts b/web/app/components/workflow/nodes/variable-assigner/use-config.ts index 83009144b3..1aa26a49e0 100644 --- a/web/app/components/workflow/nodes/variable-assigner/use-config.ts +++ b/web/app/components/workflow/nodes/variable-assigner/use-config.ts @@ -15,6 +15,7 @@ const useConfig = (id: string, payload: VariableAssignerNodeType) => { }, [inputs, setInputs]) const { handleVarListChange, handleAddVariable } = useVarList({ + id, inputs, setInputs, }) diff --git a/web/app/components/workflow/utils.ts b/web/app/components/workflow/utils.ts index 3af92ee62c..0c733a74f0 100644 --- a/web/app/components/workflow/utils.ts +++ b/web/app/components/workflow/utils.ts @@ -4,6 +4,7 @@ import { getOutgoers, } from 'reactflow' import dagre from 'dagre' +import { cloneDeep } from 'lodash-es' import type { Edge, Node, @@ -145,7 +146,9 @@ export const getNodesPositionMap = (nodes: Node[], edges: Edge[]) => { return positionMap } -export const getLayoutByDagre = (nodes: Node[], edges: Edge[]) => { +export const getLayoutByDagre = (originNodes: Node[], originEdges: Edge[]) => { + const nodes = cloneDeep(originNodes) + const edges = cloneDeep(originEdges) const dagreGraph = new dagre.graphlib.Graph() dagreGraph.setGraph({ rankdir: 'LR',