Feature/newnew workflow loop node (#14863)
Co-authored-by: arkunzz <4873204@qq.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import type { HandleAddCondition } from '../types'
|
||||
import Button from '@/app/components/base/button'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
|
||||
import type {
|
||||
NodeOutPutVar,
|
||||
ValueSelector,
|
||||
Var,
|
||||
} from '@/app/components/workflow/types'
|
||||
|
||||
type ConditionAddProps = {
|
||||
className?: string
|
||||
variables: NodeOutPutVar[]
|
||||
onSelectVariable: HandleAddCondition
|
||||
disabled?: boolean
|
||||
}
|
||||
const ConditionAdd = ({
|
||||
className,
|
||||
variables,
|
||||
onSelectVariable,
|
||||
disabled,
|
||||
}: ConditionAddProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const handleSelectVariable = useCallback((valueSelector: ValueSelector, varItem: Var) => {
|
||||
onSelectVariable(valueSelector, varItem)
|
||||
setOpen(false)
|
||||
}, [onSelectVariable, setOpen])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-start'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 0,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(!open)}>
|
||||
<Button
|
||||
size='small'
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
>
|
||||
<RiAddLine className='mr-1 w-3.5 h-3.5' />
|
||||
{t('workflow.nodes.ifElse.addCondition')}
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
<div className='w-[296px] bg-components-panel-bg-blur rounded-lg border-[0.5px] border-components-panel-border shadow-lg'>
|
||||
<VarReferenceVars
|
||||
vars={variables}
|
||||
isSupportFileVar
|
||||
onChange={handleSelectVariable}
|
||||
/>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConditionAdd
|
@@ -0,0 +1,115 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ComparisonOperator, type Condition } from '../types'
|
||||
import {
|
||||
comparisonOperatorNotRequireValue,
|
||||
isComparisonOperatorNeedTranslate,
|
||||
isEmptyRelatedOperator,
|
||||
} from '../utils'
|
||||
import type { ValueSelector } from '../../../types'
|
||||
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from './../default'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import cn from '@/utils/classnames'
|
||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
const i18nPrefix = 'workflow.nodes.ifElse'
|
||||
|
||||
type ConditionValueProps = {
|
||||
condition: Condition
|
||||
}
|
||||
const ConditionValue = ({
|
||||
condition,
|
||||
}: ConditionValueProps) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
variable_selector,
|
||||
comparison_operator: operator,
|
||||
sub_variable_condition,
|
||||
} = condition
|
||||
|
||||
const variableSelector = variable_selector as ValueSelector
|
||||
|
||||
const variableName = (isSystemVar(variableSelector) ? variableSelector.slice(0).join('.') : variableSelector.slice(1).join('.'))
|
||||
const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator
|
||||
const notHasValue = comparisonOperatorNotRequireValue(operator)
|
||||
const isEnvVar = isENV(variableSelector)
|
||||
const isChatVar = isConversationVar(variableSelector)
|
||||
const formatValue = useCallback((c: Condition) => {
|
||||
const notHasValue = comparisonOperatorNotRequireValue(c.comparison_operator)
|
||||
if (notHasValue)
|
||||
return ''
|
||||
|
||||
const value = c.value as string
|
||||
return value.replace(/{{#([^#]*)#}}/g, (a, b) => {
|
||||
const arr: string[] = b.split('.')
|
||||
if (isSystemVar(arr))
|
||||
return `{{${b}}}`
|
||||
|
||||
return `{{${arr.slice(1).join('.')}}}`
|
||||
})
|
||||
}, [])
|
||||
|
||||
const isSelect = useCallback((c: Condition) => {
|
||||
return c.comparison_operator === ComparisonOperator.in || c.comparison_operator === ComparisonOperator.notIn
|
||||
}, [])
|
||||
|
||||
const selectName = useCallback((c: Condition) => {
|
||||
const isSelect = c.comparison_operator === ComparisonOperator.in || c.comparison_operator === ComparisonOperator.notIn
|
||||
if (isSelect) {
|
||||
const name = [...FILE_TYPE_OPTIONS, ...TRANSFER_METHOD].filter(item => item.value === (Array.isArray(c.value) ? c.value[0] : c.value))[0]
|
||||
return name
|
||||
? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/{{#([^#]*)#}}/g, (a, b) => {
|
||||
const arr: string[] = b.split('.')
|
||||
if (isSystemVar(arr))
|
||||
return `{{${b}}}`
|
||||
|
||||
return `{{${arr.slice(1).join('.')}}}`
|
||||
})
|
||||
: ''
|
||||
}
|
||||
return ''
|
||||
}, [t])
|
||||
|
||||
return (
|
||||
<div className='rounded-md bg-workflow-block-parma-bg'>
|
||||
<div className='flex items-center px-1 h-6 '>
|
||||
{!isEnvVar && !isChatVar && <Variable02 className='shrink-0 mr-1 w-3.5 h-3.5 text-text-accent' />}
|
||||
{isEnvVar && <Env className='shrink-0 mr-1 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(
|
||||
'shrink-0 truncate text-xs font-medium text-text-accent',
|
||||
!notHasValue && 'max-w-[70px]',
|
||||
)}
|
||||
title={variableName}
|
||||
>
|
||||
{variableName}
|
||||
</div>
|
||||
<div
|
||||
className='shrink-0 mx-1 text-xs font-medium text-text-primary'
|
||||
title={operatorName}
|
||||
>
|
||||
{operatorName}
|
||||
</div>
|
||||
</div>
|
||||
<div className='ml-[10px] pl-[10px] border-l border-divider-regular'>
|
||||
{
|
||||
sub_variable_condition?.conditions.map((c: Condition, index) => (
|
||||
<div className='relative flex items-center h-6 space-x-1' key={c.id}>
|
||||
<div className='text-text-accent system-xs-medium'>{c.key}</div>
|
||||
<div className='text-text-primary system-xs-medium'>{isComparisonOperatorNeedTranslate(c.comparison_operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${c.comparison_operator}`) : c.comparison_operator}</div>
|
||||
{c.comparison_operator && !isEmptyRelatedOperator(c.comparison_operator) && <div className='text-text-secondary system-xs-regular'>{isSelect(c) ? selectName(c) : formatValue(c)}</div>}
|
||||
{index !== sub_variable_condition.conditions.length - 1 && (<div className='absolute z-10 right-1 bottom-[-10px] leading-4 text-[10px] font-medium text-text-accent uppercase'>{t(`${i18nPrefix}.${sub_variable_condition.logical_operator}`)}</div>)}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ConditionValue)
|
@@ -0,0 +1,53 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import PromptEditor from '@/app/components/base/prompt-editor'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type {
|
||||
Node,
|
||||
} from '@/app/components/workflow/types'
|
||||
|
||||
type ConditionInputProps = {
|
||||
disabled?: boolean
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
availableNodes: Node[]
|
||||
}
|
||||
const ConditionInput = ({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
availableNodes,
|
||||
}: ConditionInputProps) => {
|
||||
const { t } = useTranslation()
|
||||
const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey)
|
||||
|
||||
return (
|
||||
<PromptEditor
|
||||
key={controlPromptEditorRerenderKey}
|
||||
compact
|
||||
value={value}
|
||||
placeholder={t('workflow.nodes.ifElse.enterValue') || ''}
|
||||
workflowVariableBlock={{
|
||||
show: true,
|
||||
variables: [],
|
||||
workflowNodesMap: availableNodes.reduce((acc, node) => {
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
title: t('workflow.blocks.start'),
|
||||
type: BlockEnum.Start,
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {} as any),
|
||||
}}
|
||||
onChange={onChange}
|
||||
editable={!disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConditionInput
|
@@ -0,0 +1,330 @@
|
||||
import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiDeleteBinLine } from '@remixicon/react'
|
||||
import produce from 'immer'
|
||||
import type { VarType as NumberVarType } from '../../../tool/types'
|
||||
import type {
|
||||
Condition,
|
||||
HandleAddSubVariableCondition,
|
||||
HandleRemoveCondition,
|
||||
HandleToggleSubVariableConditionLogicalOperator,
|
||||
HandleUpdateCondition,
|
||||
HandleUpdateSubVariableCondition,
|
||||
handleRemoveSubVariableCondition,
|
||||
} from '../../types'
|
||||
import {
|
||||
ComparisonOperator,
|
||||
} from '../../types'
|
||||
import ConditionNumberInput from '../condition-number-input'
|
||||
import ConditionWrap from '../condition-wrap'
|
||||
import { comparisonOperatorNotRequireValue, getOperators } from './../../utils'
|
||||
import ConditionOperator from './condition-operator'
|
||||
import ConditionInput from './condition-input'
|
||||
import { FILE_TYPE_OPTIONS, SUB_VARIABLES, TRANSFER_METHOD } from './../../default'
|
||||
import type {
|
||||
Node,
|
||||
NodeOutPutVar,
|
||||
ValueSelector,
|
||||
Var,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
import { SimpleSelect as Select } from '@/app/components/base/select'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import ConditionVarSelector from './condition-var-selector'
|
||||
|
||||
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
|
||||
|
||||
type ConditionItemProps = {
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
conditionId: string // in isSubVariableKey it's the value of the parent condition's id
|
||||
condition: Condition // condition may the condition of case or condition of sub variable
|
||||
file?: { key: string }
|
||||
isSubVariableKey?: boolean
|
||||
isValueFieldShort?: boolean
|
||||
onRemoveCondition?: HandleRemoveCondition
|
||||
onUpdateCondition?: HandleUpdateCondition
|
||||
onAddSubVariableCondition?: HandleAddSubVariableCondition
|
||||
onRemoveSubVariableCondition?: handleRemoveSubVariableCondition
|
||||
onUpdateSubVariableCondition?: HandleUpdateSubVariableCondition
|
||||
onToggleSubVariableConditionLogicalOperator?: HandleToggleSubVariableConditionLogicalOperator
|
||||
nodeId: string
|
||||
availableNodes: Node[]
|
||||
numberVariables: NodeOutPutVar[]
|
||||
availableVars: NodeOutPutVar[]
|
||||
}
|
||||
const ConditionItem = ({
|
||||
className,
|
||||
disabled,
|
||||
conditionId,
|
||||
condition,
|
||||
file,
|
||||
isSubVariableKey,
|
||||
isValueFieldShort,
|
||||
onRemoveCondition,
|
||||
onUpdateCondition,
|
||||
onAddSubVariableCondition,
|
||||
onRemoveSubVariableCondition,
|
||||
onUpdateSubVariableCondition,
|
||||
onToggleSubVariableConditionLogicalOperator,
|
||||
nodeId,
|
||||
availableNodes,
|
||||
numberVariables,
|
||||
availableVars,
|
||||
}: ConditionItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const doUpdateCondition = useCallback((newCondition: Condition) => {
|
||||
if (isSubVariableKey)
|
||||
onUpdateSubVariableCondition?.(conditionId, condition.id, newCondition)
|
||||
else
|
||||
onUpdateCondition?.(condition.id, newCondition)
|
||||
}, [condition, conditionId, isSubVariableKey, onUpdateCondition, onUpdateSubVariableCondition])
|
||||
|
||||
const canChooseOperator = useMemo(() => {
|
||||
if (disabled)
|
||||
return false
|
||||
|
||||
if (isSubVariableKey)
|
||||
return !!condition.key
|
||||
|
||||
return true
|
||||
}, [condition.key, disabled, isSubVariableKey])
|
||||
const handleUpdateConditionOperator = useCallback((value: ComparisonOperator) => {
|
||||
const newCondition = {
|
||||
...condition,
|
||||
comparison_operator: value,
|
||||
}
|
||||
doUpdateCondition(newCondition)
|
||||
}, [condition, doUpdateCondition])
|
||||
|
||||
const handleUpdateConditionNumberVarType = useCallback((numberVarType: NumberVarType) => {
|
||||
const newCondition = {
|
||||
...condition,
|
||||
numberVarType,
|
||||
value: '',
|
||||
}
|
||||
doUpdateCondition(newCondition)
|
||||
}, [condition, doUpdateCondition])
|
||||
|
||||
const isSubVariable = condition.varType === VarType.arrayFile && [ComparisonOperator.contains, ComparisonOperator.notContains, ComparisonOperator.allOf].includes(condition.comparison_operator!)
|
||||
const fileAttr = useMemo(() => {
|
||||
if (file)
|
||||
return file
|
||||
if (isSubVariableKey) {
|
||||
return {
|
||||
key: condition.key!,
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}, [condition.key, file, isSubVariableKey])
|
||||
|
||||
const isArrayValue = fileAttr?.key === 'transfer_method' || fileAttr?.key === 'type'
|
||||
|
||||
const handleUpdateConditionValue = useCallback((value: string) => {
|
||||
if (value === condition.value || (isArrayValue && value === condition.value?.[0]))
|
||||
return
|
||||
const newCondition = {
|
||||
...condition,
|
||||
value: isArrayValue ? [value] : value,
|
||||
}
|
||||
doUpdateCondition(newCondition)
|
||||
}, [condition, doUpdateCondition, isArrayValue])
|
||||
|
||||
const isSelect = condition.comparison_operator && [ComparisonOperator.in, ComparisonOperator.notIn].includes(condition.comparison_operator)
|
||||
const selectOptions = useMemo(() => {
|
||||
if (isSelect) {
|
||||
if (fileAttr?.key === 'type' || condition.comparison_operator === ComparisonOperator.allOf) {
|
||||
return FILE_TYPE_OPTIONS.map(item => ({
|
||||
name: t(`${optionNameI18NPrefix}.${item.i18nKey}`),
|
||||
value: item.value,
|
||||
}))
|
||||
}
|
||||
if (fileAttr?.key === 'transfer_method') {
|
||||
return TRANSFER_METHOD.map(item => ({
|
||||
name: t(`${optionNameI18NPrefix}.${item.i18nKey}`),
|
||||
value: item.value,
|
||||
}))
|
||||
}
|
||||
return []
|
||||
}
|
||||
return []
|
||||
}, [condition.comparison_operator, fileAttr?.key, isSelect, t])
|
||||
|
||||
const isNotInput = isSelect || isSubVariable
|
||||
|
||||
const isSubVarSelect = isSubVariableKey
|
||||
const subVarOptions = SUB_VARIABLES.map(item => ({
|
||||
name: item,
|
||||
value: item,
|
||||
}))
|
||||
|
||||
const handleSubVarKeyChange = useCallback((key: string) => {
|
||||
const newCondition = produce(condition, (draft) => {
|
||||
draft.key = key
|
||||
if (key === 'size')
|
||||
draft.varType = VarType.number
|
||||
else
|
||||
draft.varType = VarType.string
|
||||
|
||||
draft.value = ''
|
||||
draft.comparison_operator = getOperators(undefined, { key })[0]
|
||||
})
|
||||
|
||||
onUpdateSubVariableCondition?.(conditionId, condition.id, newCondition)
|
||||
}, [condition, conditionId, onUpdateSubVariableCondition])
|
||||
|
||||
const doRemoveCondition = useCallback(() => {
|
||||
if (isSubVariableKey)
|
||||
onRemoveSubVariableCondition?.(conditionId, condition.id)
|
||||
else
|
||||
onRemoveCondition?.(condition.id)
|
||||
}, [condition, conditionId, isSubVariableKey, onRemoveCondition, onRemoveSubVariableCondition])
|
||||
|
||||
const handleVarChange = useCallback((valueSelector: ValueSelector, varItem: Var) => {
|
||||
const newCondition = produce(condition, (draft) => {
|
||||
draft.variable_selector = valueSelector
|
||||
draft.varType = varItem.type
|
||||
draft.value = ''
|
||||
draft.comparison_operator = getOperators(varItem.type)[0]
|
||||
})
|
||||
doUpdateCondition(newCondition)
|
||||
setOpen(false)
|
||||
}, [condition, doUpdateCondition])
|
||||
|
||||
return (
|
||||
<div className={cn('flex mb-1 last-of-type:mb-0', className)}>
|
||||
<div className={cn(
|
||||
'grow bg-components-input-bg-normal rounded-lg',
|
||||
isHovered && 'bg-state-destructive-hover',
|
||||
)}>
|
||||
<div className='flex items-center p-1'>
|
||||
<div className='grow w-0'>
|
||||
{isSubVarSelect
|
||||
? (
|
||||
<Select
|
||||
wrapperClassName='h-6'
|
||||
className='pl-0 text-xs'
|
||||
optionWrapClassName='w-[165px] max-h-none'
|
||||
defaultValue={condition.key}
|
||||
items={subVarOptions}
|
||||
onSelect={item => handleSubVarKeyChange(item.value as string)}
|
||||
renderTrigger={item => (
|
||||
item
|
||||
? <div className='flex justify-start cursor-pointer'>
|
||||
<div className='inline-flex max-w-full px-1.5 items-center h-6 rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark shadow-xs text-text-accent'>
|
||||
<Variable02 className='shrink-0 w-3.5 h-3.5 text-text-accent' />
|
||||
<div className='ml-0.5 truncate system-xs-medium'>{item?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
: <div className='text-left text-components-input-text-placeholder system-sm-regular'>{t('common.placeholder.select')}</div>
|
||||
)}
|
||||
hideChecked
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<ConditionVarSelector
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
valueSelector={condition.variable_selector || []}
|
||||
varType={condition.varType}
|
||||
availableNodes={availableNodes}
|
||||
nodesOutputVars={availableVars}
|
||||
onChange={handleVarChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className='mx-1 w-[1px] h-3 bg-divider-regular'></div>
|
||||
<ConditionOperator
|
||||
disabled={!canChooseOperator}
|
||||
varType={condition.varType}
|
||||
value={condition.comparison_operator}
|
||||
onSelect={handleUpdateConditionOperator}
|
||||
file={fileAttr}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && (
|
||||
<div className='px-2 py-1 max-h-[100px] border-t border-t-divider-subtle overflow-y-auto'>
|
||||
<ConditionInput
|
||||
disabled={disabled}
|
||||
value={condition.value as string}
|
||||
onChange={handleUpdateConditionValue}
|
||||
availableNodes={availableNodes}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.number && (
|
||||
<div className='px-2 py-1 pt-[3px] border-t border-t-divider-subtle'>
|
||||
<ConditionNumberInput
|
||||
numberVarType={condition.numberVarType}
|
||||
onNumberVarTypeChange={handleUpdateConditionNumberVarType}
|
||||
value={condition.value as string}
|
||||
onValueChange={handleUpdateConditionValue}
|
||||
variables={numberVariables}
|
||||
isShort={isValueFieldShort}
|
||||
unit={fileAttr?.key === 'size' ? 'Byte' : undefined}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!comparisonOperatorNotRequireValue(condition.comparison_operator) && isSelect && (
|
||||
<div className='border-t border-t-divider-subtle'>
|
||||
<Select
|
||||
wrapperClassName='h-8'
|
||||
className='px-2 text-xs rounded-t-none'
|
||||
defaultValue={isArrayValue ? (condition.value as string[])?.[0] : (condition.value as string)}
|
||||
items={selectOptions}
|
||||
onSelect={item => handleUpdateConditionValue(item.value as string)}
|
||||
hideChecked
|
||||
notClearable
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!comparisonOperatorNotRequireValue(condition.comparison_operator) && isSubVariable && (
|
||||
<div className='p-1'>
|
||||
<ConditionWrap
|
||||
isSubVariable
|
||||
conditions={condition.sub_variable_condition?.conditions || []}
|
||||
logicalOperator={condition.sub_variable_condition?.logical_operator}
|
||||
conditionId={conditionId}
|
||||
readOnly={!!disabled}
|
||||
handleAddSubVariableCondition={onAddSubVariableCondition}
|
||||
handleRemoveSubVariableCondition={onRemoveSubVariableCondition}
|
||||
handleUpdateSubVariableCondition={onUpdateSubVariableCondition}
|
||||
handleToggleSubVariableConditionLogicalOperator={onToggleSubVariableConditionLogicalOperator}
|
||||
nodeId={nodeId}
|
||||
availableNodes={availableNodes}
|
||||
availableVars={availableVars}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
className='shrink-0 flex items-center justify-center ml-1 mt-1 w-6 h-6 rounded-lg cursor-pointer hover:bg-state-destructive-hover text-text-tertiary hover:text-text-destructive'
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
onClick={doRemoveCondition}
|
||||
>
|
||||
<RiDeleteBinLine className='w-4 h-4' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConditionItem
|
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import { getOperators, isComparisonOperatorNeedTranslate } from '../../utils'
|
||||
import type { ComparisonOperator } from '../../types'
|
||||
import Button from '@/app/components/base/button'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import type { VarType } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
const i18nPrefix = 'workflow.nodes.ifElse'
|
||||
|
||||
type ConditionOperatorProps = {
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
varType: VarType
|
||||
file?: { key: string }
|
||||
value?: string
|
||||
onSelect: (value: ComparisonOperator) => void
|
||||
}
|
||||
const ConditionOperator = ({
|
||||
className,
|
||||
disabled,
|
||||
varType,
|
||||
file,
|
||||
value,
|
||||
onSelect,
|
||||
}: ConditionOperatorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const options = useMemo(() => {
|
||||
return getOperators(varType, file).map((o) => {
|
||||
return {
|
||||
label: isComparisonOperatorNeedTranslate(o) ? t(`${i18nPrefix}.comparisonOperator.${o}`) : o,
|
||||
value: o,
|
||||
}
|
||||
})
|
||||
}, [t, varType, file])
|
||||
const selectedOption = options.find(o => Array.isArray(value) ? o.value === value[0] : o.value === value)
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 0,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<Button
|
||||
className={cn('shrink-0', !selectedOption && 'opacity-50', className)}
|
||||
size='small'
|
||||
variant='ghost'
|
||||
disabled={disabled}
|
||||
>
|
||||
{
|
||||
selectedOption
|
||||
? selectedOption.label
|
||||
: t(`${i18nPrefix}.select`)
|
||||
}
|
||||
<RiArrowDownSLine className='ml-1 w-3.5 h-3.5' />
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-10'>
|
||||
<div className='p-1 bg-components-panel-bg-blur rounded-xl border-[0.5px] border-components-panel-border shadow-lg'>
|
||||
{
|
||||
options.map(option => (
|
||||
<div
|
||||
key={option.value}
|
||||
className='flex items-center px-3 py-1.5 h-7 text-[13px] font-medium text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
onSelect(option.value)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConditionOperator
|
@@ -0,0 +1,58 @@
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
import VariableTag from '@/app/components/workflow/nodes/_base/components/variable-tag'
|
||||
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
|
||||
import type { Node, NodeOutPutVar, ValueSelector, Var, VarType } from '@/app/components/workflow/types'
|
||||
|
||||
type ConditionVarSelectorProps = {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
valueSelector: ValueSelector
|
||||
varType: VarType
|
||||
availableNodes: Node[]
|
||||
nodesOutputVars: NodeOutPutVar[]
|
||||
onChange: (valueSelector: ValueSelector, varItem: Var) => void
|
||||
}
|
||||
|
||||
const ConditionVarSelector = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
valueSelector,
|
||||
varType,
|
||||
availableNodes,
|
||||
nodesOutputVars,
|
||||
onChange,
|
||||
}: ConditionVarSelectorProps) => {
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
placement='bottom-start'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 0,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => onOpenChange(!open)}>
|
||||
<div className="cursor-pointer">
|
||||
<VariableTag
|
||||
valueSelector={valueSelector}
|
||||
varType={varType}
|
||||
availableNodes={availableNodes}
|
||||
isShort
|
||||
/>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
<div className='w-[296px] bg-components-panel-bg-blur rounded-lg border-[0.5px] border-components-panel-border shadow-lg'>
|
||||
<VarReferenceVars
|
||||
vars={nodesOutputVars}
|
||||
isSupportFileVar
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConditionVarSelector
|
@@ -0,0 +1,126 @@
|
||||
import { RiLoopLeftLine } from '@remixicon/react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import {
|
||||
type Condition,
|
||||
type HandleAddSubVariableCondition,
|
||||
type HandleRemoveCondition,
|
||||
type HandleToggleConditionLogicalOperator,
|
||||
type HandleToggleSubVariableConditionLogicalOperator,
|
||||
type HandleUpdateCondition,
|
||||
type HandleUpdateSubVariableCondition,
|
||||
LogicalOperator,
|
||||
type handleRemoveSubVariableCondition,
|
||||
} from '../../types'
|
||||
import ConditionItem from './condition-item'
|
||||
import type {
|
||||
Node,
|
||||
NodeOutPutVar,
|
||||
} from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type ConditionListProps = {
|
||||
isSubVariable?: boolean
|
||||
disabled?: boolean
|
||||
conditionId?: string
|
||||
conditions: Condition[]
|
||||
logicalOperator?: LogicalOperator
|
||||
onRemoveCondition?: HandleRemoveCondition
|
||||
onUpdateCondition?: HandleUpdateCondition
|
||||
onToggleConditionLogicalOperator?: HandleToggleConditionLogicalOperator
|
||||
nodeId: string
|
||||
availableNodes: Node[]
|
||||
numberVariables: NodeOutPutVar[]
|
||||
onAddSubVariableCondition?: HandleAddSubVariableCondition
|
||||
onRemoveSubVariableCondition?: handleRemoveSubVariableCondition
|
||||
onUpdateSubVariableCondition?: HandleUpdateSubVariableCondition
|
||||
onToggleSubVariableConditionLogicalOperator?: HandleToggleSubVariableConditionLogicalOperator
|
||||
availableVars: NodeOutPutVar[]
|
||||
}
|
||||
const ConditionList = ({
|
||||
isSubVariable,
|
||||
disabled,
|
||||
conditionId,
|
||||
conditions,
|
||||
logicalOperator,
|
||||
onUpdateCondition,
|
||||
onRemoveCondition,
|
||||
onToggleConditionLogicalOperator,
|
||||
onAddSubVariableCondition,
|
||||
onRemoveSubVariableCondition,
|
||||
onUpdateSubVariableCondition,
|
||||
onToggleSubVariableConditionLogicalOperator,
|
||||
nodeId,
|
||||
availableNodes,
|
||||
numberVariables,
|
||||
availableVars,
|
||||
}: ConditionListProps) => {
|
||||
const doToggleConditionLogicalOperator = useCallback((conditionId?: string) => {
|
||||
if (isSubVariable && conditionId)
|
||||
onToggleSubVariableConditionLogicalOperator?.(conditionId)
|
||||
else
|
||||
onToggleConditionLogicalOperator?.()
|
||||
}, [isSubVariable, onToggleConditionLogicalOperator, onToggleSubVariableConditionLogicalOperator])
|
||||
|
||||
const isValueFieldShort = useMemo(() => {
|
||||
if (isSubVariable && conditions.length > 1)
|
||||
return true
|
||||
|
||||
return false
|
||||
}, [conditions.length, isSubVariable])
|
||||
const conditionItemClassName = useMemo(() => {
|
||||
if (!isSubVariable)
|
||||
return ''
|
||||
if (conditions.length < 2)
|
||||
return ''
|
||||
return logicalOperator === LogicalOperator.and ? 'pl-[51px]' : 'pl-[42px]'
|
||||
}, [conditions.length, isSubVariable, logicalOperator])
|
||||
|
||||
return (
|
||||
<div className={cn('relative', conditions.length > 1 && !isSubVariable && 'pl-[60px]')}>
|
||||
{
|
||||
conditions.length > 1 && (
|
||||
<div className={cn(
|
||||
'absolute top-0 bottom-0 left-0 w-[60px]',
|
||||
isSubVariable && logicalOperator === LogicalOperator.and && 'left-[-10px]',
|
||||
isSubVariable && logicalOperator === LogicalOperator.or && 'left-[-18px]',
|
||||
)}>
|
||||
<div className='absolute top-4 bottom-4 left-[46px] w-2.5 border border-divider-deep rounded-l-[8px] border-r-0'></div>
|
||||
<div className='absolute top-1/2 -translate-y-1/2 right-0 w-4 h-[29px] bg-components-panel-bg'></div>
|
||||
<div
|
||||
className='absolute top-1/2 right-1 -translate-y-1/2 flex items-center px-1 h-[21px] rounded-md border-[0.5px] border-components-button-secondary-border shadow-xs bg-components-button-secondary-bg text-text-accent-secondary text-[10px] font-semibold cursor-pointer select-none'
|
||||
onClick={() => doToggleConditionLogicalOperator(conditionId)}
|
||||
>
|
||||
{logicalOperator && logicalOperator.toUpperCase()}
|
||||
<RiLoopLeftLine className='ml-0.5 w-3 h-3' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
conditions.map(condition => (
|
||||
<ConditionItem
|
||||
key={condition.id}
|
||||
className={conditionItemClassName}
|
||||
disabled={disabled}
|
||||
conditionId={isSubVariable ? conditionId! : condition.id}
|
||||
condition={condition}
|
||||
isValueFieldShort={isValueFieldShort}
|
||||
onUpdateCondition={onUpdateCondition}
|
||||
onRemoveCondition={onRemoveCondition}
|
||||
onAddSubVariableCondition={onAddSubVariableCondition}
|
||||
onRemoveSubVariableCondition={onRemoveSubVariableCondition}
|
||||
onUpdateSubVariableCondition={onUpdateSubVariableCondition}
|
||||
onToggleSubVariableConditionLogicalOperator={onToggleSubVariableConditionLogicalOperator}
|
||||
nodeId={nodeId}
|
||||
availableNodes={availableNodes}
|
||||
numberVariables={numberVariables}
|
||||
isSubVariableKey={isSubVariable}
|
||||
availableVars={availableVars}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConditionList
|
@@ -0,0 +1,168 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import { capitalize } from 'lodash-es'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { VarType as NumberVarType } from '../../tool/types'
|
||||
import VariableTag from '../../_base/components/variable-tag'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Button from '@/app/components/base/button'
|
||||
import cn from '@/utils/classnames'
|
||||
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
|
||||
import type {
|
||||
NodeOutPutVar,
|
||||
ValueSelector,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import { variableTransformer } from '@/app/components/workflow/utils'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
|
||||
const options = [
|
||||
NumberVarType.variable,
|
||||
NumberVarType.constant,
|
||||
]
|
||||
|
||||
type ConditionNumberInputProps = {
|
||||
numberVarType?: NumberVarType
|
||||
onNumberVarTypeChange: (v: NumberVarType) => void
|
||||
value: string
|
||||
onValueChange: (v: string) => void
|
||||
variables: NodeOutPutVar[]
|
||||
isShort?: boolean
|
||||
unit?: string
|
||||
}
|
||||
const ConditionNumberInput = ({
|
||||
numberVarType = NumberVarType.constant,
|
||||
onNumberVarTypeChange,
|
||||
value,
|
||||
onValueChange,
|
||||
variables,
|
||||
isShort,
|
||||
unit,
|
||||
}: ConditionNumberInputProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [numberVarTypeVisible, setNumberVarTypeVisible] = useState(false)
|
||||
const [variableSelectorVisible, setVariableSelectorVisible] = useState(false)
|
||||
const [isFocus, {
|
||||
setTrue: setFocus,
|
||||
setFalse: setBlur,
|
||||
}] = useBoolean()
|
||||
|
||||
const handleSelectVariable = useCallback((valueSelector: ValueSelector) => {
|
||||
onValueChange(variableTransformer(valueSelector) as string)
|
||||
setVariableSelectorVisible(false)
|
||||
}, [onValueChange])
|
||||
|
||||
return (
|
||||
<div className='flex items-center cursor-pointer'>
|
||||
<PortalToFollowElem
|
||||
open={numberVarTypeVisible}
|
||||
onOpenChange={setNumberVarTypeVisible}
|
||||
placement='bottom-start'
|
||||
offset={{ mainAxis: 2, crossAxis: 0 }}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setNumberVarTypeVisible(v => !v)}>
|
||||
<Button
|
||||
className='shrink-0'
|
||||
variant='ghost'
|
||||
size='small'
|
||||
>
|
||||
{capitalize(numberVarType)}
|
||||
<RiArrowDownSLine className='ml-[1px] w-3.5 h-3.5' />
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
<div className='p-1 w-[112px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
|
||||
{
|
||||
options.map(option => (
|
||||
<div
|
||||
key={option}
|
||||
className={cn(
|
||||
'flex items-center px-3 h-7 rounded-md hover:bg-state-base-hover cursor-pointer',
|
||||
'text-[13px] font-medium text-text-secondary',
|
||||
numberVarType === option && 'bg-state-base-hover',
|
||||
)}
|
||||
onClick={() => {
|
||||
onNumberVarTypeChange(option)
|
||||
setNumberVarTypeVisible(false)
|
||||
}}
|
||||
>
|
||||
{capitalize(option)}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
<div className='mx-1 w-[1px] h-4 bg-divider-regular'></div>
|
||||
<div className='grow w-0 ml-0.5'>
|
||||
{
|
||||
numberVarType === NumberVarType.variable && (
|
||||
<PortalToFollowElem
|
||||
open={variableSelectorVisible}
|
||||
onOpenChange={setVariableSelectorVisible}
|
||||
placement='bottom-start'
|
||||
offset={{ mainAxis: 2, crossAxis: 0 }}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
className='w-full'
|
||||
onClick={() => setVariableSelectorVisible(v => !v)}>
|
||||
{
|
||||
value && (
|
||||
<VariableTag
|
||||
valueSelector={variableTransformer(value) as string[]}
|
||||
varType={VarType.number}
|
||||
isShort={isShort}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!value && (
|
||||
<div className='flex items-center p-1 h-6 text-components-input-text-placeholder text-[13px]'>
|
||||
<Variable02 className='shrink-0 mr-1 w-4 h-4' />
|
||||
<div className='w-0 grow truncate'>{t('workflow.nodes.ifElse.selectVariable')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
<div className={cn('w-[296px] pt-1 bg-components-panel-bg-blur rounded-lg border-[0.5px] border-components-panel-border shadow-lg', isShort && 'w-[200px]')}>
|
||||
<VarReferenceVars
|
||||
vars={variables}
|
||||
onChange={handleSelectVariable}
|
||||
/>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
{
|
||||
numberVarType === NumberVarType.constant && (
|
||||
<div className=' relative'>
|
||||
<input
|
||||
className={cn('block w-full px-2 text-[13px] text-components-input-text-filled placeholder:text-components-input-text-placeholder outline-none appearance-none bg-transparent', unit && 'pr-6')}
|
||||
type='number'
|
||||
value={value}
|
||||
onChange={e => onValueChange(e.target.value)}
|
||||
placeholder={t('workflow.nodes.ifElse.enterValue') || ''}
|
||||
onFocus={setFocus}
|
||||
onBlur={setBlur}
|
||||
/>
|
||||
{!isFocus && unit && <div className='absolute right-2 top-[50%] translate-y-[-50%] text-text-tertiary system-sm-regular'>{unit}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ConditionNumberInput)
|
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ComparisonOperator } from '../types'
|
||||
import {
|
||||
comparisonOperatorNotRequireValue,
|
||||
isComparisonOperatorNeedTranslate,
|
||||
} from '../utils'
|
||||
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from './../default'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import cn from '@/utils/classnames'
|
||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
|
||||
type ConditionValueProps = {
|
||||
variableSelector: string[]
|
||||
labelName?: string
|
||||
operator: ComparisonOperator
|
||||
value: string | string[]
|
||||
}
|
||||
const ConditionValue = ({
|
||||
variableSelector,
|
||||
labelName,
|
||||
operator,
|
||||
value,
|
||||
}: ConditionValueProps) => {
|
||||
const { t } = useTranslation()
|
||||
const variableName = labelName || (isSystemVar(variableSelector) ? variableSelector.slice(0).join('.') : variableSelector.slice(1).join('.'))
|
||||
const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator
|
||||
const notHasValue = comparisonOperatorNotRequireValue(operator)
|
||||
const isEnvVar = isENV(variableSelector)
|
||||
const isChatVar = isConversationVar(variableSelector)
|
||||
const formatValue = useMemo(() => {
|
||||
if (notHasValue)
|
||||
return ''
|
||||
|
||||
if (Array.isArray(value)) // transfer method
|
||||
return value[0]
|
||||
|
||||
return value.replace(/{{#([^#]*)#}}/g, (a, b) => {
|
||||
const arr: string[] = b.split('.')
|
||||
if (isSystemVar(arr))
|
||||
return `{{${b}}}`
|
||||
|
||||
return `{{${arr.slice(1).join('.')}}}`
|
||||
})
|
||||
}, [notHasValue, value])
|
||||
|
||||
const isSelect = operator === ComparisonOperator.in || operator === ComparisonOperator.notIn
|
||||
const selectName = useMemo(() => {
|
||||
if (isSelect) {
|
||||
const name = [...FILE_TYPE_OPTIONS, ...TRANSFER_METHOD].filter(item => item.value === (Array.isArray(value) ? value[0] : value))[0]
|
||||
return name
|
||||
? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/{{#([^#]*)#}}/g, (a, b) => {
|
||||
const arr: string[] = b.split('.')
|
||||
if (isSystemVar(arr))
|
||||
return `{{${b}}}`
|
||||
|
||||
return `{{${arr.slice(1).join('.')}}}`
|
||||
})
|
||||
: ''
|
||||
}
|
||||
return ''
|
||||
}, [isSelect, t, value])
|
||||
|
||||
return (
|
||||
<div className='flex items-center px-1 h-6 rounded-md bg-workflow-block-parma-bg'>
|
||||
{!isEnvVar && !isChatVar && <Variable02 className='shrink-0 mr-1 w-3.5 h-3.5 text-text-accent' />}
|
||||
{isEnvVar && <Env className='shrink-0 mr-1 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(
|
||||
'shrink-0 truncate text-xs font-medium text-text-accent',
|
||||
!notHasValue && 'max-w-[70px]',
|
||||
)}
|
||||
title={variableName}
|
||||
>
|
||||
{variableName}
|
||||
</div>
|
||||
<div
|
||||
className='shrink-0 mx-1 text-xs font-medium text-text-primary'
|
||||
title={operatorName}
|
||||
>
|
||||
{operatorName}
|
||||
</div>
|
||||
{
|
||||
!notHasValue && (
|
||||
<div className='truncate text-xs text-text-secondary' title={formatValue}>{isSelect ? selectName : formatValue}</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ConditionValue)
|
@@ -0,0 +1,149 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiAddLine,
|
||||
} from '@remixicon/react'
|
||||
import type { Condition, HandleAddCondition, HandleAddSubVariableCondition, HandleRemoveCondition, HandleToggleConditionLogicalOperator, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition, LogicalOperator, handleRemoveSubVariableCondition } from '../types'
|
||||
import type { Node, NodeOutPutVar, Var } from '../../../types'
|
||||
import { VarType } from '../../../types'
|
||||
import { useGetAvailableVars } from '../../variable-assigner/hooks'
|
||||
import ConditionList from './condition-list'
|
||||
import ConditionAdd from './condition-add'
|
||||
import { SUB_VARIABLES } from './../default'
|
||||
import cn from '@/utils/classnames'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { PortalSelect as Select } from '@/app/components/base/select'
|
||||
|
||||
type Props = {
|
||||
isSubVariable?: boolean
|
||||
conditionId?: string
|
||||
conditions: Condition[]
|
||||
logicalOperator: LogicalOperator | undefined
|
||||
readOnly: boolean
|
||||
handleAddCondition?: HandleAddCondition
|
||||
handleRemoveCondition?: HandleRemoveCondition
|
||||
handleUpdateCondition?: HandleUpdateCondition
|
||||
handleToggleConditionLogicalOperator?: HandleToggleConditionLogicalOperator
|
||||
handleAddSubVariableCondition?: HandleAddSubVariableCondition
|
||||
handleRemoveSubVariableCondition?: handleRemoveSubVariableCondition
|
||||
handleUpdateSubVariableCondition?: HandleUpdateSubVariableCondition
|
||||
handleToggleSubVariableConditionLogicalOperator?: HandleToggleSubVariableConditionLogicalOperator
|
||||
nodeId: string
|
||||
availableNodes: Node[]
|
||||
availableVars: NodeOutPutVar[]
|
||||
}
|
||||
|
||||
const ConditionWrap: FC<Props> = ({
|
||||
isSubVariable,
|
||||
conditionId,
|
||||
conditions,
|
||||
logicalOperator,
|
||||
nodeId: id = '',
|
||||
readOnly,
|
||||
handleUpdateCondition,
|
||||
handleAddCondition,
|
||||
handleRemoveCondition,
|
||||
handleToggleConditionLogicalOperator,
|
||||
handleAddSubVariableCondition,
|
||||
handleRemoveSubVariableCondition,
|
||||
handleUpdateSubVariableCondition,
|
||||
handleToggleSubVariableConditionLogicalOperator,
|
||||
availableNodes = [],
|
||||
availableVars = [],
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const getAvailableVars = useGetAvailableVars()
|
||||
|
||||
const filterNumberVar = useCallback((varPayload: Var) => {
|
||||
return varPayload.type === VarType.number
|
||||
}, [])
|
||||
|
||||
const subVarOptions = SUB_VARIABLES.map(item => ({
|
||||
name: item,
|
||||
value: item,
|
||||
}))
|
||||
|
||||
if (!conditions)
|
||||
return <div />
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div
|
||||
className={cn(
|
||||
'group relative rounded-[10px] bg-components-panel-bg',
|
||||
!isSubVariable && 'py-1 px-3 min-h-[40px] ',
|
||||
isSubVariable && 'px-1 py-2',
|
||||
)}
|
||||
>
|
||||
{
|
||||
conditions && !!conditions.length && (
|
||||
<div className='mb-2'>
|
||||
<ConditionList
|
||||
disabled={readOnly}
|
||||
conditionId={conditionId}
|
||||
conditions={conditions}
|
||||
logicalOperator={logicalOperator}
|
||||
onUpdateCondition={handleUpdateCondition}
|
||||
onRemoveCondition={handleRemoveCondition}
|
||||
onToggleConditionLogicalOperator={handleToggleConditionLogicalOperator}
|
||||
nodeId={id}
|
||||
availableNodes={availableNodes}
|
||||
numberVariables={getAvailableVars(id, '', filterNumberVar)}
|
||||
onAddSubVariableCondition={handleAddSubVariableCondition}
|
||||
onRemoveSubVariableCondition={handleRemoveSubVariableCondition}
|
||||
onUpdateSubVariableCondition={handleUpdateSubVariableCondition}
|
||||
onToggleSubVariableConditionLogicalOperator={handleToggleSubVariableConditionLogicalOperator}
|
||||
isSubVariable={isSubVariable}
|
||||
availableVars={availableVars}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div className={cn(
|
||||
'flex items-center justify-between pr-[30px]',
|
||||
!conditions.length && !isSubVariable && 'mt-1',
|
||||
!conditions.length && isSubVariable && 'mt-2',
|
||||
conditions.length > 1 && !isSubVariable && 'ml-[60px]',
|
||||
)}>
|
||||
{isSubVariable
|
||||
? (
|
||||
<Select
|
||||
popupInnerClassName='w-[165px] max-h-none'
|
||||
onSelect={value => handleAddSubVariableCondition?.(conditionId!, value.value as string)}
|
||||
items={subVarOptions}
|
||||
value=''
|
||||
renderTrigger={() => (
|
||||
<Button
|
||||
size='small'
|
||||
disabled={readOnly}
|
||||
>
|
||||
<RiAddLine className='mr-1 w-3.5 h-3.5' />
|
||||
{t('workflow.nodes.ifElse.addSubVariable')}
|
||||
</Button>
|
||||
)}
|
||||
hideChecked
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<ConditionAdd
|
||||
disabled={readOnly}
|
||||
variables={availableVars}
|
||||
onSelectVariable={handleAddCondition!}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!isSubVariable && (
|
||||
<div className='my-2 mx-3 h-[1px] bg-divider-subtle'></div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ConditionWrap)
|
Reference in New Issue
Block a user