| | |
| | | RiArrowDownSLine, |
| | | RiCloseLine, |
| | | RiErrorWarningFill, |
| | | RiMoreLine, |
| | | } from '@remixicon/react' |
| | | import produce from 'immer' |
| | | import { useStoreApi } from 'reactflow' |
| | | import RemoveButton from '../remove-button' |
| | | import useAvailableVarList from '../../hooks/use-available-var-list' |
| | | import VarReferencePopup from './var-reference-popup' |
| | | import { getNodeInfoById, isConversationVar, isENV, isSystemVar, varTypeToStructType } from './utils' |
| | | import { getNodeInfoById, isConversationVar, isENV, isSystemVar } from './utils' |
| | | import ConstantField from './constant-field' |
| | | import cn from '@/utils/classnames' |
| | | import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' |
| | |
| | | import Badge from '@/app/components/base/badge' |
| | | import Tooltip from '@/app/components/base/tooltip' |
| | | import { isExceptionVariable } from '@/app/components/workflow/utils' |
| | | import VarFullPathPanel from './var-full-path-panel' |
| | | import { noop } from 'lodash-es' |
| | | |
| | | const TRIGGER_DEFAULT_WIDTH = 227 |
| | | |
| | |
| | | placeholder?: string |
| | | minWidth?: number |
| | | popupFor?: 'assigned' | 'toAssigned' |
| | | zIndex?: number |
| | | } |
| | | |
| | | const DEFAULT_VALUE_SELECTOR: Props['value'] = [] |
| | | |
| | | const VarReferencePicker: FC<Props> = ({ |
| | | nodeId, |
| | | readonly, |
| | | className, |
| | | isShowNodeName = true, |
| | | value = DEFAULT_VALUE_SELECTOR, |
| | | onOpen = noop, |
| | | value = [], |
| | | onOpen = () => { }, |
| | | onChange, |
| | | isSupportConstantValue, |
| | | defaultVarKindType = VarKindType.constant, |
| | |
| | | placeholder, |
| | | minWidth, |
| | | popupFor, |
| | | zIndex, |
| | | }) => { |
| | | const { t } = useTranslation() |
| | | const store = useStoreApi() |
| | |
| | | const isChatMode = useIsChatMode() |
| | | |
| | | const { getCurrentVariableType } = useWorkflowVariables() |
| | | const { availableVars, availableNodesWithParent: availableNodes } = useAvailableVarList(nodeId, { |
| | | const { availableNodes, availableVars } = useAvailableVarList(nodeId, { |
| | | onlyLeafNodeVar, |
| | | passedInAvailableNodes, |
| | | filterVar, |
| | |
| | | const node = getNodes().find(n => n.id === nodeId) |
| | | const isInIteration = !!node?.data.isInIteration |
| | | const iterationNode = isInIteration ? getNodes().find(n => n.id === node.parentId) : null |
| | | |
| | | const isInLoop = !!node?.data.isInLoop |
| | | const loopNode = isInLoop ? getNodes().find(n => n.id === node.parentId) : null |
| | | |
| | | const triggerRef = useRef<HTMLDivElement>(null) |
| | | const [triggerWidth, setTriggerWidth] = useState(TRIGGER_DEFAULT_WIDTH) |
| | |
| | | return false |
| | | }, [isInIteration, value, node]) |
| | | |
| | | const isLoopVar = useMemo(() => { |
| | | if (!isInLoop) |
| | | return false |
| | | if (value[0] === node?.parentId && ['item', 'index'].includes(value[1])) |
| | | return true |
| | | return false |
| | | }, [isInLoop, value, node]) |
| | | |
| | | const outputVarNodeId = hasValue ? value[0] : '' |
| | | const outputVarNode = useMemo(() => { |
| | | if (!hasValue || isConstant) |
| | |
| | | if (isIterationVar) |
| | | return iterationNode?.data |
| | | |
| | | if (isLoopVar) |
| | | return loopNode?.data |
| | | |
| | | if (isSystemVar(value as ValueSelector)) |
| | | return startNode?.data |
| | | |
| | | return getNodeInfoById(availableNodes, outputVarNodeId)?.data |
| | | }, [value, hasValue, isConstant, isIterationVar, iterationNode, availableNodes, outputVarNodeId, startNode, isLoopVar, loopNode]) |
| | | |
| | | const isShowAPart = (value as ValueSelector).length > 2 |
| | | }, [value, hasValue, isConstant, isIterationVar, iterationNode, availableNodes, outputVarNodeId, startNode]) |
| | | |
| | | const varName = useMemo(() => { |
| | | if (!hasValue) |
| | | return '' |
| | | if (hasValue) { |
| | | const isSystem = isSystemVar(value as ValueSelector) |
| | | let varName = '' |
| | | if (Array.isArray(value)) |
| | | varName = value.length >= 3 ? (value as ValueSelector).slice(-2).join('.') : value[value.length - 1] |
| | | |
| | | const isSystem = isSystemVar(value as ValueSelector) |
| | | const varName = Array.isArray(value) ? value[(value as ValueSelector).length - 1] : '' |
| | | return `${isSystem ? 'sys.' : ''}${varName}` |
| | | return `${isSystem ? 'sys.' : ''}${varName}` |
| | | } |
| | | return '' |
| | | }, [hasValue, value]) |
| | | |
| | | const varKindTypes = [ |
| | |
| | | }, [onChange, varKindType]) |
| | | |
| | | const type = getCurrentVariableType({ |
| | | parentNode: isInIteration ? iterationNode : loopNode, |
| | | parentNode: iterationNode, |
| | | valueSelector: value as ValueSelector, |
| | | availableNodes, |
| | | isChatMode, |
| | |
| | | |
| | | const WrapElem = isSupportConstantValue ? 'div' : PortalToFollowElemTrigger |
| | | const VarPickerWrap = !isSupportConstantValue ? 'div' : PortalToFollowElemTrigger |
| | | |
| | | const tooltipPopup = useMemo(() => { |
| | | if (isValidVar && isShowAPart) { |
| | | return ( |
| | | <VarFullPathPanel |
| | | nodeName={outputVarNode?.title} |
| | | path={(value as ValueSelector).slice(1)} |
| | | varType={varTypeToStructType(type)} |
| | | nodeType={outputVarNode?.type} |
| | | />) |
| | | } |
| | | if (!isValidVar && hasValue) |
| | | return t('workflow.errorMsg.invalidVariable') |
| | | |
| | | return null |
| | | }, [isValidVar, isShowAPart, hasValue, t, outputVarNode?.title, outputVarNode?.type, value, type]) |
| | | return ( |
| | | <div className={cn(className, !readonly && 'cursor-pointer')}> |
| | | <PortalToFollowElem |
| | |
| | | if (readonly) |
| | | return |
| | | !isConstant ? setOpen(!open) : setControlFocus(Date.now()) |
| | | }} className='group/picker-trigger-wrap relative !flex'> |
| | | }} className='!flex group/picker-trigger-wrap relative'> |
| | | <> |
| | | {isAddBtnTrigger |
| | | ? ( |
| | | <div> |
| | | <AddButton onClick={noop}></AddButton> |
| | | <AddButton onClick={() => { }}></AddButton> |
| | | </div> |
| | | ) |
| | | : (<div ref={!isSupportConstantValue ? triggerRef : null} className={cn((open || isFocus) ? 'border-gray-300' : 'border-gray-100', 'group/wrap relative flex h-8 w-full items-center', !isSupportConstantValue && 'rounded-lg bg-components-input-bg-normal p-1', isInTable && 'border-none bg-transparent', readonly && 'bg-components-input-bg-disabled')}> |
| | | : (<div ref={!isSupportConstantValue ? triggerRef : null} className={cn((open || isFocus) ? 'border-gray-300' : 'border-gray-100', 'relative group/wrap flex items-center w-full h-8', !isSupportConstantValue && 'p-1 rounded-lg bg-components-input-bg-normal', isInTable && 'bg-transparent border-none', readonly && 'bg-components-input-bg-disabled')}> |
| | | {isSupportConstantValue |
| | | ? <div onClick={(e) => { |
| | | e.stopPropagation() |
| | | setOpen(false) |
| | | setControlFocus(Date.now()) |
| | | }} className='mr-1 flex h-full items-center space-x-1'> |
| | | }} className='h-full mr-1 flex items-center space-x-1'> |
| | | <TypeSelector |
| | | noLeft |
| | | trigger={ |
| | | <div className='radius-md flex h-8 items-center bg-components-input-bg-normal px-2'> |
| | | <div className='system-sm-regular mr-1 text-components-input-text-filled'>{varKindTypes.find(item => item.value === varKindType)?.label}</div> |
| | | <RiArrowDownSLine className='h-4 w-4 text-text-quaternary' /> |
| | | <div className='flex items-center h-8 px-2 radius-md bg-components-input-bg-normal'> |
| | | <div className='mr-1 system-sm-regular text-components-input-text-filled'>{varKindTypes.find(item => item.value === varKindType)?.label}</div> |
| | | <RiArrowDownSLine className='w-4 h-4 text-text-quaternary' /> |
| | | </div> |
| | | } |
| | | popupClassName='top-8' |
| | |
| | | /> |
| | | </div> |
| | | : (!hasValue && <div className='ml-1.5 mr-1'> |
| | | <Variable02 className={`h-4 w-4 ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'}`} /> |
| | | <Variable02 className={`w-4 h-4 ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'}`} /> |
| | | </div>)} |
| | | {isConstant |
| | | ? ( |
| | |
| | | return |
| | | !isConstant ? setOpen(!open) : setControlFocus(Date.now()) |
| | | }} |
| | | className='h-full grow' |
| | | className='grow h-full' |
| | | > |
| | | <div ref={isSupportConstantValue ? triggerRef : null} className={cn('h-full', isSupportConstantValue && 'flex items-center rounded-lg bg-components-panel-bg py-1 pl-1')}> |
| | | <Tooltip noDecoration={isShowAPart} popupContent={tooltipPopup}> |
| | | <div className={cn('h-full items-center rounded-[5px] px-1.5', hasValue ? 'inline-flex bg-components-badge-white-to-dark' : 'flex')}> |
| | | <div ref={isSupportConstantValue ? triggerRef : null} className={cn('h-full', isSupportConstantValue && 'flex items-center pl-1 py-1 rounded-lg bg-gray-100')}> |
| | | <Tooltip popupContent={!isValidVar && hasValue && t('workflow.errorMsg.invalidVariable')}> |
| | | <div className={cn('h-full items-center px-1.5 rounded-[5px]', hasValue ? 'bg-white inline-flex' : 'flex')}> |
| | | {hasValue |
| | | ? ( |
| | | <> |
| | | {isShowNodeName && !isEnv && !isChatVar && ( |
| | | <div className='flex items-center'> |
| | | <div className='h-3 px-[1px]'> |
| | | <div className='px-[1px] h-3'> |
| | | {outputVarNode?.type && <VarBlockIcon |
| | | className='!text-text-primary' |
| | | className='!text-gray-900' |
| | | type={outputVarNode.type} |
| | | />} |
| | | </div> |
| | | <div className='mx-0.5 truncate text-xs font-medium text-text-secondary' title={outputVarNode?.title} style={{ |
| | | <div className='mx-0.5 text-xs font-medium text-gray-700 truncate' title={outputVarNode?.title} style={{ |
| | | maxWidth: maxNodeNameWidth, |
| | | }}>{outputVarNode?.title}</div> |
| | | <Line3 className='mr-0.5'></Line3> |
| | | </div> |
| | | )} |
| | | {isShowAPart && ( |
| | | <div className='flex items-center'> |
| | | <RiMoreLine className='h-3 w-3 text-text-secondary' /> |
| | | <Line3 className='mr-0.5 text-divider-deep'></Line3> |
| | | </div> |
| | | )} |
| | | <div className='flex items-center text-text-accent'> |
| | | {!hasValue && <Variable02 className='h-3.5 w-3.5' />} |
| | | {isEnv && <Env className='h-3.5 w-3.5 text-util-colors-violet-violet-600' />} |
| | | {isChatVar && <BubbleX className='h-3.5 w-3.5 text-util-colors-teal-teal-700' />} |
| | | <div className={cn('ml-0.5 truncate text-xs font-medium', isEnv && '!text-text-secondary', isChatVar && 'text-util-colors-teal-teal-700', isException && 'text-text-warning')} title={varName} style={{ |
| | | <div className='flex items-center text-primary-600'> |
| | | {!hasValue && <Variable02 className='w-3.5 h-3.5' />} |
| | | {isEnv && <Env className='w-3.5 h-3.5 text-util-colors-violet-violet-600' />} |
| | | {isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />} |
| | | <div className={cn('ml-0.5 text-xs font-medium truncate', isEnv && '!text-text-secondary', isChatVar && 'text-util-colors-teal-teal-700', isException && 'text-text-warning')} title={varName} style={{ |
| | | maxWidth: maxVarNameWidth, |
| | | }}>{varName}</div> |
| | | </div> |
| | | <div className='system-xs-regular ml-0.5 truncate text-center capitalize text-text-tertiary' title={type} style={{ |
| | | <div className='ml-0.5 capitalize truncate text-text-tertiary text-center system-xs-regular' title={type} style={{ |
| | | maxWidth: maxTypeWidth, |
| | | }}>{type}</div> |
| | | {!isValidVar && <RiErrorWarningFill className='ml-0.5 h-3 w-3 text-text-destructive' />} |
| | | {!isValidVar && <RiErrorWarningFill className='ml-0.5 w-3 h-3 text-[#D92D20]' />} |
| | | </> |
| | | ) |
| | | : <div className={`overflow-hidden ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'} system-sm-regular text-ellipsis`}>{placeholder ?? t('workflow.common.setVarValuePlaceholder')}</div>} |
| | | : <div className={`overflow-hidden ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'} text-ellipsis system-sm-regular`}>{placeholder ?? t('workflow.common.setVarValuePlaceholder')}</div>} |
| | | </div> |
| | | </Tooltip> |
| | | </div> |
| | |
| | | </VarPickerWrap> |
| | | )} |
| | | {(hasValue && !readonly && !isInTable) && (<div |
| | | className='group invisible absolute right-1 top-[50%] h-5 translate-y-[-50%] cursor-pointer rounded-md p-1 hover:bg-state-base-hover group-hover/wrap:visible' |
| | | className='invisible group-hover/wrap:visible absolute h-5 right-1 top-[50%] translate-y-[-50%] group p-1 rounded-md hover:bg-black/5 cursor-pointer' |
| | | onClick={handleClearVar} |
| | | > |
| | | <RiCloseLine className='h-3.5 w-3.5 text-text-tertiary group-hover:text-text-secondary' /> |
| | | <RiCloseLine className='w-3.5 h-3.5 text-gray-500 group-hover:text-gray-800' /> |
| | | </div>)} |
| | | {!hasValue && valueTypePlaceHolder && ( |
| | | <Badge |
| | |
| | | </div>)} |
| | | {!readonly && isInTable && ( |
| | | <RemoveButton |
| | | className='absolute right-1 top-0.5 hidden group-hover/picker-trigger-wrap:block' |
| | | className='group-hover/picker-trigger-wrap:block hidden absolute right-1 top-0.5' |
| | | onClick={() => onRemove?.()} |
| | | /> |
| | | )} |
| | |
| | | </> |
| | | </WrapElem> |
| | | <PortalToFollowElemContent style={{ |
| | | zIndex: zIndex || 100, |
| | | zIndex: 100, |
| | | }} className='mt-1'> |
| | | {!isConstant && ( |
| | | <VarReferencePopup |