Kaynağa Gözat

feat: workflow variable aggregator support group (#4811)

Co-authored-by: Yeuoly <admin@srmxy.cn>
zxhlyh 10 ay önce
ebeveyn
işleme
4b91383efc

+ 2 - 2
api/core/workflow/nodes/variable_aggregator/entities.py

@@ -7,7 +7,7 @@ from pydantic import BaseModel
 from core.workflow.entities.base_node_data_entities import BaseNodeData
 
 
-class AdvancedSetting(BaseModel):
+class AdvancedSettings(BaseModel):
     """
     Advanced setting.
     """
@@ -30,4 +30,4 @@ class VariableAssignerNodeData(BaseNodeData):
     type: str = 'variable-assigner'
     output_type: str
     variables: list[list[str]]
-    advanced_setting: Optional[AdvancedSetting]
+    advanced_settings: Optional[AdvancedSettings]

+ 5 - 3
api/core/workflow/nodes/variable_aggregator/variable_aggregator_node.py

@@ -18,7 +18,7 @@ class VariableAggregatorNode(BaseNode):
         outputs = {}
         inputs = {}
 
-        if not node_data.advanced_setting or node_data.advanced_setting.group_enabled:
+        if not node_data.advanced_settings or not node_data.advanced_settings.group_enabled:
             for variable in node_data.variables:
                 value = variable_pool.get_variable_value(variable)
 
@@ -32,12 +32,14 @@ class VariableAggregatorNode(BaseNode):
                     }
                     break
         else:
-            for group in node_data.advanced_setting.groups:
+            for group in node_data.advanced_settings.groups:
                 for variable in group.variables:
                     value = variable_pool.get_variable_value(variable)
 
                     if value is not None:
-                        outputs[f'{group.group_name}_output'] = value
+                        outputs[group.group_name] = {
+                            'output': value
+                        }
                         inputs['.'.join(variable[1:])] = value
                         break
 

+ 25 - 44
web/app/components/workflow/hooks/use-nodes-interactions.ts

@@ -164,6 +164,7 @@ export const useNodesInteractions = () => {
       if (sameLevel) {
         setEnteringNodePayload({
           nodeId: node.id,
+          nodeData: node.data as VariableAssignerNodeType,
         })
         const fromType = connectingNodePayload.handleType
 
@@ -360,6 +361,11 @@ export const useNodesInteractions = () => {
       const { getNodes } = store.getState()
       const node = getNodes().find(n => n.id === nodeId)!
 
+      if (node.data.type === BlockEnum.VariableAggregator || node.data.type === BlockEnum.VariableAssigner) {
+        if (handleType === 'target')
+          return
+      }
+
       if (!node.data.isIterationStart) {
         setConnectingNodePayload({
           nodeId,
@@ -395,7 +401,6 @@ export const useNodesInteractions = () => {
       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)
 
@@ -406,39 +411,15 @@ export const useNodesInteractions = () => {
 
       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',
-          })
+        const firstGroupId = toNode.data.advanced_settings?.groups[0].groupId
+        let handleId = 'target'
+
+        if (groupEnabled) {
+          if (hoveringAssignVariableGroupId)
+            handleId = hoveringAssignVariableGroupId
+          else
+            handleId = firstGroupId
         }
-      }
-      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) {
@@ -449,20 +430,20 @@ export const useNodesInteractions = () => {
         })
         setNodes(newNodes)
         setShowAssignVariablePopup({
-          nodeId: toNode.id,
-          nodeData: toNode.data,
-          variableAssignerNodeId: fromNode.id,
-          variableAssignerNodeData: fromNode.data,
-          variableAssignerNodeHandleId: fromHandleId || 'target',
-          parentNode: fromNodeParent,
+          nodeId: fromNode.id,
+          nodeData: fromNode.data,
+          variableAssignerNodeId: toNode.id,
+          variableAssignerNodeData: toNode.data,
+          variableAssignerNodeHandleId: handleId,
+          parentNode: toParentNode,
           x: x - toNode.positionAbsolute!.x,
           y: y - toNode.positionAbsolute!.y,
         })
         handleNodeConnect({
-          source: toNode.id,
-          sourceHandle: 'source',
-          target: fromNode.id,
-          targetHandle: fromHandleId,
+          source: fromNode.id,
+          sourceHandle: fromHandleId,
+          target: toNode.id,
+          targetHandle: 'target',
         })
       }
     }
@@ -1111,7 +1092,7 @@ export const useNodesInteractions = () => {
       setNodes([...nodes, ...nodesToPaste])
       handleSyncWorkflowDraft()
     }
-  }, [t, getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, reactflow, handleNodeIterationChildrenCopy])
+  }, [getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, reactflow, handleNodeIterationChildrenCopy])
 
   const handleNodesDuplicate = useCallback(() => {
     if (getNodesReadOnly())

+ 1 - 1
web/app/components/workflow/index.tsx

@@ -207,7 +207,7 @@ const Workflow: FC<WorkflowProps> = memo(({
   })
 
   useKeyPress('delete', handleNodesDelete)
-  useKeyPress('delete', handleEdgeDelete)
+  useKeyPress(['delete', 'backspace'], handleEdgeDelete)
   useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.c`, handleNodesCopy, { exactMatch: true, useCapture: true })
   useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.v`, handleNodesPaste, { exactMatch: true, useCapture: true })
   useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.d`, handleNodesDuplicate, { exactMatch: true, useCapture: true })

+ 3 - 1
web/app/components/workflow/nodes/_base/components/add-variable-popup-with-position.tsx

@@ -43,7 +43,9 @@ const AddVariablePopupWithPosition = ({
     if (!showAssignVariablePopup)
       return ''
 
-    if (showAssignVariablePopup.variableAssignerNodeHandleId === 'target')
+    const groupEnabled = showAssignVariablePopup.variableAssignerNodeData.advanced_settings?.group_enabled
+
+    if (!groupEnabled)
       return showAssignVariablePopup.variableAssignerNodeData.output_type
 
     const group = showAssignVariablePopup.variableAssignerNodeData.advanced_settings?.groups.find(group => group.groupId === showAssignVariablePopup.variableAssignerNodeHandleId)

+ 1 - 3
web/app/components/workflow/nodes/_base/components/node-handle.tsx

@@ -113,13 +113,11 @@ export const NodeSourceHandle = memo(({
   nodeSelectorClassName,
 }: NodeHandleProps) => {
   const notInitialWorkflow = useStore(s => s.notInitialWorkflow)
-  const connectingNodePayload = useStore(s => s.connectingNodePayload)
   const [open, setOpen] = useState(false)
   const { handleNodeAdd } = useNodesInteractions()
   const { getNodesReadOnly } = useNodesReadOnly()
   const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration)
-  const isUnConnectable = !availableNextBlocks.length || ((connectingNodePayload?.nodeType === BlockEnum.VariableAssigner || connectingNodePayload?.nodeType === BlockEnum.VariableAggregator) && connectingNodePayload?.handleType === 'target')
-  const isConnectable = !isUnConnectable
+  const isConnectable = !!availableNextBlocks.length
 
   const connected = data._connectedSourceHandleIds?.includes(handleId)
   const handleOpenChange = useCallback((v: boolean) => {

+ 1 - 1
web/app/components/workflow/nodes/_base/node.tsx

@@ -117,7 +117,7 @@ const BaseNode: FC<BaseNodeProps> = ({
           )
         }
         {
-          data.type !== BlockEnum.VariableAssigner && data.type !== BlockEnum.VariableAggregator && !data._isCandidate && (
+          !data._isCandidate && (
             <NodeTargetHandle
               id={id}
               data={data}

+ 19 - 17
web/app/components/workflow/nodes/variable-assigner/components/add-variable/index.tsx

@@ -1,6 +1,7 @@
 import {
   memo,
   useCallback,
+  useState,
 } from 'react'
 import cn from 'classnames'
 import { useVariableAssigner } from '../../hooks'
@@ -19,21 +20,18 @@ import type {
 } from '@/app/components/workflow/types'
 
 export type AddVariableProps = {
-  open: boolean
-  onOpenChange: (open: boolean) => void
   variableAssignerNodeId: string
   variableAssignerNodeData: VariableAssignerNodeType
   availableVars: NodeOutPutVar[]
   handleId?: string
 }
 const AddVariable = ({
-  open,
-  onOpenChange,
   availableVars,
   variableAssignerNodeId,
   variableAssignerNodeData,
   handleId,
 }: AddVariableProps) => {
+  const [open, setOpen] = useState(false)
   const { handleAssignVariableValueChange } = useVariableAssigner()
 
   const handleSelectVariable = useCallback((v: ValueSelector, varDetail: Var) => {
@@ -43,34 +41,38 @@ const AddVariable = ({
       varDetail,
       handleId,
     )
-    onOpenChange(false)
-  }, [handleAssignVariableValueChange, variableAssignerNodeId, handleId, onOpenChange])
+    setOpen(false)
+  }, [handleAssignVariableValueChange, variableAssignerNodeId, handleId, setOpen])
 
   return (
     <div className={cn(
-      'hidden group-hover:flex absolute top-0 left-0 z-10 pointer-events-none',
       open && '!flex',
       variableAssignerNodeData.selected && '!flex',
     )}>
       <PortalToFollowElem
-        placement={'left-start'}
-        offset={{
-          mainAxis: 4,
-          crossAxis: -60,
-        }}
+        placement={'right'}
+        offset={4}
         open={open}
-        onOpenChange={onOpenChange}
+        onOpenChange={setOpen}
       >
         <PortalToFollowElemTrigger
-          onClick={() => onOpenChange(!open)}
+          onClick={() => setOpen(!open)}
         >
           <div
             className={cn(
-              'flex items-center justify-center',
-              'w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10',
+              'group/addvariable flex items-center justify-center',
+              'w-4 h-4 cursor-pointer',
+              'hover:rounded-full hover:bg-primary-600',
+              open && '!rounded-full !bg-primary-600',
             )}
           >
-            <Plus02 className='w-2.5 h-2.5 text-white' />
+            <Plus02
+              className={cn(
+                'w-2.5 h-2.5 text-gray-500',
+                'group-hover/addvariable:text-white',
+                open && '!text-white',
+              )}
+            />
           </div>
         </PortalToFollowElemTrigger>
         <PortalToFollowElemContent className='z-[1000]'>

+ 54 - 18
web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx

@@ -18,7 +18,7 @@ import {
   useVariableAssigner,
 } from '../hooks'
 import { filterVar } from '../utils'
-import NodeHandle from './node-handle'
+import AddVariable from './add-variable'
 import NodeVariableItem from './node-variable-item'
 import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
 
@@ -47,40 +47,75 @@ const NodeGroupItem = ({
     handleGroupItemMouseLeave,
   } = useVariableAssigner()
   const getAvailableVars = useGetAvailableVars()
+  const groupEnabled = item.groupEnabled
   const outputType = useMemo(() => {
-    if (item.targetHandleId === 'target')
+    if (!groupEnabled)
       return item.variableAssignerNodeData.output_type
 
     const group = item.variableAssignerNodeData.advanced_settings?.groups.find(group => group.groupId === item.targetHandleId)
     return group?.output_type || ''
-  }, [item.variableAssignerNodeData, item.targetHandleId])
+  }, [item.variableAssignerNodeData, item.targetHandleId, groupEnabled])
   const availableVars = getAvailableVars(item.variableAssignerNodeId, item.targetHandleId, filterVar(outputType as VarType))
-  const showSelectionBorder = enteringNodePayload?.nodeId === item.variableAssignerNodeId && item.groupEnabled && hoveringAssignVariableGroupId === item.targetHandleId
-  const connected = item.variableAssignerNodeData._connectedTargetHandleIds?.includes(item.targetHandleId)
+  const showSelectionBorder = useMemo(() => {
+    if (groupEnabled && enteringNodePayload?.nodeId === item.variableAssignerNodeId) {
+      if (hoveringAssignVariableGroupId)
+        return hoveringAssignVariableGroupId !== item.targetHandleId
+      else
+        return enteringNodePayload?.nodeData.advanced_settings?.groups[0].groupId !== item.targetHandleId
+    }
+
+    return false
+  }, [enteringNodePayload, groupEnabled, hoveringAssignVariableGroupId, item.targetHandleId, item.variableAssignerNodeId])
+  const showSelectedBorder = useMemo(() => {
+    if (groupEnabled && enteringNodePayload?.nodeId === item.variableAssignerNodeId) {
+      if (hoveringAssignVariableGroupId)
+        return hoveringAssignVariableGroupId === item.targetHandleId
+      else
+        return enteringNodePayload?.nodeData.advanced_settings?.groups[0].groupId === item.targetHandleId
+    }
+
+    return false
+  }, [enteringNodePayload, groupEnabled, hoveringAssignVariableGroupId, item.targetHandleId, item.variableAssignerNodeId])
 
   return (
     <div
       className={cn(
-        'relative pt-1 px-1.5 pb-1.5 rounded-lg border border-transparent',
-        showSelectionBorder && '!border-primary-600',
+        'relative pt-1 px-1.5 pb-1.5 rounded-lg border-[1.5px] border-transparent',
+        showSelectionBorder && '!border-gray-300 !border-dashed bg-black/[0.02]',
+        showSelectedBorder && '!border-primary-600 !bg-primary-50',
       )}
-      onMouseEnter={() => handleGroupItemMouseEnter(item.targetHandleId)}
+      onMouseEnter={() => groupEnabled && handleGroupItemMouseEnter(item.targetHandleId)}
       onMouseLeave={handleGroupItemMouseLeave}
     >
       <div className='flex items-center justify-between h-4 text-[10px] font-medium text-gray-500'>
-        <NodeHandle
-          connected={connected}
-          variableAssignerNodeId={item.variableAssignerNodeId}
-          variableAssignerNodeData={item.variableAssignerNodeData}
-          handleId={item.targetHandleId}
-          availableVars={availableVars}
-        />
-        <span className='grow uppercase truncate' title={item.title}>{item.title}</span>
-        <span className='shrink-0 ml-2'>{item.type}</span>
+        <span
+          className={cn(
+            'grow uppercase truncate',
+            showSelectedBorder && 'text-primary-600',
+          )}
+          title={item.title}
+        >
+          {item.title}
+        </span>
+        <div className='flex items-center'>
+          <span className='shrink-0 ml-2'>{item.type}</span>
+          <div className='ml-2 mr-1 w-[1px] h-2.5 bg-gray-200'></div>
+          <AddVariable
+            availableVars={availableVars}
+            variableAssignerNodeId={item.variableAssignerNodeId}
+            variableAssignerNodeData={item.variableAssignerNodeData}
+            handleId={item.targetHandleId}
+          />
+        </div>
       </div>
       {
         !item.variables.length && (
-          <div className='relative flex items-center px-1 h-[22px] justify-between bg-gray-100 rounded-md space-x-1 text-[10px] font-normal text-gray-400 uppercase'>
+          <div
+            className={cn(
+              'relative flex items-center px-1 h-[22px] justify-between bg-gray-100 rounded-md space-x-1 text-[10px] font-normal text-gray-400 uppercase',
+              (showSelectedBorder || showSelectionBorder) && '!bg-black/[0.02]',
+            )}
+          >
             {t(`${i18nPrefix}.varNotSet`)}
           </div>
         )
@@ -96,6 +131,7 @@ const NodeGroupItem = ({
               key={index}
               node={node as Node}
               varName={varName}
+              showBorder={showSelectedBorder || showSelectionBorder}
             />
           )
         })

+ 0 - 69
web/app/components/workflow/nodes/variable-assigner/components/node-handle.tsx

@@ -1,69 +0,0 @@
-import type { MouseEvent } from 'react'
-import {
-  memo,
-  useCallback,
-  useState,
-} from 'react'
-import cn from 'classnames'
-import {
-  Handle,
-  Position,
-} from 'reactflow'
-import type { VariableAssignerNodeType } from '../types'
-import AddVariable from './add-variable'
-import type { NodeOutPutVar } from '@/app/components/workflow/types'
-import { useStore } from '@/app/components/workflow/store'
-
-type NodeHandleProps = {
-  handleId?: string
-  connected?: boolean
-  variableAssignerNodeId: string
-  availableVars: NodeOutPutVar[]
-  variableAssignerNodeData: VariableAssignerNodeType
-}
-const NodeHandle = ({
-  connected,
-  variableAssignerNodeId,
-  handleId = 'target',
-  availableVars,
-  variableAssignerNodeData,
-}: NodeHandleProps) => {
-  const [open, setOpen] = useState(false)
-  const connectingNodePayload = useStore(s => s.connectingNodePayload)
-  const isUnConnectable = connectingNodePayload?.handleType === 'source'
-
-  const handleOpenChange = useCallback((v: boolean) => {
-    setOpen(v)
-  }, [])
-
-  const handleHandleClick = useCallback((e: MouseEvent) => {
-    e.stopPropagation()
-    setOpen(v => !v)
-  }, [])
-
-  return (
-    <Handle
-      id={handleId}
-      type='target'
-      onClick={handleHandleClick}
-      position={Position.Left}
-      isConnectable={!isUnConnectable}
-      className={cn(
-        '!-left-[13px] !top-1 !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1] !transform-none',
-        'after:absolute after:w-0.5 after:h-2 after:left-[5px] after:top-1 after:bg-primary-500 pointer-events-none',
-        !connected && 'after:opacity-0',
-      )}
-    >
-      <AddVariable
-        open={open}
-        onOpenChange={handleOpenChange}
-        variableAssignerNodeId={variableAssignerNodeId}
-        variableAssignerNodeData={variableAssignerNodeData}
-        handleId={handleId}
-        availableVars={availableVars}
-      />
-    </Handle>
-  )
-}
-
-export default memo(NodeHandle)

+ 7 - 1
web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx

@@ -1,4 +1,5 @@
 import { memo } from 'react'
+import cn from 'classnames'
 import { VarBlockIcon } from '@/app/components/workflow/block-icon'
 import { Line3 } from '@/app/components/base/icons/src/public/common'
 import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
@@ -8,13 +9,18 @@ import { BlockEnum } from '@/app/components/workflow/types'
 type NodeVariableItemProps = {
   node: Node
   varName: string
+  showBorder?: boolean
 }
 const NodeVariableItem = ({
   node,
   varName,
+  showBorder,
 }: NodeVariableItemProps) => {
   return (
-    <div className='relative flex items-center mt-0.5 h-6 bg-gray-100 rounded-md  px-1 text-xs font-normal text-gray-700' >
+    <div className={cn(
+      'relative flex items-center mt-0.5 h-6 bg-gray-100 rounded-md  px-1 text-xs font-normal text-gray-700',
+      showBorder && '!bg-black/[0.02]',
+    )}>
       <div className='flex items-center'>
         <div className='p-[1px]'>
           <VarBlockIcon

+ 5 - 97
web/app/components/workflow/nodes/variable-assigner/hooks.ts

@@ -1,6 +1,5 @@
 import { useCallback } from 'react'
 import {
-  useEdges,
   useNodes,
   useStoreApi,
 } from 'reactflow'
@@ -12,9 +11,7 @@ import {
   useNodeDataUpdate,
   useWorkflow,
 } from '../../hooks'
-import { getNodesConnectedSourceOrTargetHandleIdsMap } from '../../utils'
 import type {
-  Edge,
   Node,
   ValueSelector,
   Var,
@@ -99,82 +96,6 @@ export const useVariableAssigner = () => {
     handleAssignVariableValueChange(variableAssignerNodeId, value, varDetail, variableAssignerNodeHandleId)
   }, [store, workflowStore, handleAssignVariableValueChange])
 
-  const handleRemoveEdges = useCallback((nodeId: string, enabled: boolean) => {
-    const {
-      getNodes,
-      setNodes,
-      edges,
-      setEdges,
-    } = store.getState()
-    const nodes = getNodes()
-    const needDeleteEdges = edges.filter(edge => edge.target === nodeId)
-
-    if (!needDeleteEdges.length)
-      return
-
-    const currentNode = nodes.find(node => node.id === nodeId)!
-    const groups = currentNode.data.advanced_settings?.groups || []
-
-    let shouldKeepEdges: Edge[] = []
-
-    if (enabled) {
-      shouldKeepEdges = edges.filter((edge) => {
-        return edge.target === nodeId && edge.targetHandle === 'target'
-      }).map((edge) => {
-        return {
-          ...edge,
-          targetHandle: groups[0].groupId,
-        }
-      })
-    }
-    else {
-      shouldKeepEdges = edges.filter((edge) => {
-        return edge.target === nodeId && edge.targetHandle === groups[0].groupId
-      }).map((edge) => {
-        return {
-          ...edge,
-          targetHandle: 'target',
-        }
-      })
-    }
-
-    const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
-      [
-        ...needDeleteEdges.map((needDeleteEdge) => {
-          return {
-            type: 'remove',
-            edge: needDeleteEdge,
-          }
-        }),
-        ...shouldKeepEdges.map((shouldKeepEdge) => {
-          return {
-            type: 'add',
-            edge: shouldKeepEdge,
-          }
-        }),
-      ],
-      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) => {
-      draft = draft.filter(edge => edge.target !== nodeId)
-      draft.push(...shouldKeepEdges)
-      return draft
-    })
-    setEdges(newEdges)
-  }, [store])
-
   const handleGroupItemMouseEnter = useCallback((groupId: string) => {
     const {
       setHoveringAssignVariableGroupId,
@@ -195,7 +116,6 @@ export const useVariableAssigner = () => {
 
   return {
     handleAddVariableInAddVariablePopupWithPosition,
-    handleRemoveEdges,
     handleGroupItemMouseEnter,
     handleGroupItemMouseLeave,
     handleAssignVariableValueChange,
@@ -205,8 +125,7 @@ export const useVariableAssigner = () => {
 export const useGetAvailableVars = () => {
   const { t } = useTranslation()
   const nodes: Node[] = useNodes()
-  const edges: Edge[] = useEdges()
-  const { getBeforeNodesInSameBranch } = useWorkflow()
+  const { getBeforeNodesInSameBranchIncludeParent } = useWorkflow()
   const isChatMode = useIsChatMode()
   const getAvailableVars = useCallback((nodeId: string, handleId: string, filterVar: (v: Var) => boolean) => {
     const availableNodes: Node[] = []
@@ -214,21 +133,10 @@ export const useGetAvailableVars = () => {
 
     if (!currentNode)
       return []
-    const parentNode = nodes.find(node => node.id === currentNode.parentId)
-    const connectedEdges = edges.filter(edge => edge.target === nodeId && edge.targetHandle === handleId)
 
-    if (parentNode && !connectedEdges.length) {
-      const beforeNodes = getBeforeNodesInSameBranch(parentNode.id)
-      availableNodes.push(...beforeNodes)
-    }
-    else {
-      connectedEdges.forEach((connectedEdge) => {
-        const beforeNodes = getBeforeNodesInSameBranch(connectedEdge.source)
-        const connectedNode = nodes.find(node => node.id === connectedEdge.source)!
-
-        availableNodes.push(connectedNode, ...beforeNodes)
-      })
-    }
+    const beforeNodes = getBeforeNodesInSameBranchIncludeParent(nodeId)
+    availableNodes.push(...beforeNodes)
+    const parentNode = nodes.find(node => node.id === currentNode.parentId)
 
     return toNodeAvailableVars({
       parentNode,
@@ -237,7 +145,7 @@ export const useGetAvailableVars = () => {
       isChatMode,
       filterVar,
     })
-  }, [nodes, edges, t, isChatMode, getBeforeNodesInSameBranch])
+  }, [nodes, t, isChatMode, getBeforeNodesInSameBranchIncludeParent])
 
   return getAvailableVars
 }

+ 1 - 1
web/app/components/workflow/nodes/variable-assigner/node.tsx

@@ -43,7 +43,7 @@ const Node: FC<NodeProps<VariableAssignerNodeType>> = (props) => {
   }, [t, advanced_settings, data, id])
 
   return (
-    <div className='relative mb-1 px-1' ref={ref}>
+    <div className='relative mb-1 px-1 space-y-0.5' ref={ref}>
       {
         groups.map((item) => {
           return (

+ 10 - 10
web/app/components/workflow/nodes/variable-assigner/panel.tsx

@@ -1,16 +1,16 @@
 import type { FC } from 'react'
 import React from 'react'
 import { useTranslation } from 'react-i18next'
-// import cn from 'classnames'
-// import Field from '../_base/components/field'
+import cn from 'classnames'
+import Field from '../_base/components/field'
 import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm'
 import useConfig from './use-config'
 import type { VariableAssignerNodeType } from './types'
 import VarGroupItem from './components/var-group-item'
 import { type NodePanelProps } from '@/app/components/workflow/types'
 import Split from '@/app/components/workflow/nodes/_base/components/split'
-// import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
-// import Switch from '@/app/components/base/switch'
+import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
+import Switch from '@/app/components/base/switch'
 import AddButton from '@/app/components/workflow/nodes/_base/components/add-button'
 
 const i18nPrefix = 'workflow.nodes.variableAssigner'
@@ -25,7 +25,7 @@ const Panel: FC<NodePanelProps<VariableAssignerNodeType>> = ({
     inputs,
     handleListOrTypeChange,
     isEnableGroup,
-    // handleGroupEnabledChange,
+    handleGroupEnabledChange,
     handleAddGroup,
     handleListOrTypeChangeInGroup,
     handleGroupRemoved,
@@ -81,8 +81,8 @@ const Panel: FC<NodePanelProps<VariableAssignerNodeType>> = ({
             />
           </div>)}
       </div>
-      {/* <Split /> */}
-      {/* <div className={cn('px-4 pt-4', isEnableGroup ? 'pb-4' : 'pb-2')}>
+      <Split />
+      <div className={cn('px-4 pt-4', isEnableGroup ? 'pb-4' : 'pb-2')}>
         <Field
           title={t(`${i18nPrefix}.aggregationGroup`)}
           tooltip={t(`${i18nPrefix}.aggregationGroupTip`)!}
@@ -95,8 +95,8 @@ const Panel: FC<NodePanelProps<VariableAssignerNodeType>> = ({
             />
           }
         />
-      </div> */}
-      {/* {isEnableGroup && (
+      </div>
+      {isEnableGroup && (
         <>
           <Split />
           <div className='px-4 pt-4 pb-2'>
@@ -116,7 +116,7 @@ const Panel: FC<NodePanelProps<VariableAssignerNodeType>> = ({
             </OutputVars>
           </div>
         </>
-      )} */}
+      )}
       <RemoveEffectVarConfirm
         isShow={isShowRemoveVarConfirm}
         onCancel={hideRemoveVarConfirm}

+ 2 - 4
web/app/components/workflow/nodes/variable-assigner/use-config.ts

@@ -5,7 +5,7 @@ import { v4 as uuid4 } from 'uuid'
 import type { ValueSelector, Var } from '../../types'
 import { VarType } from '../../types'
 import type { VarGroupItem, VariableAssignerNodeType } from './types'
-import { useGetAvailableVars, useVariableAssigner } from './hooks'
+import { useGetAvailableVars } from './hooks'
 import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
 
 import {
@@ -19,7 +19,6 @@ const useConfig = (id: string, payload: VariableAssignerNodeType) => {
 
   const { inputs, setInputs } = useNodeCrud<VariableAssignerNodeType>(id, payload)
   const isEnableGroup = !!inputs.advanced_settings?.group_enabled
-  const { handleRemoveEdges } = useVariableAssigner()
 
   // Not Enable Group
   const handleListOrTypeChange = useCallback((payload: VarGroupItem) => {
@@ -114,8 +113,7 @@ const useConfig = (id: string, payload: VariableAssignerNodeType) => {
       draft.advanced_settings.group_enabled = enabled
     })
     setInputs(newInputs)
-    handleRemoveEdges(id, enabled)
-  }, [handleOutVarRenameChange, id, inputs, isVarUsedInNodes, setInputs, showRemoveVarConfirm, handleRemoveEdges])
+  }, [handleOutVarRenameChange, id, inputs, isVarUsedInNodes, setInputs, showRemoveVarConfirm])
 
   const handleAddGroup = useCallback(() => {
     let maxInGroupName = 1

+ 4 - 1
web/app/components/workflow/store.ts

@@ -120,7 +120,10 @@ type Shape = {
   setHoveringAssignVariableGroupId: (hoveringAssignVariableGroupId?: string) => void
   connectingNodePayload?: { nodeId: string; nodeType: string; handleType: string; handleId: string | null }
   setConnectingNodePayload: (startConnectingPayload?: Shape['connectingNodePayload']) => void
-  enteringNodePayload?: { nodeId: string }
+  enteringNodePayload?: {
+    nodeId: string
+    nodeData: VariableAssignerNodeType
+  }
   setEnteringNodePayload: (enteringNodePayload?: Shape['enteringNodePayload']) => void
 }
 

+ 8 - 4
web/app/components/workflow/utils.ts

@@ -257,16 +257,20 @@ export const getNodesConnectedSourceOrTargetHandleIdsMap = (changes: ConnectedSo
     }
 
     if (sourceNode) {
-      if (type === 'remove')
-        nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds = nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds.filter((handleId: string) => handleId !== edge.sourceHandle)
+      if (type === 'remove') {
+        const index = nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds.findIndex((handleId: string) => handleId === edge.sourceHandle)
+        nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds.splice(index, 1)
+      }
 
       if (type === 'add')
         nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds.push(edge.sourceHandle || 'source')
     }
 
     if (targetNode) {
-      if (type === 'remove')
-        nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds = nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds.filter((handleId: string) => handleId !== edge.targetHandle)
+      if (type === 'remove') {
+        const index = nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds.findIndex((handleId: string) => handleId === edge.targetHandle)
+        nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds.splice(index, 1)
+      }
 
       if (type === 'add')
         nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds.push(edge.targetHandle || 'target')