| import type { |
| FC, |
| ReactElement, |
| } from 'react' |
| import { |
| cloneElement, |
| memo, |
| useEffect, |
| useMemo, |
| useRef, |
| } from 'react' |
| import { |
| RiCheckboxCircleLine, |
| RiErrorWarningLine, |
| RiLoader2Line, |
| } from '@remixicon/react' |
| import { useTranslation } from 'react-i18next' |
| import type { NodeProps } from '../../types' |
| import { |
| BlockEnum, |
| NodeRunningStatus, |
| } from '../../types' |
| import { |
| useNodesReadOnly, |
| useToolIcon, |
| } from '../../hooks' |
| import { useNodeIterationInteractions } from '../iteration/use-interactions' |
| import type { IterationNodeType } from '../iteration/types' |
| import { |
| NodeSourceHandle, |
| NodeTargetHandle, |
| } from './components/node-handle' |
| import NodeResizer from './components/node-resizer' |
| import NodeControl from './components/node-control' |
| import AddVariablePopupWithPosition from './components/add-variable-popup-with-position' |
| import cn from '@/utils/classnames' |
| import BlockIcon from '@/app/components/workflow/block-icon' |
| import Tooltip from '@/app/components/base/tooltip' |
|
|
| type BaseNodeProps = { |
| children: ReactElement |
| } & NodeProps |
|
|
| const BaseNode: FC<BaseNodeProps> = ({ |
| id, |
| data, |
| children, |
| }) => { |
| const { t } = useTranslation() |
| const nodeRef = useRef<HTMLDivElement>(null) |
| const { nodesReadOnly } = useNodesReadOnly() |
| const { handleNodeIterationChildSizeChange } = useNodeIterationInteractions() |
| const toolIcon = useToolIcon(data) |
|
|
| useEffect(() => { |
| if (nodeRef.current && data.selected && data.isInIteration) { |
| const resizeObserver = new ResizeObserver(() => { |
| handleNodeIterationChildSizeChange(id) |
| }) |
|
|
| resizeObserver.observe(nodeRef.current) |
|
|
| return () => { |
| resizeObserver.disconnect() |
| } |
| } |
| }, [data.isInIteration, data.selected, id, handleNodeIterationChildSizeChange]) |
|
|
| const showSelectedBorder = data.selected || data._isBundled || data._isEntering |
| const { |
| showRunningBorder, |
| showSuccessBorder, |
| showFailedBorder, |
| } = useMemo(() => { |
| return { |
| showRunningBorder: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder, |
| showSuccessBorder: data._runningStatus === NodeRunningStatus.Succeeded && !showSelectedBorder, |
| showFailedBorder: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder, |
| } |
| }, [data._runningStatus, showSelectedBorder]) |
|
|
| return ( |
| <div |
| className={cn( |
| 'flex border-[2px] rounded-2xl', |
| showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent', |
| !showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight', |
| )} |
| ref={nodeRef} |
| style={{ |
| width: data.type === BlockEnum.Iteration ? data.width : 'auto', |
| height: data.type === BlockEnum.Iteration ? data.height : 'auto', |
| }} |
| > |
| <div |
| className={cn( |
| 'group relative pb-1 shadow-xs', |
| 'border border-transparent rounded-[15px]', |
| data.type !== BlockEnum.Iteration && 'w-[240px] bg-workflow-block-bg', |
| data.type === BlockEnum.Iteration && 'flex flex-col w-full h-full bg-[#fcfdff]/80', |
| !data._runningStatus && 'hover:shadow-lg', |
| showRunningBorder && '!border-primary-500', |
| showSuccessBorder && '!border-[#12B76A]', |
| showFailedBorder && '!border-[#F04438]', |
| data._isBundled && '!shadow-lg', |
| )} |
| > |
| { |
| data._inParallelHovering && ( |
| <div className='absolute left-2 -top-2.5 top system-2xs-medium-uppercase text-text-tertiary z-10'> |
| {t('workflow.common.parallelRun')} |
| </div> |
| ) |
| } |
| { |
| data._showAddVariablePopup && ( |
| <AddVariablePopupWithPosition |
| nodeId={id} |
| nodeData={data} |
| /> |
| ) |
| } |
| { |
| data.type === BlockEnum.Iteration && ( |
| <NodeResizer |
| nodeId={id} |
| nodeData={data} |
| /> |
| ) |
| } |
| { |
| !data._isCandidate && ( |
| <NodeTargetHandle |
| id={id} |
| data={data} |
| handleClassName='!top-4 !-left-[9px] !translate-y-0' |
| handleId='target' |
| /> |
| ) |
| } |
| { |
| data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._isCandidate && ( |
| <NodeSourceHandle |
| id={id} |
| data={data} |
| handleClassName='!top-4 !-right-[9px] !translate-y-0' |
| handleId='source' |
| /> |
| ) |
| } |
| { |
| !data._runningStatus && !nodesReadOnly && !data._isCandidate && ( |
| <NodeControl |
| id={id} |
| data={data} |
| /> |
| ) |
| } |
| <div className={cn( |
| 'flex items-center px-3 pt-3 pb-2 rounded-t-2xl', |
| data.type === BlockEnum.Iteration && 'bg-[rgba(250,252,255,0.9)]', |
| )}> |
| <BlockIcon |
| className='shrink-0 mr-2' |
| type={data.type} |
| size='md' |
| toolIcon={toolIcon} |
| /> |
| <div |
| title={data.title} |
| className='grow mr-1 system-sm-semibold-uppercase text-text-primary truncate flex items-center' |
| > |
| <div> |
| {data.title} |
| </div> |
| { |
| data.type === BlockEnum.Iteration && (data as IterationNodeType).is_parallel && ( |
| <Tooltip popupContent={ |
| <div className='w-[180px]'> |
| <div className='font-extrabold'> |
| {t('workflow.nodes.iteration.parallelModeEnableTitle')} |
| </div> |
| {t('workflow.nodes.iteration.parallelModeEnableDesc')} |
| </div>} |
| > |
| <div className='flex justify-center items-center px-[5px] py-[3px] ml-1 border-[1px] border-text-warning rounded-[5px] text-text-warning system-2xs-medium-uppercase '> |
| {t('workflow.nodes.iteration.parallelModeUpper')} |
| </div> |
| </Tooltip> |
| ) |
| } |
| </div> |
| { |
| data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running && ( |
| <div className='mr-1.5 text-xs font-medium text-primary-600'> |
| {data._iterationIndex}/{data._iterationLength} |
| </div> |
| ) |
| } |
| { |
| (data._runningStatus === NodeRunningStatus.Running || data._singleRunningStatus === NodeRunningStatus.Running) && ( |
| <RiLoader2Line className='w-3.5 h-3.5 text-primary-600 animate-spin' /> |
| ) |
| } |
| { |
| data._runningStatus === NodeRunningStatus.Succeeded && ( |
| <RiCheckboxCircleLine className='w-3.5 h-3.5 text-[#12B76A]' /> |
| ) |
| } |
| { |
| data._runningStatus === NodeRunningStatus.Failed && ( |
| <RiErrorWarningLine className='w-3.5 h-3.5 text-[#F04438]' /> |
| ) |
| } |
| </div> |
| { |
| data.type !== BlockEnum.Iteration && ( |
| cloneElement(children, { id, data }) |
| ) |
| } |
| { |
| data.type === BlockEnum.Iteration && ( |
| <div className='grow pl-1 pr-1 pb-1'> |
| {cloneElement(children, { id, data })} |
| </div> |
| ) |
| } |
| { |
| data.desc && data.type !== BlockEnum.Iteration && ( |
| <div className='px-3 pt-1 pb-2 system-xs-regular text-text-tertiary whitespace-pre-line break-words'> |
| {data.desc} |
| </div> |
| ) |
| } |
| </div> |
| </div> |
| ) |
| } |
|
|
| export default memo(BaseNode) |
|
|