pull/20589/merge
kenwoodjw 10 months ago committed by GitHub
commit 192d787a47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -158,6 +158,9 @@ SupportedComparisonOperator = Literal[
# for time
"before",
"after",
# for array operations
"in",
"not in",
]
@ -175,7 +178,7 @@ class Condition(BaseModel):
name: str
comparison_operator: SupportedComparisonOperator
value: str | Sequence[str] | None | int | float = None
value: str | Sequence[str] | Sequence[int] | Sequence[float] | None | int | float = None
class MetadataFilteringCondition(BaseModel):

@ -13,6 +13,8 @@ SupportedComparisonOperator = Literal[
"is not",
"empty",
"not empty",
"in",
"not in",
# for number
"=",
"",
@ -33,7 +35,7 @@ class Condition(BaseModel):
name: str
comparison_operator: SupportedComparisonOperator
value: str | Sequence[str] | None | int | float = None
value: str | Sequence[str] | Sequence[int] | Sequence[float] | None | int | float = None
class MetadataCondition(BaseModel):

@ -1051,6 +1051,62 @@ class DatasetRetrieval:
filters.append(DatasetDocument.doc_metadata[metadata_name] != f'"{value}"')
else:
filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) != value)
case "in":
if isinstance(value, list | tuple):
if not value:
return filters
or_conditions = []
for i, v in enumerate(value):
if isinstance(v, str):
or_conditions.append(DatasetDocument.doc_metadata[metadata_name] == f'"{v}"')
elif isinstance(v, int | float):
or_conditions.append(
sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) == v
)
or_conditions.append(DatasetDocument.doc_metadata[metadata_name] == str(v))
else:
v_str = str(v)
or_conditions.append(DatasetDocument.doc_metadata[metadata_name] == f'"{v_str}"')
if or_conditions:
filters.append(or_(*or_conditions))
else:
# Single value case (backward compatibility)
if isinstance(value, str):
filters.append(DatasetDocument.doc_metadata[metadata_name] == f'"{value}"')
else:
filters.append(
sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) == value
)
case "not in":
if isinstance(value, list | tuple):
if not value:
return filters
and_conditions = []
for i, v in enumerate(value):
if isinstance(v, str):
and_conditions.append(DatasetDocument.doc_metadata[metadata_name] != f'"{v}"')
elif isinstance(v, int | float):
and_conditions.append(
sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) != v
)
and_conditions.append(DatasetDocument.doc_metadata[metadata_name] != str(v))
else:
v_str = str(v)
and_conditions.append(DatasetDocument.doc_metadata[metadata_name] != f'"{v_str}"')
if and_conditions:
filters.append(and_(*and_conditions))
else:
# Single value case (backward compatibility)
if isinstance(value, str):
filters.append(DatasetDocument.doc_metadata[metadata_name] != f'"{value}"')
else:
filters.append(
sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) != value
)
case "empty":
filters.append(DatasetDocument.doc_metadata[metadata_name].is_(None))
case "not empty":

@ -85,6 +85,8 @@ SupportedComparisonOperator = Literal[
"is not",
"empty",
"not empty",
"in",
"not in",
# for number
"=",
"",
@ -105,7 +107,7 @@ class Condition(BaseModel):
name: str
comparison_operator: SupportedComparisonOperator
value: str | Sequence[str] | None | int | float = None
value: str | Sequence[str] | Sequence[int] | Sequence[float] | None | int | float = None
class MetadataFilteringCondition(BaseModel):

@ -383,12 +383,23 @@ class KnowledgeRetrievalNode(LLMNode):
expected_value = self.graph_runtime_state.variable_pool.convert_template(
expected_value
).value[0]
if expected_value.value_type == "number": # type: ignore
expected_value = expected_value.value # type: ignore
elif expected_value.value_type == "string": # type: ignore
expected_value = re.sub(r"[\r\n\t]+", " ", expected_value.text).strip() # type: ignore
else:
raise ValueError("Invalid expected metadata value type")
if hasattr(expected_value, "value_type") and expected_value is not None:
if expected_value.value_type == "number": # type: ignore
expected_value = expected_value.value # type: ignore
elif expected_value.value_type == "string": # type: ignore
expected_value = re.sub(r"[\r\n\t]+", " ", expected_value.text).strip() # type: ignore
elif expected_value.value_type in ( # type: ignore
"array[number]",
"array[string]",
"array[object]",
"array",
):
expected_value = expected_value.value # type: ignore
else:
raise ValueError("Invalid expected metadata value type")
elif isinstance(expected_value, list):
# For constant array values
pass
conditions.append(
Condition(
name=metadata_name,
@ -530,6 +541,60 @@ class KnowledgeRetrievalNode(LLMNode):
filters.append(Document.doc_metadata[metadata_name] != f'"{value}"')
else:
filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) != value)
case "in":
if isinstance(value, list | tuple):
if not value:
return filters
# Generate matching conditions for each value, supporting both number and string matching
or_conditions = []
for i, v in enumerate(value):
if isinstance(v, str):
or_conditions.append(Document.doc_metadata[metadata_name] == f'"{v}"')
elif isinstance(v, int | float):
or_conditions.append(
sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) == v
)
or_conditions.append(Document.doc_metadata[metadata_name] == str(v))
else:
v_str = str(v)
or_conditions.append(Document.doc_metadata[metadata_name] == f'"{v_str}"')
if or_conditions:
filters.append(or_(*or_conditions))
else:
# Single value case (backward compatibility)
if isinstance(value, str):
filters.append(Document.doc_metadata[metadata_name] == f'"{value}"')
else:
filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) == value)
case "not in":
if isinstance(value, list | tuple):
if not value:
return filters
# generate not in conditions
and_conditions = []
for i, v in enumerate(value):
if isinstance(v, str):
and_conditions.append(Document.doc_metadata[metadata_name] != f'"{v}"')
elif isinstance(v, int | float):
and_conditions.append(
sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) != v
)
and_conditions.append(Document.doc_metadata[metadata_name] != str(v))
else:
v_str = str(v)
and_conditions.append(Document.doc_metadata[metadata_name] != f'"{v_str}"')
if and_conditions:
filters.append(and_(*and_conditions))
else:
# Single value case (backward compatibility)
if isinstance(value, str):
filters.append(Document.doc_metadata[metadata_name] != f'"{value}"')
else:
filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) != value)
case "empty":
filters.append(Document.doc_metadata[metadata_name].is_(None))
case "not empty":

@ -34,7 +34,7 @@ SupportedComparisonOperator = Literal[
class SubCondition(BaseModel):
key: str
comparison_operator: SupportedComparisonOperator
value: str | Sequence[str] | None = None
value: str | Sequence[str] | Sequence[int] | Sequence[float] | None | int | float = None
class SubVariableCondition(BaseModel):
@ -45,5 +45,5 @@ class SubVariableCondition(BaseModel):
class Condition(BaseModel):
variable_selector: list[str]
comparison_operator: SupportedComparisonOperator
value: str | Sequence[str] | None = None
value: str | Sequence[str] | Sequence[int] | Sequence[float] | None | int | float = None
sub_variable_condition: SubVariableCondition | None = None

@ -77,7 +77,7 @@ def _evaluate_condition(
*,
operator: SupportedComparisonOperator,
value: Any,
expected: str | Sequence[str] | None,
expected: str | Sequence[str] | Sequence[int] | Sequence[float] | int | float | None,
) -> bool:
match operator:
case "contains":

@ -272,6 +272,7 @@ const DatasetConfig: FC = () => {
isCommonVariable
availableCommonStringVars={promptVariablesToSelect.filter(item => item.type === MetadataFilteringVariableType.string || item.type === MetadataFilteringVariableType.select)}
availableCommonNumberVars={promptVariablesToSelect.filter(item => item.type === MetadataFilteringVariableType.number)}
availableCommonArrayVars={promptVariablesToSelect.filter(item => item.type === MetadataFilteringVariableType.array || item.type.startsWith('array'))}
/>
</div>

@ -58,7 +58,7 @@ const CreateContent: FC<Props> = ({
>
<div className='space-y-3'>
<Field label={t(`${i18nPrefix}.type`)}>
<div className='grid grid-cols-3 gap-2'>
<div className='grid grid-cols-4 gap-2'>
<OptionCard
title='String'
selected={type === DataType.string}
@ -74,6 +74,11 @@ const CreateContent: FC<Props> = ({
selected={type === DataType.time}
onSelect={handleTypeChange(DataType.time)}
/>
<OptionCard
title='Array'
selected={type === DataType.array}
onSelect={handleTypeChange(DataType.array)}
/>
</div>
</Field>
<Field label={t(`${i18nPrefix}.name`)}>

@ -2,6 +2,7 @@ export enum DataType {
string = 'string',
number = 'number',
time = 'time',
array = 'array',
}
export type BuiltInMetadataItem = {

@ -0,0 +1,237 @@
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import ConditionValueMethod from './condition-value-method'
import type { ConditionValueMethodProps } from './condition-value-method'
import ConditionVariableSelector from './condition-variable-selector'
import ConditionCommonVariableSelector from './condition-common-variable-selector'
import type {
Node,
NodeOutPutVar,
ValueSelector,
} from '@/app/components/workflow/types'
import { VarType } from '@/app/components/workflow/types'
import Input from '@/app/components/base/input'
import type { MetadataFilteringVariableType } from '@/app/components/workflow/nodes/knowledge-retrieval/types'
type ConditionArrayProps = {
value?: string | string[] | (string | number)[] | number
onChange: (value?: string | string[] | (string | number)[]) => void
nodesOutputVars: NodeOutPutVar[]
availableNodes: Node[]
isCommonVariable?: boolean
commonVariables: { name: string, type: string }[]
fieldType?: MetadataFilteringVariableType
strictTypeChecking?: boolean
} & ConditionValueMethodProps
const ConditionArray = ({
value,
onChange,
valueMethod = 'constant',
onValueMethodChange,
nodesOutputVars,
availableNodes,
isCommonVariable,
commonVariables,
fieldType,
strictTypeChecking = false,
}: ConditionArrayProps) => {
const { t } = useTranslation()
const parseValueSelector = useCallback((value?: string | string[] | (string | number)[] | number): string[] => {
if (typeof value !== 'string')
return []
// Support multiple formats:
// 1. {{#nodeId.variable#}} format
if (value.includes('#')) {
const match = value.match(/\{\{#([^#]+)#\}\}/)
if (match && match[1])
return match[1].split('.')
}
// 2. nodeId.variable format (direct format)
if (value.includes('.'))
return value.split('.')
return []
}, [])
const currentValueSelector = parseValueSelector(value)
const handleVariableValueChange = useCallback((v: ValueSelector) => {
onChange(`{{#${v.join('.')}#}}`)
}, [onChange])
const handleCommonVariableValueChange = useCallback((v: string) => {
onChange(`{{${v}}}`)
}, [onChange])
// Type compatibility check
const checkTypeCompatibility = useCallback((selectedVariable: any) => {
if (!fieldType || !selectedVariable) return null
// Get variable type
const variableType = selectedVariable.type || selectedVariable.value_type
// Define compatibility rules
const compatibilityRules: Record<string, string[]> = {
string: ['array[string]', 'array', 'string'],
number: ['array[number]', 'array', 'number'],
select: ['array[string]', 'array', 'string'],
array: ['array[string]', 'array[number]', 'array[object]', 'array'],
time: ['array[string]', 'array', 'string'], // Time field compatibility
}
const compatibleTypes = compatibilityRules[fieldType as string] || []
if (!compatibleTypes.includes(variableType)) {
return {
warning: true,
message: `⚠️ Type mismatch: ${fieldType} field is not recommended to use ${variableType} type variables`,
}
}
return null
}, [fieldType])
// Check if currently selected variable is compatible
const typeCompatibilityCheck = useMemo(() => {
if (valueMethod === 'variable' && currentValueSelector.length > 0) {
// Find currently selected variable information
const selectedVar = nodesOutputVars.find(nodeVar =>
nodeVar.nodeId === currentValueSelector[0],
)?.vars.find(v => v.variable === currentValueSelector[1])
return checkTypeCompatibility(selectedVar)
}
return null
}, [valueMethod, currentValueSelector, nodesOutputVars, checkTypeCompatibility])
const handleConstantValueChange = useCallback((inputValue: string) => {
// Parse comma-separated values into array
if (inputValue.trim() === '') {
onChange([])
return
}
// Split by comma and trim whitespace
const arrayValues = inputValue.split(',').map((item) => {
const trimmed = item.trim()
if (trimmed === '') return null
// Keep natural type detection: pure numbers auto-convert to numbers, otherwise keep as strings
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
const numericValue = Number(trimmed)
if (!isNaN(numericValue) && isFinite(numericValue))
return numericValue
}
// Remove quotes (if any) and keep as string
return trimmed.replace(/^["']|["']$/g, '')
}).filter(item => item !== null)
onChange(arrayValues)
}, [onChange])
const displayValue = Array.isArray(value) ? value.map(v => String(v)).join(', ') : (value || '')
// Filter variables based on strict mode
const filteredNodesOutputVars = useMemo(() => {
const basicFilter = nodesOutputVars.filter(nodeVar =>
nodeVar.vars.some(v =>
v.type === VarType.arrayString
|| v.type === VarType.arrayNumber
|| v.type === VarType.arrayObject
|| v.type === VarType.arrayFile
|| v.type === VarType.array
|| v.type.toString().startsWith('array'),
),
)
if (!strictTypeChecking || !fieldType)
return basicFilter
// Strict mode: only show type-compatible variables
return basicFilter.map(nodeVar => ({
...nodeVar,
vars: nodeVar.vars.filter((v) => {
const typeCheck = checkTypeCompatibility(v)
return !typeCheck?.warning
}),
})).filter(nodeVar => nodeVar.vars.length > 0)
}, [nodesOutputVars, strictTypeChecking, fieldType, checkTypeCompatibility])
const filteredCommonVariables = useMemo(() => {
const basicFilter = commonVariables.filter(v =>
v.type === 'array'
|| v.type.startsWith('array'),
)
if (!strictTypeChecking || !fieldType)
return basicFilter
// Strict mode: only show type-compatible variables
return basicFilter.filter((v) => {
const typeCheck = checkTypeCompatibility(v)
return !typeCheck?.warning
})
}, [commonVariables, strictTypeChecking, fieldType, checkTypeCompatibility])
return (
<div className='flex h-8 items-center pl-1 pr-2'>
<ConditionValueMethod
valueMethod={valueMethod}
onValueMethodChange={onValueMethodChange}
/>
<div className='ml-1 mr-1.5 h-4 w-[1px] bg-divider-regular'></div>
{
valueMethod === 'variable' && !isCommonVariable && (
<div className='flex-1'>
<ConditionVariableSelector
valueSelector={currentValueSelector}
onChange={handleVariableValueChange}
nodesOutputVars={filteredNodesOutputVars}
availableNodes={availableNodes}
varType='array'
/>
{typeCompatibilityCheck?.warning && (
<div className='mt-1 text-xs text-text-warning'>
{typeCompatibilityCheck.message}
</div>
)}
</div>
)
}
{
valueMethod === 'variable' && isCommonVariable && (
<div className='flex-1'>
<ConditionCommonVariableSelector
variables={filteredCommonVariables}
value={typeof value === 'string' ? value : ''}
onChange={handleCommonVariableValueChange}
varType={VarType.array}
/>
{typeCompatibilityCheck?.warning && (
<div className='mt-1 text-xs text-text-warning'>
{typeCompatibilityCheck.message}
</div>
)}
</div>
)
}
{
valueMethod === 'constant' && (
<Input
className='border-none bg-transparent outline-none hover:bg-transparent focus:bg-transparent focus:shadow-none'
value={displayValue}
onChange={e => handleConstantValueChange(e.target.value)}
placeholder={t('workflow.nodes.knowledgeRetrieval.metadata.panel.arrayPlaceholder') || 'Enter comma-separated values'}
/>
)
}
</div>
)
}
export default ConditionArray

@ -24,6 +24,11 @@ const ConditionCommonVariableSelector = ({
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const filteredVariables = variables.filter((v) => {
const isArrayType = v.type === 'array' || v.type.startsWith('array')
return v.type === varType || isArrayType
})
const selected = variables.find(v => v.name === value)
const handleChange = useCallback((v: string) => {
onChange(v)
@ -41,7 +46,7 @@ const ConditionCommonVariableSelector = ({
}}
>
<PortalToFollowElemTrigger asChild onClick={() => {
if (!variables.length) return
if (!filteredVariables.length) return
setOpen(!open)
}}>
<div className="flex h-6 grow cursor-pointer items-center">
@ -71,16 +76,22 @@ const ConditionCommonVariableSelector = ({
<PortalToFollowElemContent className='z-[1000]'>
<div className='w-[200px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
{
variables.map(v => (
<div
key={v.name}
className='system-xs-medium flex h-6 cursor-pointer items-center rounded-md px-2 text-text-secondary hover:bg-state-base-hover'
onClick={() => handleChange(v.name)}
>
<Variable02 className='mr-1 h-4 w-4 text-text-accent' />
{v.name}
filteredVariables.length > 0 ? (
filteredVariables.map(v => (
<div
key={v.name}
className='system-xs-medium flex h-6 cursor-pointer items-center rounded-md px-2 text-text-secondary hover:bg-state-base-hover'
onClick={() => handleChange(v.name)}
>
<Variable02 className='mr-1 h-4 w-4 text-text-accent' />
{v.name}
</div>
))
) : (
<div className='system-xs-medium flex h-6 items-center px-2 text-text-tertiary'>
{t('workflow.nodes.knowledgeRetrieval.metadata.panel.noVariables')}
</div>
))
)
}
</div>
</PortalToFollowElemContent>

@ -16,14 +16,14 @@ import ConditionOperator from './condition-operator'
import ConditionString from './condition-string'
import ConditionNumber from './condition-number'
import ConditionDate from './condition-date'
import ConditionArray from './condition-array'
import type {
ComparisonOperator,
HandleRemoveCondition,
HandleUpdateCondition,
MetadataFilteringCondition,
MetadataShape,
} from '@/app/components/workflow/nodes/knowledge-retrieval/types'
import { MetadataFilteringVariableType } from '@/app/components/workflow/nodes/knowledge-retrieval/types'
import { ComparisonOperator, MetadataFilteringVariableType } from '@/app/components/workflow/nodes/knowledge-retrieval/types'
import cn from '@/utils/classnames'
type ConditionItemProps = {
@ -32,7 +32,7 @@ type ConditionItemProps = {
condition: MetadataFilteringCondition // condition may the condition of case or condition of sub variable
onRemoveCondition?: HandleRemoveCondition
onUpdateCondition?: HandleUpdateCondition
} & Pick<MetadataShape, 'metadataList' | 'availableStringVars' | 'availableStringNodesWithParent' | 'availableNumberVars' | 'availableNumberNodesWithParent' | 'isCommonVariable' | 'availableCommonStringVars' | 'availableCommonNumberVars'>
} & Pick<MetadataShape, 'metadataList' | 'availableStringVars' | 'availableStringNodesWithParent' | 'availableNumberVars' | 'availableNumberNodesWithParent' | 'availableArrayVars' | 'availableArrayNodesWithParent' | 'isCommonVariable' | 'availableCommonStringVars' | 'availableCommonNumberVars' | 'availableCommonArrayVars'>
const ConditionItem = ({
className,
disabled,
@ -44,9 +44,12 @@ const ConditionItem = ({
availableStringNodesWithParent = [],
availableNumberVars = [],
availableNumberNodesWithParent = [],
availableArrayVars = [],
availableArrayNodesWithParent = [],
isCommonVariable,
availableCommonStringVars = [],
availableCommonNumberVars = [],
availableCommonArrayVars = [],
}: ConditionItemProps) => {
const [isHovered, setIsHovered] = useState(false)
@ -100,6 +103,35 @@ const ConditionItem = ({
}
}
// For array type, handle both string and array values
if (currentMetadata?.type === MetadataFilteringVariableType.array) {
if (typeof condition.value === 'string') {
const regex = isCommonVariable ? COMMON_VARIABLE_REGEX : VARIABLE_REGEX
const matchedStartNumber = isCommonVariable ? 2 : 3
const matched = condition.value.match(regex)
if (matched?.length) {
return {
value: matched[0].slice(matchedStartNumber, -matchedStartNumber),
valueMethod: 'variable',
}
}
else {
return {
value: condition.value,
valueMethod: 'constant',
}
}
}
else {
// Array value
return {
value: condition.value,
valueMethod: 'constant',
}
}
}
return {
value: condition.value,
valueMethod: 'constant',
@ -144,7 +176,8 @@ const ConditionItem = ({
{
!comparisonOperatorNotRequireValue(condition.comparison_operator)
&& (currentMetadata?.type === MetadataFilteringVariableType.string
|| currentMetadata?.type === MetadataFilteringVariableType.select) && (
|| currentMetadata?.type === MetadataFilteringVariableType.select)
&& ![ComparisonOperator.in, ComparisonOperator.notIn].includes(condition.comparison_operator) && (
<ConditionString
valueMethod={localValueMethod}
onValueMethodChange={handleValueMethodChange}
@ -158,7 +191,8 @@ const ConditionItem = ({
)
}
{
!comparisonOperatorNotRequireValue(condition.comparison_operator) && currentMetadata?.type === MetadataFilteringVariableType.number && (
!comparisonOperatorNotRequireValue(condition.comparison_operator) && currentMetadata?.type === MetadataFilteringVariableType.number
&& ![ComparisonOperator.in, ComparisonOperator.notIn].includes(condition.comparison_operator) && (
<ConditionNumber
valueMethod={localValueMethod}
onValueMethodChange={handleValueMethodChange}
@ -171,6 +205,24 @@ const ConditionItem = ({
/>
)
}
{
!comparisonOperatorNotRequireValue(condition.comparison_operator)
&& ([ComparisonOperator.in, ComparisonOperator.notIn].includes(condition.comparison_operator)
|| currentMetadata?.type === MetadataFilteringVariableType.array) && (
<ConditionArray
valueMethod={localValueMethod}
onValueMethodChange={handleValueMethodChange}
nodesOutputVars={availableArrayVars}
availableNodes={availableArrayNodesWithParent}
value={valueAndValueMethod.value}
onChange={handleValueChange}
isCommonVariable={isCommonVariable}
commonVariables={availableCommonArrayVars}
fieldType={currentMetadata?.type}
strictTypeChecking={false}
/>
)
}
{
!comparisonOperatorNotRequireValue(condition.comparison_operator) && currentMetadata?.type === MetadataFilteringVariableType.time && (
<ConditionDate

@ -16,9 +16,9 @@ import {
} from '@/app/components/base/portal-to-follow-elem'
import cn from '@/utils/classnames'
import type {
ComparisonOperator,
MetadataFilteringVariableType,
} from '@/app/components/workflow/nodes/knowledge-retrieval/types'
import type { ComparisonOperator } from '@/app/components/workflow/nodes/knowledge-retrieval/types'
const i18nPrefix = 'workflow.nodes.ifElse'

@ -18,7 +18,7 @@ import { Variable02 } from '@/app/components/base/icons/src/vender/solid/develop
type ConditionVariableSelectorProps = {
valueSelector?: ValueSelector
varType?: VarType
varType?: VarType | string
availableNodes?: Node[]
nodesOutputVars?: NodeOutPutVar[]
onChange: (valueSelector: ValueSelector, varItem: Var) => void
@ -34,11 +34,22 @@ const ConditionVariableSelector = ({
const { t } = useTranslation()
const [open, setOpen] = useState(false)
// 添加调试日志
console.log('🔍 ConditionVariableSelector Debug:')
console.log(' - varType:', varType)
console.log(' - nodesOutputVars:', nodesOutputVars)
console.log(' - availableNodes:', availableNodes)
const handleChange = useCallback((valueSelector: ValueSelector, varItem: Var) => {
onChange(valueSelector, varItem)
setOpen(false)
}, [onChange])
const isArrayType = varType === 'array' || varType === VarType.array
|| varType === VarType.arrayString || varType === VarType.arrayNumber
|| varType === VarType.arrayObject || varType === VarType.arrayFile
|| (typeof varType === 'string' && varType.startsWith('array'))
return (
<PortalToFollowElem
open={open}
@ -55,7 +66,7 @@ const ConditionVariableSelector = ({
!!valueSelector.length && (
<VariableTag
valueSelector={valueSelector}
varType={varType}
varType={varType as VarType}
availableNodes={availableNodes}
isShort
/>
@ -69,7 +80,7 @@ const ConditionVariableSelector = ({
{t('workflow.nodes.knowledgeRetrieval.metadata.panel.select')}
</div>
<div className='system-2xs-medium flex h-5 shrink-0 items-center rounded-[5px] border border-divider-deep px-[5px] text-text-tertiary'>
{varType}
{isArrayType ? 'array' : varType}
</div>
</>
)
@ -82,6 +93,15 @@ const ConditionVariableSelector = ({
vars={nodesOutputVars}
isSupportFileVar
onChange={handleChange}
filterVar={(varPayload) => {
// If varType is array-related, filter for all array types
if (isArrayType) {
return [VarType.arrayString, VarType.arrayNumber, VarType.arrayObject, VarType.arrayFile, VarType.array].includes(varPayload.type)
|| varPayload.type.toString().startsWith('array')
}
// For other types, use exact match
return varPayload.type === varType
}}
/>
</div>
</PortalToFollowElemContent>

@ -22,9 +22,12 @@ const ConditionList = ({
availableStringNodesWithParent,
availableNumberVars,
availableNumberNodesWithParent,
availableArrayVars,
availableArrayNodesWithParent,
isCommonVariable,
availableCommonNumberVars,
availableCommonStringVars,
availableCommonArrayVars,
}: ConditionListProps) => {
const { conditions, logical_operator } = metadataFilteringConditions
@ -61,9 +64,12 @@ const ConditionList = ({
availableStringNodesWithParent={availableStringNodesWithParent}
availableNumberVars={availableNumberVars}
availableNumberNodesWithParent={availableNumberNodesWithParent}
availableArrayVars={availableArrayVars}
availableArrayNodesWithParent={availableArrayNodesWithParent}
isCommonVariable={isCommonVariable}
availableCommonStringVars={availableCommonStringVars}
availableCommonNumberVars={availableCommonNumberVars}
availableCommonArrayVars={availableCommonArrayVars}
/>
))
}

@ -30,6 +30,8 @@ export const getOperators = (type?: MetadataFilteringVariableType) => {
ComparisonOperator.notContains,
ComparisonOperator.startWith,
ComparisonOperator.endWith,
ComparisonOperator.in,
ComparisonOperator.notIn,
ComparisonOperator.empty,
ComparisonOperator.notEmpty,
]
@ -41,6 +43,17 @@ export const getOperators = (type?: MetadataFilteringVariableType) => {
ComparisonOperator.lessThan,
ComparisonOperator.largerThanOrEqual,
ComparisonOperator.lessThanOrEqual,
ComparisonOperator.in,
ComparisonOperator.notIn,
ComparisonOperator.empty,
ComparisonOperator.notEmpty,
]
case MetadataFilteringVariableType.array:
return [
ComparisonOperator.in,
ComparisonOperator.notIn,
ComparisonOperator.contains,
ComparisonOperator.notContains,
ComparisonOperator.empty,
ComparisonOperator.notEmpty,
]

@ -1,6 +1,7 @@
import { memo } from 'react'
import {
RiHashtag,
RiListUnordered,
RiTextSnippet,
RiTimeLine,
} from '@remixicon/react'
@ -32,6 +33,11 @@ const MetadataIcon = ({
<RiTimeLine className={cn('h-3.5 w-3.5', className)} />
)
}
{
type === MetadataFilteringVariableType.array && (
<RiListUnordered className={cn('h-3.5 w-3.5', className)} />
)
}
</>
)
}

@ -51,6 +51,8 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({
availableStringNodesWithParent,
availableNumberVars,
availableNumberNodesWithParent,
availableArrayVars,
availableArrayNodesWithParent,
} = useConfig(id, data)
const handleOpenFromPropsChange = useCallback((openFromProps: boolean) => {
@ -139,6 +141,8 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({
availableStringNodesWithParent={availableStringNodesWithParent}
availableNumberVars={availableNumberVars}
availableNumberNodesWithParent={availableNumberNodesWithParent}
availableArrayVars={availableArrayVars}
availableArrayNodesWithParent={availableArrayNodesWithParent}
/>
</div>
<Split />

@ -81,13 +81,14 @@ export enum MetadataFilteringVariableType {
number = 'number',
time = 'time',
select = 'select',
array = 'array',
}
export type MetadataFilteringCondition = {
id: string
name: string
comparison_operator: ComparisonOperator
value?: string | number
value?: string | number | string[]
}
export type MetadataFilteringConditions = {
@ -127,7 +128,10 @@ export type MetadataShape = {
availableStringNodesWithParent?: Node[]
availableNumberVars?: NodeOutPutVar[]
availableNumberNodesWithParent?: Node[]
availableArrayVars?: NodeOutPutVar[]
availableArrayNodesWithParent?: Node[]
isCommonVariable?: boolean
availableCommonStringVars?: { name: string; type: string; }[]
availableCommonNumberVars?: { name: string; type: string; }[]
availableCommonArrayVars?: { name: string; type: string; }[]
}

@ -290,6 +290,8 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
if (type === MetadataFilteringVariableType.number)
operator = ComparisonOperator.equal
else if (type === MetadataFilteringVariableType.array)
operator = ComparisonOperator.in
const newCondition = {
id: uuid4(),
@ -386,6 +388,20 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
filterVar: filterNumberVar,
})
const filterArrayVar = useCallback((varPayload: Var) => {
// 匹配所有数组类型array, array[string], array[number], array[object], array[file]
return [VarType.arrayString, VarType.arrayNumber, VarType.arrayObject, VarType.arrayFile, VarType.array].includes(varPayload.type)
|| varPayload.type.toString().startsWith('array')
}, [])
const {
availableVars: availableArrayVars,
availableNodesWithParent: availableArrayNodesWithParent,
} = useAvailableVarList(id, {
onlyLeafNodeVar: false,
filterVar: filterArrayVar,
})
return {
readOnly,
inputs,
@ -411,6 +427,8 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
availableStringNodesWithParent,
availableNumberVars,
availableNumberNodesWithParent,
availableArrayVars,
availableArrayNodesWithParent,
}
}

Loading…
Cancel
Save