From 13299197b8e176a9f98e701062a2d6bc62def738 Mon Sep 17 00:00:00 2001 From: balibabu Date: Fri, 21 Nov 2025 16:21:27 +0800 Subject: [PATCH 1/2] Feat: Enable logical operators in metadata. #11387 #11376 (#11442) ### What problem does this PR solve? Feat: Enable logical operators in metadata. #11387 #11376 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/components/metadata-filter/index.tsx | 1 + .../metadata-filter-conditions.tsx | 162 ++++++++++-------- web/src/constants/agent.tsx | 5 + .../hooks/logic-hooks/use-build-options.ts | 12 ++ web/src/pages/agent/constant/index.tsx | 5 +- .../pages/agent/form/switch-form/index.tsx | 14 +- 6 files changed, 119 insertions(+), 80 deletions(-) create mode 100644 web/src/hooks/logic-hooks/use-build-options.ts diff --git a/web/src/components/metadata-filter/index.tsx b/web/src/components/metadata-filter/index.tsx index 8dbdce42f..48388e4c2 100644 --- a/web/src/components/metadata-filter/index.tsx +++ b/web/src/components/metadata-filter/index.tsx @@ -14,6 +14,7 @@ type MetadataFilterProps = { export const MetadataFilterSchema = { meta_data_filter: z .object({ + logic: z.string().optional(), method: z.string().optional(), manual: z .array( diff --git a/web/src/components/metadata-filter/metadata-filter-conditions.tsx b/web/src/components/metadata-filter/metadata-filter-conditions.tsx index 80cb6409b..aee103a1f 100644 --- a/web/src/components/metadata-filter/metadata-filter-conditions.tsx +++ b/web/src/components/metadata-filter/metadata-filter-conditions.tsx @@ -15,14 +15,17 @@ import { } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Separator } from '@/components/ui/separator'; -import { SwitchOperatorOptions } from '@/constants/agent'; +import { SwitchLogicOperator, SwitchOperatorOptions } from '@/constants/agent'; import { useBuildSwitchOperatorOptions } from '@/hooks/logic-hooks/use-build-operator-options'; +import { useBuildSwitchLogicOperatorOptions } from '@/hooks/logic-hooks/use-build-options'; import { useFetchKnowledgeMetadata } from '@/hooks/use-knowledge-request'; import { PromptEditor } from '@/pages/agent/form/components/prompt-editor'; import { Plus, X } from 'lucide-react'; import { useCallback } from 'react'; import { useFieldArray, useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; +import { RAGFlowFormItem } from '../ragflow-form'; +import { RAGFlowSelect } from '../ui/select'; export function MetadataFilterConditions({ kbIds, @@ -36,10 +39,13 @@ export function MetadataFilterConditions({ const { t } = useTranslation(); const form = useFormContext(); const name = prefix + 'meta_data_filter.manual'; + const logic = prefix + 'meta_data_filter.logic'; const metadata = useFetchKnowledgeMetadata(kbIds); const switchOperatorOptions = useBuildSwitchOperatorOptions(); + const switchLogicOperatorOptions = useBuildSwitchLogicOperatorOptions(); + const { fields, remove, append } = useFieldArray({ name, control: form.control, @@ -47,13 +53,14 @@ export function MetadataFilterConditions({ const add = useCallback( (key: string) => () => { + form.setValue(logic, SwitchLogicOperator.And); append({ key, value: '', op: SwitchOperatorOptions[0].value, }); }, - [append], + [append, form, logic], ); return ( @@ -77,73 +84,92 @@ export function MetadataFilterConditions({ -
- {fields.map((field, index) => { - const typeField = `${name}.${index}.key`; - return ( -
- ( - - - - - - - )} - /> - - ( - - - - - - - )} - /> - - ( - - - {canReference ? ( - - ) : ( - +
+ {fields.length > 1 && ( +
+ + + +
+
+ )} +
+ {fields.map((field, index) => { + const typeField = `${name}.${index}.key`; + return ( +
+
+
+ ( + + + + + + )} - - - - )} - /> - -
- ); - })} -
+ /> + + ( + + + + + + + )} + /> +
+ ( + + + {canReference ? ( + + ) : ( + + )} + + + + )} + /> +
+ + + ); + })} +
+ ); } diff --git a/web/src/constants/agent.tsx b/web/src/constants/agent.tsx index 0ba1d927c..5877b91b1 100644 --- a/web/src/constants/agent.tsx +++ b/web/src/constants/agent.tsx @@ -179,3 +179,8 @@ export enum JsonSchemaDataType { Array = 'array', Object = 'object', } + +export enum SwitchLogicOperator { + And = 'and', + Or = 'or', +} diff --git a/web/src/hooks/logic-hooks/use-build-options.ts b/web/src/hooks/logic-hooks/use-build-options.ts new file mode 100644 index 000000000..62370e9bd --- /dev/null +++ b/web/src/hooks/logic-hooks/use-build-options.ts @@ -0,0 +1,12 @@ +import { SwitchLogicOperator } from '@/constants/agent'; +import { buildOptions } from '@/utils/form'; +import { useTranslation } from 'react-i18next'; + +export function useBuildSwitchLogicOperatorOptions() { + const { t } = useTranslation(); + return buildOptions( + SwitchLogicOperator, + t, + 'flow.switchLogicOperatorOptions', + ); +} diff --git a/web/src/pages/agent/constant/index.tsx b/web/src/pages/agent/constant/index.tsx index c357e8cb7..4a442271b 100644 --- a/web/src/pages/agent/constant/index.tsx +++ b/web/src/pages/agent/constant/index.tsx @@ -10,6 +10,7 @@ import { JsonSchemaDataType, Operator, ProgrammingLanguage, + SwitchLogicOperator, SwitchOperatorOptions, initialLlmBaseValues, } from '@/constants/agent'; @@ -51,8 +52,6 @@ import { export const BeginId = 'begin'; -export const SwitchLogicOperatorOptions = ['and', 'or']; - export const CommonOperatorList = Object.values(Operator).filter( (x) => x !== Operator.Note, ); @@ -308,7 +307,7 @@ export const initialExeSqlValues = { export const initialSwitchValues = { conditions: [ { - logical_operator: SwitchLogicOperatorOptions[0], + logical_operator: SwitchLogicOperator.And, items: [ { operator: SwitchOperatorOptions[0].value, diff --git a/web/src/pages/agent/form/switch-form/index.tsx b/web/src/pages/agent/form/switch-form/index.tsx index f9ccee919..53f4995af 100644 --- a/web/src/pages/agent/form/switch-form/index.tsx +++ b/web/src/pages/agent/form/switch-form/index.tsx @@ -11,16 +11,17 @@ import { import { RAGFlowSelect } from '@/components/ui/select'; import { Separator } from '@/components/ui/separator'; import { Textarea } from '@/components/ui/textarea'; +import { SwitchLogicOperator } from '@/constants/agent'; import { useBuildSwitchOperatorOptions } from '@/hooks/logic-hooks/use-build-operator-options'; +import { useBuildSwitchLogicOperatorOptions } from '@/hooks/logic-hooks/use-build-options'; import { cn } from '@/lib/utils'; import { zodResolver } from '@hookform/resolvers/zod'; import { t } from 'i18next'; import { X } from 'lucide-react'; -import { memo, useCallback, useMemo } from 'react'; +import { memo, useCallback } from 'react'; import { useFieldArray, useForm, useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { z } from 'zod'; -import { SwitchLogicOperatorOptions } from '../../constant'; import { IOperatorForm } from '../../interface'; import { FormWrapper } from '../components/form-wrapper'; import { QueryVariable } from '../components/query-variable'; @@ -185,12 +186,7 @@ function SwitchForm({ node }: IOperatorForm) { control: form.control, }); - const switchLogicOperatorOptions = useMemo(() => { - return SwitchLogicOperatorOptions.map((x) => ({ - value: x, - label: t(`flow.switchLogicOperatorOptions.${x}`), - })); - }, [t]); + const switchLogicOperatorOptions = useBuildSwitchLogicOperatorOptions(); useWatchFormChange(node?.id, form); @@ -253,7 +249,7 @@ function SwitchForm({ node }: IOperatorForm) { append({ - logical_operator: SwitchLogicOperatorOptions[0], + logical_operator: SwitchLogicOperator.And, [ItemKey]: [ { operator: switchOperatorOptions[0].value, From a0959b9d38328e104ffeb7fe54945339095d55c1 Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Fri, 21 Nov 2025 17:20:26 +0800 Subject: [PATCH 2/2] Fix:Resolves the issue of sessions not being saved when the variable is array. (#11446) ### What problem does this PR solve? Fix:Resolves the issue of sessions not being saved when the variable is array. ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --- web/src/components/dynamic-form.tsx | 9 +++-- web/src/locales/en.ts | 2 +- web/src/locales/zh.ts | 2 +- .../agent/gobal-variable-sheet/constant.ts | 2 +- .../hooks/use-object-fields.tsx | 33 ++++++++++++++++--- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/web/src/components/dynamic-form.tsx b/web/src/components/dynamic-form.tsx index c74e4c381..ca0b08763 100644 --- a/web/src/components/dynamic-form.tsx +++ b/web/src/components/dynamic-form.tsx @@ -356,6 +356,13 @@ const DynamicForm = { ...combinedErrors, ...fieldErrors, } as any; + + console.log('combinedErrors', combinedErrors); + for (const key in combinedErrors) { + if (Array.isArray(combinedErrors[key])) { + combinedErrors[key] = combinedErrors[key][0]; + } + } console.log('combinedErrors', combinedErrors); return { values: Object.keys(combinedErrors).length ? {} : data, @@ -720,9 +727,7 @@ const DynamicForm = { type="button" disabled={submitLoading} onClick={() => { - console.log('form submit'); (async () => { - console.log('form submit2'); try { let beValid = await form.formControl.trigger(); console.log('form valid', beValid, form, form.formControl); diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 8a3488cee..8ab8e17b3 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1046,7 +1046,7 @@ Example: https://fsn1.your-objectstorage.com`, downloadFileType: 'Download file type', formatTypeError: 'Format or type error', variableNameMessage: - 'Variable name can only contain letters and underscores', + 'Variable name can only contain letters and underscores and numbers', variableDescription: 'Variable Description', defaultValue: 'Default Value', conversationVariable: 'Conversation variable', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 375b2fadc..70a78c825 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -980,7 +980,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 downloadFileTypeTip: '文件下载的类型', downloadFileType: '文件类型', formatTypeError: '格式或类型错误', - variableNameMessage: '名称只能包含字母和下划线', + variableNameMessage: '名称只能包含字母,数字和下划线', variableDescription: '变量的描述', defaultValue: '默认值', conversationVariable: '会话变量', diff --git a/web/src/pages/agent/gobal-variable-sheet/constant.ts b/web/src/pages/agent/gobal-variable-sheet/constant.ts index 935540c15..8470ffa86 100644 --- a/web/src/pages/agent/gobal-variable-sheet/constant.ts +++ b/web/src/pages/agent/gobal-variable-sheet/constant.ts @@ -18,7 +18,7 @@ export const GlobalFormFields = [ placeholder: t('common.namePlaceholder'), required: true, validation: { - pattern: /^[a-zA-Z_]+$/, + pattern: /^[a-zA-Z_0-9]+$/, message: t('flow.variableNameMessage'), }, type: FormFieldType.Text, diff --git a/web/src/pages/agent/gobal-variable-sheet/hooks/use-object-fields.tsx b/web/src/pages/agent/gobal-variable-sheet/hooks/use-object-fields.tsx index 7e60a7aec..c41e766f2 100644 --- a/web/src/pages/agent/gobal-variable-sheet/hooks/use-object-fields.tsx +++ b/web/src/pages/agent/gobal-variable-sheet/hooks/use-object-fields.tsx @@ -3,6 +3,7 @@ import { BlockButton, Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Segmented } from '@/components/ui/segmented'; import { t } from 'i18next'; +import { isEmpty } from 'lodash'; import { Trash2, X } from 'lucide-react'; import { useCallback } from 'react'; import { FieldValues } from 'react-hook-form'; @@ -36,14 +37,19 @@ export const useObjectFields = () => { path: (string | number)[] = [], ): Array<{ path: (string | number)[]; message: string }> => { const errors: Array<{ path: (string | number)[]; message: string }> = []; - - if (obj !== null && typeof obj === 'object' && !Array.isArray(obj)) { + if (typeof obj === 'object' && !Array.isArray(obj)) { + if (isEmpty(obj)) { + errors.push({ + path: [...path], + message: 'No empty parameters are allowed.', + }); + } for (const key in obj) { if (obj.hasOwnProperty(key)) { - if (!/^[a-zA-Z_]+$/.test(key)) { + if (!/^[a-zA-Z_0-9]+$/.test(key)) { errors.push({ path: [...path, key], - message: `Key "${key}" is invalid. Keys can only contain letters and underscores.`, + message: `Key "${key}" is invalid. Keys can only contain letters and underscores and numbers.`, }); } const nestedErrors = validateKeys(obj[key], [...path, key]); @@ -108,6 +114,21 @@ export const useObjectFields = () => { } }, []); + const arrayObjectValidate = useCallback((value: any) => { + try { + if (validateKeys(value, [])?.length > 0) { + throw new Error(t('flow.formatTypeError')); + } + if (value && typeof value === 'string' && !JSON.parse(value)) { + throw new Error(t('flow.formatTypeError')); + } + return true; + } catch (e) { + console.log('object-render-error', e, value); + throw new Error(t('flow.formatTypeError')); + } + }, []); + const arrayStringRender = useCallback((field: FieldValues, type = 'text') => { const values = Array.isArray(field.value) ? field.value @@ -253,8 +274,9 @@ export const useObjectFields = () => { const handleCustomValidate = (value: TypesWithArray) => { switch (value) { case TypesWithArray.Object: - case TypesWithArray.ArrayObject: return objectValidate; + case TypesWithArray.ArrayObject: + return arrayObjectValidate; case TypesWithArray.ArrayString: return arrayStringValidate; case TypesWithArray.ArrayNumber: @@ -284,6 +306,7 @@ export const useObjectFields = () => { return { objectRender, objectValidate, + arrayObjectValidate, arrayStringRender, arrayStringValidate, arrayNumberRender,