feat: Support selecting variables in conditional filtering in list operations. (#23029)

Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: crazywoola <427733928@qq.com>
This commit is contained in:
呆萌闷油瓶
2025-07-28 13:59:34 +08:00
committed by GitHub
parent ee731c7810
commit f72c03a174
3 changed files with 61 additions and 14 deletions

View File

@@ -299,7 +299,7 @@ def _endswith(value: str) -> Callable[[str], bool]:
def _is(value: str) -> Callable[[str], bool]: def _is(value: str) -> Callable[[str], bool]:
return lambda x: x is value return lambda x: x == value
def _in(value: str | Sequence[str]) -> Callable[[str], bool]: def _in(value: str | Sequence[str]) -> Callable[[str], bool]:

View File

@@ -1,36 +1,60 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useCallback, useMemo } from 'react' import React, { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ConditionOperator from '../../if-else/components/condition-list/condition-operator' import ConditionOperator from '../../if-else/components/condition-list/condition-operator'
import { VarType } from '../../../types'
import type { Condition } from '../types' import type { Condition } from '../types'
import { ComparisonOperator } from '../../if-else/types' import { ComparisonOperator } from '../../if-else/types'
import { comparisonOperatorNotRequireValue, getOperators } from '../../if-else/utils' import { comparisonOperatorNotRequireValue, getOperators } from '../../if-else/utils'
import SubVariablePicker from './sub-variable-picker' import SubVariablePicker from './sub-variable-picker'
import Input from '@/app/components/base/input'
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '@/app/components/workflow/nodes/constants' import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '@/app/components/workflow/nodes/constants'
import { SimpleSelect as Select } from '@/app/components/base/select' import { SimpleSelect as Select } from '@/app/components/base/select'
import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
import cn from '@/utils/classnames'
import { VarType } from '../../../types'
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName' const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
const VAR_INPUT_SUPPORTED_KEYS: Record<string, VarType> = {
name: VarType.string,
url: VarType.string,
extension: VarType.string,
mime_type: VarType.string,
related_id: VarType.number,
}
type Props = { type Props = {
condition: Condition condition: Condition
onChange: (condition: Condition) => void onChange: (condition: Condition) => void
varType: VarType
hasSubVariable: boolean hasSubVariable: boolean
readOnly: boolean readOnly: boolean
nodeId: string
} }
const FilterCondition: FC<Props> = ({ const FilterCondition: FC<Props> = ({
condition = { key: '', comparison_operator: ComparisonOperator.equal, value: '' }, condition = { key: '', comparison_operator: ComparisonOperator.equal, value: '' },
varType,
onChange, onChange,
hasSubVariable, hasSubVariable,
readOnly, readOnly,
nodeId,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const [isFocus, setIsFocus] = useState(false)
const expectedVarType = VAR_INPUT_SUPPORTED_KEYS[condition.key]
const supportVariableInput = !!expectedVarType
const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, {
onlyLeafNodeVar: false,
filterVar: (varPayload) => {
return expectedVarType ? varPayload.type === expectedVarType : true
},
})
const isSelect = [ComparisonOperator.in, ComparisonOperator.notIn, ComparisonOperator.allOf].includes(condition.comparison_operator) const isSelect = [ComparisonOperator.in, ComparisonOperator.notIn, ComparisonOperator.allOf].includes(condition.comparison_operator)
const isArrayValue = condition.key === 'transfer_method' || condition.key === 'type' const isArrayValue = condition.key === 'transfer_method' || condition.key === 'type'
const selectOptions = useMemo(() => { const selectOptions = useMemo(() => {
if (isSelect) { if (isSelect) {
if (condition.key === 'type' || condition.comparison_operator === ComparisonOperator.allOf) { if (condition.key === 'type' || condition.comparison_operator === ComparisonOperator.allOf) {
@@ -49,6 +73,7 @@ const FilterCondition: FC<Props> = ({
} }
return [] return []
}, [condition.comparison_operator, condition.key, isSelect, t]) }, [condition.comparison_operator, condition.key, isSelect, t])
const handleChange = useCallback((key: string) => { const handleChange = useCallback((key: string) => {
return (value: any) => { return (value: any) => {
onChange({ onChange({
@@ -59,12 +84,14 @@ const FilterCondition: FC<Props> = ({
}, [condition, onChange, isArrayValue]) }, [condition, onChange, isArrayValue])
const handleSubVariableChange = useCallback((value: string) => { const handleSubVariableChange = useCallback((value: string) => {
const operators = getOperators(expectedVarType ?? VarType.string, { key: value })
const newOperator = operators.length > 0 ? operators[0] : ComparisonOperator.equal
onChange({ onChange({
key: value, key: value,
comparison_operator: getOperators(varType, { key: value })[0], comparison_operator: newOperator,
value: '', value: '',
}) })
}, [onChange, varType]) }, [onChange, expectedVarType])
return ( return (
<div> <div>
@@ -78,7 +105,7 @@ const FilterCondition: FC<Props> = ({
<div className='flex space-x-1'> <div className='flex space-x-1'>
<ConditionOperator <ConditionOperator
className='h-8 bg-components-input-bg-normal' className='h-8 bg-components-input-bg-normal'
varType={varType} varType={expectedVarType ?? VarType.string}
value={condition.comparison_operator} value={condition.comparison_operator}
onSelect={handleChange('comparison_operator')} onSelect={handleChange('comparison_operator')}
file={hasSubVariable ? { key: condition.key } : undefined} file={hasSubVariable ? { key: condition.key } : undefined}
@@ -86,7 +113,7 @@ const FilterCondition: FC<Props> = ({
/> />
{!comparisonOperatorNotRequireValue(condition.comparison_operator) && ( {!comparisonOperatorNotRequireValue(condition.comparison_operator) && (
<> <>
{isSelect && ( {isSelect ? (
<Select <Select
items={selectOptions} items={selectOptions}
defaultValue={isArrayValue ? (condition.value as string[])[0] : condition.value as string} defaultValue={isArrayValue ? (condition.value as string[])[0] : condition.value as string}
@@ -95,13 +122,31 @@ const FilterCondition: FC<Props> = ({
wrapperClassName='grow h-8' wrapperClassName='grow h-8'
placeholder='Select value' placeholder='Select value'
/> />
)} ) : supportVariableInput ? (
{!isSelect && (
<Input <Input
type={((hasSubVariable && condition.key === 'size') || (!hasSubVariable && varType === VarType.number)) ? 'number' : 'text'} instanceId='filter-condition-input'
className='grow' className={cn(
isFocus
? 'border-components-input-border-active bg-components-input-bg-active shadow-xs'
: 'border-components-input-border-hover bg-components-input-bg-normal',
'w-0 grow rounded-lg border px-3 py-[6px]',
)}
value={condition.value}
onChange={handleChange('value')}
readOnly={readOnly}
nodesOutputVars={availableVars}
availableNodes={availableNodesWithParent}
onFocusChange={setIsFocus}
placeholder={!readOnly ? t('workflow.nodes.http.extractListPlaceholder')! : ''}
placeholderClassName='!leading-[21px]'
/>
) : (
<input
type={(condition.key === 'size' || expectedVarType === VarType.number) ? 'number' : 'text'}
className='grow rounded-lg border border-components-input-border-hover bg-components-input-bg-normal px-3 py-[6px]'
value={condition.value} value={condition.value}
onChange={e => handleChange('value')(e.target.value)} onChange={e => handleChange('value')(e.target.value)}
readOnly={readOnly}
/> />
)} )}
</> </>
@@ -110,4 +155,5 @@ const FilterCondition: FC<Props> = ({
</div> </div>
) )
} }
export default React.memo(FilterCondition) export default React.memo(FilterCondition)

View File

@@ -78,6 +78,7 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({
varType={itemVarType} varType={itemVarType}
hasSubVariable={hasSubVariable} hasSubVariable={hasSubVariable}
readOnly={readOnly} readOnly={readOnly}
nodeId={id}
/> />
) )
: null} : null}