diff --git a/api/core/app/app_config/entities.py b/api/core/app/app_config/entities.py index 75bd2f677a..a82a3cd7b8 100644 --- a/api/core/app/app_config/entities.py +++ b/api/core/app/app_config/entities.py @@ -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): diff --git a/api/core/rag/entities/metadata_entities.py b/api/core/rag/entities/metadata_entities.py index 6ef932ad22..d8395c575e 100644 --- a/api/core/rag/entities/metadata_entities.py +++ b/api/core/rag/entities/metadata_entities.py @@ -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): diff --git a/api/core/rag/retrieval/dataset_retrieval.py b/api/core/rag/retrieval/dataset_retrieval.py index 3fca48be22..7048791a12 100644 --- a/api/core/rag/retrieval/dataset_retrieval.py +++ b/api/core/rag/retrieval/dataset_retrieval.py @@ -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": diff --git a/api/core/workflow/nodes/knowledge_retrieval/entities.py b/api/core/workflow/nodes/knowledge_retrieval/entities.py index 19bdee4fe2..34ff54ab47 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/entities.py +++ b/api/core/workflow/nodes/knowledge_retrieval/entities.py @@ -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): diff --git a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py index b34d62d669..99c6339de0 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py +++ b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py @@ -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": diff --git a/api/core/workflow/utils/condition/entities.py b/api/core/workflow/utils/condition/entities.py index 56871a15d8..00b4f6fc8e 100644 --- a/api/core/workflow/utils/condition/entities.py +++ b/api/core/workflow/utils/condition/entities.py @@ -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 diff --git a/api/core/workflow/utils/condition/processor.py b/api/core/workflow/utils/condition/processor.py index 9795387788..c321f1812c 100644 --- a/api/core/workflow/utils/condition/processor.py +++ b/api/core/workflow/utils/condition/processor.py @@ -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": diff --git a/web/app/components/app/configuration/dataset-config/index.tsx b/web/app/components/app/configuration/dataset-config/index.tsx index 6165cfdeec..6c3bd0f772 100644 --- a/web/app/components/app/configuration/dataset-config/index.tsx +++ b/web/app/components/app/configuration/dataset-config/index.tsx @@ -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'))} /> diff --git a/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx b/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx index 3ac1a046ff..0282d87a29 100644 --- a/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx +++ b/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx @@ -58,7 +58,7 @@ const CreateContent: FC = ({ >
-
+
= ({ selected={type === DataType.time} onSelect={handleTypeChange(DataType.time)} /> +
diff --git a/web/app/components/datasets/metadata/types.ts b/web/app/components/datasets/metadata/types.ts index 00688e29cc..f3b4a25b6f 100644 --- a/web/app/components/datasets/metadata/types.ts +++ b/web/app/components/datasets/metadata/types.ts @@ -2,6 +2,7 @@ export enum DataType { string = 'string', number = 'number', time = 'time', + array = 'array', } export type BuiltInMetadataItem = { diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-array.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-array.tsx new file mode 100644 index 0000000000..e7aa1d8b5a --- /dev/null +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-array.tsx @@ -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: ['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 ( +
+ +
+ { + valueMethod === 'variable' && !isCommonVariable && ( +
+ + {typeCompatibilityCheck?.warning && ( +
+ {typeCompatibilityCheck.message} +
+ )} +
+ ) + } + { + valueMethod === 'variable' && isCommonVariable && ( +
+ + {typeCompatibilityCheck?.warning && ( +
+ {typeCompatibilityCheck.message} +
+ )} +
+ ) + } + { + valueMethod === 'constant' && ( + handleConstantValueChange(e.target.value)} + placeholder={t('workflow.nodes.knowledgeRetrieval.metadata.panel.arrayPlaceholder') || 'Enter comma-separated values'} + /> + ) + } +
+ ) +} + +export default ConditionArray diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx index 00ba306d03..77ec63e8f3 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx @@ -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 = ({ }} > { - if (!variables.length) return + if (!filteredVariables.length) return setOpen(!open) }}>
@@ -71,16 +76,22 @@ const ConditionCommonVariableSelector = ({
{ - variables.map(v => ( -
handleChange(v.name)} - > - - {v.name} + filteredVariables.length > 0 ? ( + filteredVariables.map(v => ( +
handleChange(v.name)} + > + + {v.name} +
+ )) + ) : ( +
+ {t('workflow.nodes.knowledgeRetrieval.metadata.panel.noVariables')}
- )) + ) }
diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx index a93155113e..1876e3358f 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx @@ -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 +} & Pick 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) && ( ) } + { + !comparisonOperatorNotRequireValue(condition.comparison_operator) + && ([ComparisonOperator.in, ComparisonOperator.notIn].includes(condition.comparison_operator) + || currentMetadata?.type === MetadataFilteringVariableType.array) && ( + + ) + } { !comparisonOperatorNotRequireValue(condition.comparison_operator) && currentMetadata?.type === MetadataFilteringVariableType.time && ( 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 ( @@ -69,7 +80,7 @@ const ConditionVariableSelector = ({ {t('workflow.nodes.knowledgeRetrieval.metadata.panel.select')}
- {varType} + {isArrayType ? 'array' : varType}
) @@ -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 + }} />
diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/index.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/index.tsx index 4b129f4c31..d682293a78 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/index.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/index.tsx @@ -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} /> )) } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils.ts b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils.ts index 6397023991..57bc3fc763 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils.ts @@ -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, ] diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-icon.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-icon.tsx index 4a3f539ef4..be2e94c916 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-icon.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-icon.tsx @@ -1,6 +1,7 @@ import { memo } from 'react' import { RiHashtag, + RiListUnordered, RiTextSnippet, RiTimeLine, } from '@remixicon/react' @@ -32,6 +33,11 @@ const MetadataIcon = ({ ) } + { + type === MetadataFilteringVariableType.array && ( + + ) + } ) } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx index 267a0ef797..c74197289f 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx @@ -51,6 +51,8 @@ const Panel: FC> = ({ availableStringNodesWithParent, availableNumberVars, availableNumberNodesWithParent, + availableArrayVars, + availableArrayNodesWithParent, } = useConfig(id, data) const handleOpenFromPropsChange = useCallback((openFromProps: boolean) => { @@ -139,6 +141,8 @@ const Panel: FC> = ({ availableStringNodesWithParent={availableStringNodesWithParent} availableNumberVars={availableNumberVars} availableNumberNodesWithParent={availableNumberNodesWithParent} + availableArrayVars={availableArrayVars} + availableArrayNodesWithParent={availableArrayNodesWithParent} />
diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/types.ts b/web/app/components/workflow/nodes/knowledge-retrieval/types.ts index 1cae4ecd3b..cef0dec5bb 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/types.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/types.ts @@ -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; }[] } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts b/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts index 380f8b95c9..7c702a62f9 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts @@ -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, } }