Co-authored-by: Yeuoly <admin@srmxy.cn>tags/0.6.10
| from core.workflow.entities.base_node_data_entities import BaseNodeData | from core.workflow.entities.base_node_data_entities import BaseNodeData | ||||
| class AdvancedSetting(BaseModel): | |||||
| class AdvancedSettings(BaseModel): | |||||
| """ | """ | ||||
| Advanced setting. | Advanced setting. | ||||
| """ | """ | ||||
| type: str = 'variable-assigner' | type: str = 'variable-assigner' | ||||
| output_type: str | output_type: str | ||||
| variables: list[list[str]] | variables: list[list[str]] | ||||
| advanced_setting: Optional[AdvancedSetting] | |||||
| advanced_settings: Optional[AdvancedSettings] |
| outputs = {} | outputs = {} | ||||
| inputs = {} | 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: | for variable in node_data.variables: | ||||
| value = variable_pool.get_variable_value(variable) | value = variable_pool.get_variable_value(variable) | ||||
| } | } | ||||
| break | break | ||||
| else: | else: | ||||
| for group in node_data.advanced_setting.groups: | |||||
| for group in node_data.advanced_settings.groups: | |||||
| for variable in group.variables: | for variable in group.variables: | ||||
| value = variable_pool.get_variable_value(variable) | value = variable_pool.get_variable_value(variable) | ||||
| if value is not None: | if value is not None: | ||||
| outputs[f'{group.group_name}_output'] = value | |||||
| outputs[group.group_name] = { | |||||
| 'output': value | |||||
| } | |||||
| inputs['.'.join(variable[1:])] = value | inputs['.'.join(variable[1:])] = value | ||||
| break | break | ||||
| if (sameLevel) { | if (sameLevel) { | ||||
| setEnteringNodePayload({ | setEnteringNodePayload({ | ||||
| nodeId: node.id, | nodeId: node.id, | ||||
| nodeData: node.data as VariableAssignerNodeType, | |||||
| }) | }) | ||||
| const fromType = connectingNodePayload.handleType | const fromType = connectingNodePayload.handleType | ||||
| const { getNodes } = store.getState() | const { getNodes } = store.getState() | ||||
| const node = getNodes().find(n => n.id === nodeId)! | 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) { | if (!node.data.isIterationStart) { | ||||
| setConnectingNodePayload({ | setConnectingNodePayload({ | ||||
| nodeId, | nodeId, | ||||
| const fromHandleType = connectingNodePayload.handleType | const fromHandleType = connectingNodePayload.handleType | ||||
| const fromHandleId = connectingNodePayload.handleId | const fromHandleId = connectingNodePayload.handleId | ||||
| const fromNode = nodes.find(n => n.id === connectingNodePayload.nodeId)! | 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 toNode = nodes.find(n => n.id === enteringNodePayload.nodeId)! | ||||
| const toParentNode = nodes.find(n => n.id === toNode.parentId) | const toParentNode = nodes.find(n => n.id === toNode.parentId) | ||||
| if (fromHandleType === 'source' && (toNode.data.type === BlockEnum.VariableAssigner || toNode.data.type === BlockEnum.VariableAggregator)) { | if (fromHandleType === 'source' && (toNode.data.type === BlockEnum.VariableAssigner || toNode.data.type === BlockEnum.VariableAggregator)) { | ||||
| const groupEnabled = toNode.data.advanced_settings?.group_enabled | 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) => { | const newNodes = produce(nodes, (draft) => { | ||||
| draft.forEach((node) => { | draft.forEach((node) => { | ||||
| if (node.id === toNode.id) { | if (node.id === toNode.id) { | ||||
| }) | }) | ||||
| setNodes(newNodes) | setNodes(newNodes) | ||||
| setShowAssignVariablePopup({ | 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, | x: x - toNode.positionAbsolute!.x, | ||||
| y: y - toNode.positionAbsolute!.y, | y: y - toNode.positionAbsolute!.y, | ||||
| }) | }) | ||||
| handleNodeConnect({ | handleNodeConnect({ | ||||
| source: toNode.id, | |||||
| sourceHandle: 'source', | |||||
| target: fromNode.id, | |||||
| targetHandle: fromHandleId, | |||||
| source: fromNode.id, | |||||
| sourceHandle: fromHandleId, | |||||
| target: toNode.id, | |||||
| targetHandle: 'target', | |||||
| }) | }) | ||||
| } | } | ||||
| } | } | ||||
| setNodes([...nodes, ...nodesToPaste]) | setNodes([...nodes, ...nodesToPaste]) | ||||
| handleSyncWorkflowDraft() | handleSyncWorkflowDraft() | ||||
| } | } | ||||
| }, [t, getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, reactflow, handleNodeIterationChildrenCopy]) | |||||
| }, [getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, reactflow, handleNodeIterationChildrenCopy]) | |||||
| const handleNodesDuplicate = useCallback(() => { | const handleNodesDuplicate = useCallback(() => { | ||||
| if (getNodesReadOnly()) | if (getNodesReadOnly()) |
| }) | }) | ||||
| useKeyPress('delete', handleNodesDelete) | useKeyPress('delete', handleNodesDelete) | ||||
| useKeyPress('delete', handleEdgeDelete) | |||||
| useKeyPress(['delete', 'backspace'], handleEdgeDelete) | |||||
| useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.c`, handleNodesCopy, { exactMatch: true, useCapture: true }) | useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.c`, handleNodesCopy, { exactMatch: true, useCapture: true }) | ||||
| useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.v`, handleNodesPaste, { exactMatch: true, useCapture: true }) | useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.v`, handleNodesPaste, { exactMatch: true, useCapture: true }) | ||||
| useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.d`, handleNodesDuplicate, { exactMatch: true, useCapture: true }) | useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.d`, handleNodesDuplicate, { exactMatch: true, useCapture: true }) |
| if (!showAssignVariablePopup) | if (!showAssignVariablePopup) | ||||
| return '' | return '' | ||||
| if (showAssignVariablePopup.variableAssignerNodeHandleId === 'target') | |||||
| const groupEnabled = showAssignVariablePopup.variableAssignerNodeData.advanced_settings?.group_enabled | |||||
| if (!groupEnabled) | |||||
| return showAssignVariablePopup.variableAssignerNodeData.output_type | return showAssignVariablePopup.variableAssignerNodeData.output_type | ||||
| const group = showAssignVariablePopup.variableAssignerNodeData.advanced_settings?.groups.find(group => group.groupId === showAssignVariablePopup.variableAssignerNodeHandleId) | const group = showAssignVariablePopup.variableAssignerNodeData.advanced_settings?.groups.find(group => group.groupId === showAssignVariablePopup.variableAssignerNodeHandleId) |
| nodeSelectorClassName, | nodeSelectorClassName, | ||||
| }: NodeHandleProps) => { | }: NodeHandleProps) => { | ||||
| const notInitialWorkflow = useStore(s => s.notInitialWorkflow) | const notInitialWorkflow = useStore(s => s.notInitialWorkflow) | ||||
| const connectingNodePayload = useStore(s => s.connectingNodePayload) | |||||
| const [open, setOpen] = useState(false) | const [open, setOpen] = useState(false) | ||||
| const { handleNodeAdd } = useNodesInteractions() | const { handleNodeAdd } = useNodesInteractions() | ||||
| const { getNodesReadOnly } = useNodesReadOnly() | const { getNodesReadOnly } = useNodesReadOnly() | ||||
| const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration) | 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 connected = data._connectedSourceHandleIds?.includes(handleId) | ||||
| const handleOpenChange = useCallback((v: boolean) => { | const handleOpenChange = useCallback((v: boolean) => { |
| ) | ) | ||||
| } | } | ||||
| { | { | ||||
| data.type !== BlockEnum.VariableAssigner && data.type !== BlockEnum.VariableAggregator && !data._isCandidate && ( | |||||
| !data._isCandidate && ( | |||||
| <NodeTargetHandle | <NodeTargetHandle | ||||
| id={id} | id={id} | ||||
| data={data} | data={data} |
| import { | import { | ||||
| memo, | memo, | ||||
| useCallback, | useCallback, | ||||
| useState, | |||||
| } from 'react' | } from 'react' | ||||
| import cn from 'classnames' | import cn from 'classnames' | ||||
| import { useVariableAssigner } from '../../hooks' | import { useVariableAssigner } from '../../hooks' | ||||
| } from '@/app/components/workflow/types' | } from '@/app/components/workflow/types' | ||||
| export type AddVariableProps = { | export type AddVariableProps = { | ||||
| open: boolean | |||||
| onOpenChange: (open: boolean) => void | |||||
| variableAssignerNodeId: string | variableAssignerNodeId: string | ||||
| variableAssignerNodeData: VariableAssignerNodeType | variableAssignerNodeData: VariableAssignerNodeType | ||||
| availableVars: NodeOutPutVar[] | availableVars: NodeOutPutVar[] | ||||
| handleId?: string | handleId?: string | ||||
| } | } | ||||
| const AddVariable = ({ | const AddVariable = ({ | ||||
| open, | |||||
| onOpenChange, | |||||
| availableVars, | availableVars, | ||||
| variableAssignerNodeId, | variableAssignerNodeId, | ||||
| variableAssignerNodeData, | variableAssignerNodeData, | ||||
| handleId, | handleId, | ||||
| }: AddVariableProps) => { | }: AddVariableProps) => { | ||||
| const [open, setOpen] = useState(false) | |||||
| const { handleAssignVariableValueChange } = useVariableAssigner() | const { handleAssignVariableValueChange } = useVariableAssigner() | ||||
| const handleSelectVariable = useCallback((v: ValueSelector, varDetail: Var) => { | const handleSelectVariable = useCallback((v: ValueSelector, varDetail: Var) => { | ||||
| varDetail, | varDetail, | ||||
| handleId, | handleId, | ||||
| ) | ) | ||||
| onOpenChange(false) | |||||
| }, [handleAssignVariableValueChange, variableAssignerNodeId, handleId, onOpenChange]) | |||||
| setOpen(false) | |||||
| }, [handleAssignVariableValueChange, variableAssignerNodeId, handleId, setOpen]) | |||||
| return ( | return ( | ||||
| <div className={cn( | <div className={cn( | ||||
| 'hidden group-hover:flex absolute top-0 left-0 z-10 pointer-events-none', | |||||
| open && '!flex', | open && '!flex', | ||||
| variableAssignerNodeData.selected && '!flex', | variableAssignerNodeData.selected && '!flex', | ||||
| )}> | )}> | ||||
| <PortalToFollowElem | <PortalToFollowElem | ||||
| placement={'left-start'} | |||||
| offset={{ | |||||
| mainAxis: 4, | |||||
| crossAxis: -60, | |||||
| }} | |||||
| placement={'right'} | |||||
| offset={4} | |||||
| open={open} | open={open} | ||||
| onOpenChange={onOpenChange} | |||||
| onOpenChange={setOpen} | |||||
| > | > | ||||
| <PortalToFollowElemTrigger | <PortalToFollowElemTrigger | ||||
| onClick={() => onOpenChange(!open)} | |||||
| onClick={() => setOpen(!open)} | |||||
| > | > | ||||
| <div | <div | ||||
| className={cn( | 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> | </div> | ||||
| </PortalToFollowElemTrigger> | </PortalToFollowElemTrigger> | ||||
| <PortalToFollowElemContent className='z-[1000]'> | <PortalToFollowElemContent className='z-[1000]'> |
| useVariableAssigner, | useVariableAssigner, | ||||
| } from '../hooks' | } from '../hooks' | ||||
| import { filterVar } from '../utils' | import { filterVar } from '../utils' | ||||
| import NodeHandle from './node-handle' | |||||
| import AddVariable from './add-variable' | |||||
| import NodeVariableItem from './node-variable-item' | import NodeVariableItem from './node-variable-item' | ||||
| import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' | import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' | ||||
| handleGroupItemMouseLeave, | handleGroupItemMouseLeave, | ||||
| } = useVariableAssigner() | } = useVariableAssigner() | ||||
| const getAvailableVars = useGetAvailableVars() | const getAvailableVars = useGetAvailableVars() | ||||
| const groupEnabled = item.groupEnabled | |||||
| const outputType = useMemo(() => { | const outputType = useMemo(() => { | ||||
| if (item.targetHandleId === 'target') | |||||
| if (!groupEnabled) | |||||
| return item.variableAssignerNodeData.output_type | return item.variableAssignerNodeData.output_type | ||||
| const group = item.variableAssignerNodeData.advanced_settings?.groups.find(group => group.groupId === item.targetHandleId) | const group = item.variableAssignerNodeData.advanced_settings?.groups.find(group => group.groupId === item.targetHandleId) | ||||
| return group?.output_type || '' | 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 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 ( | return ( | ||||
| <div | <div | ||||
| className={cn( | 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} | onMouseLeave={handleGroupItemMouseLeave} | ||||
| > | > | ||||
| <div className='flex items-center justify-between h-4 text-[10px] font-medium text-gray-500'> | <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> | </div> | ||||
| { | { | ||||
| !item.variables.length && ( | !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`)} | {t(`${i18nPrefix}.varNotSet`)} | ||||
| </div> | </div> | ||||
| ) | ) | ||||
| key={index} | key={index} | ||||
| node={node as Node} | node={node as Node} | ||||
| varName={varName} | varName={varName} | ||||
| showBorder={showSelectedBorder || showSelectionBorder} | |||||
| /> | /> | ||||
| ) | ) | ||||
| }) | }) |
| 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) |
| import { memo } from 'react' | import { memo } from 'react' | ||||
| import cn from 'classnames' | |||||
| import { VarBlockIcon } from '@/app/components/workflow/block-icon' | import { VarBlockIcon } from '@/app/components/workflow/block-icon' | ||||
| import { Line3 } from '@/app/components/base/icons/src/public/common' | import { Line3 } from '@/app/components/base/icons/src/public/common' | ||||
| import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' | import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' | ||||
| type NodeVariableItemProps = { | type NodeVariableItemProps = { | ||||
| node: Node | node: Node | ||||
| varName: string | varName: string | ||||
| showBorder?: boolean | |||||
| } | } | ||||
| const NodeVariableItem = ({ | const NodeVariableItem = ({ | ||||
| node, | node, | ||||
| varName, | varName, | ||||
| showBorder, | |||||
| }: NodeVariableItemProps) => { | }: NodeVariableItemProps) => { | ||||
| return ( | 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='flex items-center'> | ||||
| <div className='p-[1px]'> | <div className='p-[1px]'> | ||||
| <VarBlockIcon | <VarBlockIcon |
| import { useCallback } from 'react' | import { useCallback } from 'react' | ||||
| import { | import { | ||||
| useEdges, | |||||
| useNodes, | useNodes, | ||||
| useStoreApi, | useStoreApi, | ||||
| } from 'reactflow' | } from 'reactflow' | ||||
| useNodeDataUpdate, | useNodeDataUpdate, | ||||
| useWorkflow, | useWorkflow, | ||||
| } from '../../hooks' | } from '../../hooks' | ||||
| import { getNodesConnectedSourceOrTargetHandleIdsMap } from '../../utils' | |||||
| import type { | import type { | ||||
| Edge, | |||||
| Node, | Node, | ||||
| ValueSelector, | ValueSelector, | ||||
| Var, | Var, | ||||
| handleAssignVariableValueChange(variableAssignerNodeId, value, varDetail, variableAssignerNodeHandleId) | handleAssignVariableValueChange(variableAssignerNodeId, value, varDetail, variableAssignerNodeHandleId) | ||||
| }, [store, workflowStore, handleAssignVariableValueChange]) | }, [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 handleGroupItemMouseEnter = useCallback((groupId: string) => { | ||||
| const { | const { | ||||
| setHoveringAssignVariableGroupId, | setHoveringAssignVariableGroupId, | ||||
| return { | return { | ||||
| handleAddVariableInAddVariablePopupWithPosition, | handleAddVariableInAddVariablePopupWithPosition, | ||||
| handleRemoveEdges, | |||||
| handleGroupItemMouseEnter, | handleGroupItemMouseEnter, | ||||
| handleGroupItemMouseLeave, | handleGroupItemMouseLeave, | ||||
| handleAssignVariableValueChange, | handleAssignVariableValueChange, | ||||
| export const useGetAvailableVars = () => { | export const useGetAvailableVars = () => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const nodes: Node[] = useNodes() | const nodes: Node[] = useNodes() | ||||
| const edges: Edge[] = useEdges() | |||||
| const { getBeforeNodesInSameBranch } = useWorkflow() | |||||
| const { getBeforeNodesInSameBranchIncludeParent } = useWorkflow() | |||||
| const isChatMode = useIsChatMode() | const isChatMode = useIsChatMode() | ||||
| const getAvailableVars = useCallback((nodeId: string, handleId: string, filterVar: (v: Var) => boolean) => { | const getAvailableVars = useCallback((nodeId: string, handleId: string, filterVar: (v: Var) => boolean) => { | ||||
| const availableNodes: Node[] = [] | const availableNodes: Node[] = [] | ||||
| if (!currentNode) | if (!currentNode) | ||||
| return [] | 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({ | return toNodeAvailableVars({ | ||||
| parentNode, | parentNode, | ||||
| isChatMode, | isChatMode, | ||||
| filterVar, | filterVar, | ||||
| }) | }) | ||||
| }, [nodes, edges, t, isChatMode, getBeforeNodesInSameBranch]) | |||||
| }, [nodes, t, isChatMode, getBeforeNodesInSameBranchIncludeParent]) | |||||
| return getAvailableVars | return getAvailableVars | ||||
| } | } |
| }, [t, advanced_settings, data, id]) | }, [t, advanced_settings, data, id]) | ||||
| return ( | 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) => { | groups.map((item) => { | ||||
| return ( | return ( |
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import React from 'react' | import React from 'react' | ||||
| import { useTranslation } from 'react-i18next' | 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 RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm' | ||||
| import useConfig from './use-config' | import useConfig from './use-config' | ||||
| import type { VariableAssignerNodeType } from './types' | import type { VariableAssignerNodeType } from './types' | ||||
| import VarGroupItem from './components/var-group-item' | import VarGroupItem from './components/var-group-item' | ||||
| import { type NodePanelProps } from '@/app/components/workflow/types' | import { type NodePanelProps } from '@/app/components/workflow/types' | ||||
| import Split from '@/app/components/workflow/nodes/_base/components/split' | 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' | import AddButton from '@/app/components/workflow/nodes/_base/components/add-button' | ||||
| const i18nPrefix = 'workflow.nodes.variableAssigner' | const i18nPrefix = 'workflow.nodes.variableAssigner' | ||||
| inputs, | inputs, | ||||
| handleListOrTypeChange, | handleListOrTypeChange, | ||||
| isEnableGroup, | isEnableGroup, | ||||
| // handleGroupEnabledChange, | |||||
| handleGroupEnabledChange, | |||||
| handleAddGroup, | handleAddGroup, | ||||
| handleListOrTypeChangeInGroup, | handleListOrTypeChangeInGroup, | ||||
| handleGroupRemoved, | handleGroupRemoved, | ||||
| /> | /> | ||||
| </div>)} | </div>)} | ||||
| </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 | <Field | ||||
| title={t(`${i18nPrefix}.aggregationGroup`)} | title={t(`${i18nPrefix}.aggregationGroup`)} | ||||
| tooltip={t(`${i18nPrefix}.aggregationGroupTip`)!} | tooltip={t(`${i18nPrefix}.aggregationGroupTip`)!} | ||||
| /> | /> | ||||
| } | } | ||||
| /> | /> | ||||
| </div> */} | |||||
| {/* {isEnableGroup && ( | |||||
| </div> | |||||
| {isEnableGroup && ( | |||||
| <> | <> | ||||
| <Split /> | <Split /> | ||||
| <div className='px-4 pt-4 pb-2'> | <div className='px-4 pt-4 pb-2'> | ||||
| </OutputVars> | </OutputVars> | ||||
| </div> | </div> | ||||
| </> | </> | ||||
| )} */} | |||||
| )} | |||||
| <RemoveEffectVarConfirm | <RemoveEffectVarConfirm | ||||
| isShow={isShowRemoveVarConfirm} | isShow={isShowRemoveVarConfirm} | ||||
| onCancel={hideRemoveVarConfirm} | onCancel={hideRemoveVarConfirm} |
| import type { ValueSelector, Var } from '../../types' | import type { ValueSelector, Var } from '../../types' | ||||
| import { VarType } from '../../types' | import { VarType } from '../../types' | ||||
| import type { VarGroupItem, VariableAssignerNodeType } 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 useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' | ||||
| import { | import { | ||||
| const { inputs, setInputs } = useNodeCrud<VariableAssignerNodeType>(id, payload) | const { inputs, setInputs } = useNodeCrud<VariableAssignerNodeType>(id, payload) | ||||
| const isEnableGroup = !!inputs.advanced_settings?.group_enabled | const isEnableGroup = !!inputs.advanced_settings?.group_enabled | ||||
| const { handleRemoveEdges } = useVariableAssigner() | |||||
| // Not Enable Group | // Not Enable Group | ||||
| const handleListOrTypeChange = useCallback((payload: VarGroupItem) => { | const handleListOrTypeChange = useCallback((payload: VarGroupItem) => { | ||||
| draft.advanced_settings.group_enabled = enabled | draft.advanced_settings.group_enabled = enabled | ||||
| }) | }) | ||||
| setInputs(newInputs) | setInputs(newInputs) | ||||
| handleRemoveEdges(id, enabled) | |||||
| }, [handleOutVarRenameChange, id, inputs, isVarUsedInNodes, setInputs, showRemoveVarConfirm, handleRemoveEdges]) | |||||
| }, [handleOutVarRenameChange, id, inputs, isVarUsedInNodes, setInputs, showRemoveVarConfirm]) | |||||
| const handleAddGroup = useCallback(() => { | const handleAddGroup = useCallback(() => { | ||||
| let maxInGroupName = 1 | let maxInGroupName = 1 |
| setHoveringAssignVariableGroupId: (hoveringAssignVariableGroupId?: string) => void | setHoveringAssignVariableGroupId: (hoveringAssignVariableGroupId?: string) => void | ||||
| connectingNodePayload?: { nodeId: string; nodeType: string; handleType: string; handleId: string | null } | connectingNodePayload?: { nodeId: string; nodeType: string; handleType: string; handleId: string | null } | ||||
| setConnectingNodePayload: (startConnectingPayload?: Shape['connectingNodePayload']) => void | setConnectingNodePayload: (startConnectingPayload?: Shape['connectingNodePayload']) => void | ||||
| enteringNodePayload?: { nodeId: string } | |||||
| enteringNodePayload?: { | |||||
| nodeId: string | |||||
| nodeData: VariableAssignerNodeType | |||||
| } | |||||
| setEnteringNodePayload: (enteringNodePayload?: Shape['enteringNodePayload']) => void | setEnteringNodePayload: (enteringNodePayload?: Shape['enteringNodePayload']) => void | ||||
| } | } | ||||
| } | } | ||||
| if (sourceNode) { | 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') | if (type === 'add') | ||||
| nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds.push(edge.sourceHandle || 'source') | nodesConnectedSourceOrTargetHandleIdsMap[sourceNode.id]._connectedSourceHandleIds.push(edge.sourceHandle || 'source') | ||||
| } | } | ||||
| if (targetNode) { | 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') | if (type === 'add') | ||||
| nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds.push(edge.targetHandle || 'target') | nodesConnectedSourceOrTargetHandleIdsMap[targetNode.id]._connectedTargetHandleIds.push(edge.targetHandle || 'target') |