Skip to content

Commit

Permalink
feat: workflow variable aggregator support group (#4811)
Browse files Browse the repository at this point in the history
Co-authored-by: Yeuoly <[email protected]>
  • Loading branch information
zxhlyh and Yeuoly authored May 30, 2024
1 parent 18ab63b commit 4b91383
Show file tree
Hide file tree
Showing 17 changed files with 148 additions and 277 deletions.
4 changes: 2 additions & 2 deletions api/core/workflow/nodes/variable_aggregator/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from core.workflow.entities.base_node_data_entities import BaseNodeData


class AdvancedSetting(BaseModel):
class AdvancedSettings(BaseModel):
"""
Advanced setting.
"""
Expand All @@ -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]
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def _run(self, variable_pool: VariablePool) -> NodeRunResult:
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)

Expand All @@ -32,12 +32,14 @@ def _run(self, variable_pool: VariablePool) -> NodeRunResult:
}
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

Expand Down
69 changes: 25 additions & 44 deletions web/app/components/workflow/hooks/use-nodes-interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export const useNodesInteractions = () => {
if (sameLevel) {
setEnteringNodePayload({
nodeId: node.id,
nodeData: node.data as VariableAssignerNodeType,
})
const fromType = connectingNodePayload.handleType

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)

Expand All @@ -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) {
Expand All @@ -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',
})
}
}
Expand Down Expand Up @@ -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())
Expand Down
2 changes: 1 addition & 1 deletion web/app/components/workflow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion web/app/components/workflow/nodes/_base/node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
memo,
useCallback,
useState,
} from 'react'
import cn from 'classnames'
import { useVariableAssigner } from '../../hooks'
Expand All @@ -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) => {
Expand All @@ -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]'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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>
)
Expand All @@ -96,6 +131,7 @@ const NodeGroupItem = ({
key={index}
node={node as Node}
varName={varName}
showBorder={showSelectedBorder || showSelectionBorder}
/>
)
})
Expand Down
Loading

0 comments on commit 4b91383

Please sign in to comment.