From 35e5fade93aaa0b61502382f80a9f45714d4c99c Mon Sep 17 00:00:00 2001 From: buua436 <66937541+buua436@users.noreply.github.com> Date: Tue, 18 Nov 2025 19:14:38 +0800 Subject: [PATCH 01/17] Feat: new component variable assigner (#11050) ### What problem does this PR solve? issue: https://github.com/infiniflow/ragflow/issues/10427 change: new component variable assigner ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- agent/canvas.py | 32 +++++ agent/component/data_operations.py | 15 +++ agent/component/iteration.py | 1 + agent/component/variable_assigner.py | 187 +++++++++++++++++++++++++++ 4 files changed, 235 insertions(+) create mode 100644 agent/component/variable_assigner.py diff --git a/agent/canvas.py b/agent/canvas.py index c156c133c..152dd945e 100644 --- a/agent/canvas.py +++ b/agent/canvas.py @@ -216,6 +216,38 @@ class Graph: else: cur = getattr(cur, key, None) return cur + + def set_variable_value(self, exp: str,value): + exp = exp.strip("{").strip("}").strip(" ").strip("{").strip("}") + if exp.find("@") < 0: + self.globals[exp] = value + return + cpn_id, var_nm = exp.split("@") + cpn = self.get_component(cpn_id) + if not cpn: + raise Exception(f"Can't find variable: '{cpn_id}@{var_nm}'") + parts = var_nm.split(".", 1) + root_key = parts[0] + rest = parts[1] if len(parts) > 1 else "" + if not rest: + cpn["obj"].set_output(root_key, value) + return + root_val = cpn["obj"].output(root_key) + if not root_val: + root_val = {} + cpn["obj"].set_output(root_key, self.set_variable_param_value(root_val,rest,value)) + + def set_variable_param_value(self, obj: Any, path: str, value) -> Any: + cur = obj + keys = path.split('.') + if not path: + return value + for key in keys: + if key not in cur or not isinstance(cur[key], dict): + cur[key] = {} + cur = cur[key] + cur[keys[-1]] = value + return obj def is_canceled(self) -> bool: return has_canceled(self.task_id) diff --git a/agent/component/data_operations.py b/agent/component/data_operations.py index fab7d8c0f..cddd20996 100644 --- a/agent/component/data_operations.py +++ b/agent/component/data_operations.py @@ -1,3 +1,18 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# from abc import ABC import ast import os diff --git a/agent/component/iteration.py b/agent/component/iteration.py index a39147d8f..cff09d622 100644 --- a/agent/component/iteration.py +++ b/agent/component/iteration.py @@ -32,6 +32,7 @@ class IterationParam(ComponentParamBase): def __init__(self): super().__init__() self.items_ref = "" + self.veriable={} def get_input_form(self) -> dict[str, dict]: return { diff --git a/agent/component/variable_assigner.py b/agent/component/variable_assigner.py new file mode 100644 index 000000000..2faecd7da --- /dev/null +++ b/agent/component/variable_assigner.py @@ -0,0 +1,187 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from abc import ABC +import os +import numbers +from agent.component.base import ComponentBase, ComponentParamBase +from api.utils.api_utils import timeout + +class VariableAssignerParam(ComponentParamBase): + """ + Define the Variable Assigner component parameters. + """ + def __init__(self): + super().__init__() + self.variables=[] + + def check(self): + return True + + def get_input_form(self) -> dict[str, dict]: + return { + "items": { + "type": "json", + "name": "Items" + } + } + +class VariableAssigner(ComponentBase,ABC): + component_name = "VariableAssigner" + + @timeout(int(os.environ.get("COMPONENT_EXEC_TIMEOUT", 10*60))) + def _invoke(self, **kwargs): + if not isinstance(self._param.variables,list): + return + else: + for item in self._param.variables: + variable=item["variable"] + operator=item["operator"] + parameter=item["parameter"] + variable_value=self._canvas.get_variable_value(variable) + new_variable=self._operate(variable_value,operator,parameter) + self._canvas.set_variable_value(variable,new_variable) + + def _operate(self,variable,operator,parameter): + if operator == "overwrite": + return self._overwrite(parameter) + elif operator == "clear": + return self._clear(variable) + elif operator == "set": + return self._set(variable,parameter) + elif operator == "append": + return self._append(variable,parameter) + elif operator == "extend": + return self._extend(variable,parameter) + elif operator == "remove_first": + return self._remove_first(variable) + elif operator == "remove_last": + return self._remove_last(variable) + elif operator == "+=": + return self._add(variable,parameter) + elif operator == "-=": + return self._subtract(variable,parameter) + elif operator == "*=": + return self._multiply(variable,parameter) + elif operator == "/=": + return self._divide(variable,parameter) + else: + return + + def _overwrite(self,parameter): + return self._canvas.get_variable_value(parameter) + + def _clear(self,variable): + if isinstance(variable,list): + return [] + elif isinstance(variable,str): + return "" + elif isinstance(variable,dict): + return {} + elif isinstance(variable,int): + return 0 + elif isinstance(variable,float): + return 0.0 + elif isinstance(variable,bool): + return False + else: + return None + + def _set(self,variable,parameter): + if variable is None: + return self._canvas.get_value_with_variable(parameter) + elif isinstance(variable,str): + return self._canvas.get_value_with_variable(parameter) + elif isinstance(variable,bool): + return parameter + elif isinstance(variable,int): + return parameter + elif isinstance(variable,float): + return parameter + else: + return parameter + + def _append(self,variable,parameter): + parameter=self._canvas.get_variable_value(parameter) + if variable is None: + variable=[] + if not isinstance(variable,list): + return "ERROR:VARIABLE_NOT_LIST" + elif len(variable)!=0 and not isinstance(parameter,type(variable[0])): + return "ERROR:PARAMETER_NOT_LIST_ELEMENT_TYPE" + else: + return variable+parameter + + def _extend(self,variable,parameter): + parameter=self._canvas.get_variable_value(parameter) + if variable is None: + variable=[] + if not isinstance(variable,list): + return "ERROR:VARIABLE_NOT_LIST" + elif not isinstance(parameter,list): + return "ERROR:PARAMETER_NOT_LIST" + elif len(variable)!=0 and len(parameter)!=0 and not isinstance(parameter[0],type(variable[0])): + return "ERROR:PARAMETER_NOT_LIST_ELEMENT_TYPE" + else: + return variable+parameter + + def _remove_first(self,variable): + if len(variable)==0: + return variable + if not isinstance(variable,list): + return "ERROR:VARIABLE_NOT_LIST" + else: + return variable[1:] + + def _remove_last(self,variable): + if len(variable)==0: + return variable + if not isinstance(variable,list): + return "ERROR:VARIABLE_NOT_LIST" + else: + return variable[:-1] + + + def is_number(self, value): + if isinstance(value, bool): + return False + return isinstance(value, numbers.Number) + + def _add(self,variable,parameter): + if self.is_number(variable) and self.is_number(parameter): + return variable+parameter + else: + return "ERROR:VARIABLE_NOT_NUMBER or PARAMETER_NOT_NUMBER" + + def _subtract(self,variable,parameter): + if self.is_number(variable) and self.is_number(parameter): + return variable-parameter + else: + return "ERROR:VARIABLE_NOT_NUMBER or PARAMETER_NOT_NUMBER" + + def _multiply(self,variable,parameter): + if self.is_number(variable) and self.is_number(parameter): + return variable*parameter + else: + return "ERROR:VARIABLE_NOT_NUMBER or PARAMETER_NOT_NUMBER" + + def _divide(self,variable,parameter): + if self.is_number(variable) and self.is_number(parameter): + if parameter==0: + return "ERROR:DIVIDE_BY_ZERO" + else: + return variable/parameter + else: + return "ERROR:VARIABLE_NOT_NUMBER or PARAMETER_NOT_NUMBER" \ No newline at end of file From 8cd488259642603eaf5d773dcae26640fc872206 Mon Sep 17 00:00:00 2001 From: balibabu Date: Tue, 18 Nov 2025 20:07:04 +0800 Subject: [PATCH 02/17] Feat: Display variables in the variable assignment node. #10427 (#11349) ### What problem does this PR solve? Feat: Display variables in the variable assignment node. #10427 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/locales/en.ts | 13 +++++ web/src/locales/zh.ts | 13 +++++ .../canvas/node/variable-assigner-node.tsx | 26 +++++++-- web/src/pages/agent/constant/index.tsx | 7 +++ .../use-build-logical-options.ts | 53 +++++++++++++++---- 5 files changed, 98 insertions(+), 14 deletions(-) diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 47a8b8351..8d2f196d7 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1852,6 +1852,19 @@ Important structured information may include: names, dates, locations, events, k asc: 'Ascending', desc: 'Descending', }, + variableAssignerLogicalOperatorOptions: { + overwrite: 'Overwrite', + clear: 'Clear', + set: 'Set', + '+=': 'Add', + '-=': 'Subtract', + '*=': 'Multiply', + '/=': 'Divide', + append: 'Append', + extend: 'Extend', + removeFirst: 'Remove first', + removeLast: 'Remove last', + }, }, llmTools: { bad_calculator: { diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index b6d25dc1f..4919dfa8a 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -1713,6 +1713,19 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`, asc: '升序', desc: '降序', }, + variableAssignerLogicalOperatorOptions: { + overwrite: '覆盖', + clear: '清除', + set: '设置', + add: '加', + subtract: '减', + multiply: '乘', + divide: '除', + append: '追加', + extend: '扩展', + removeFirst: '移除第一个', + removeLast: '移除最后一个', + }, }, footer: { profile: 'All rights reserved @ React', diff --git a/web/src/pages/agent/canvas/node/variable-assigner-node.tsx b/web/src/pages/agent/canvas/node/variable-assigner-node.tsx index c6f8beaf8..0f6c79a62 100644 --- a/web/src/pages/agent/canvas/node/variable-assigner-node.tsx +++ b/web/src/pages/agent/canvas/node/variable-assigner-node.tsx @@ -1,11 +1,31 @@ -import { IRagNode } from '@/interfaces/database/agent'; +import { NodeCollapsible } from '@/components/collapse'; +import { BaseNode } from '@/interfaces/database/agent'; import { NodeProps } from '@xyflow/react'; import { RagNode } from '.'; +import { VariableAssignerFormSchemaType } from '../../form/variable-assigner-form'; +import { useGetVariableLabelOrTypeByValue } from '../../hooks/use-get-begin-query'; +import { LabelCard } from './card'; + +export function VariableAssignerNode({ + ...props +}: NodeProps>) { + const { data } = props; + const { getLabel } = useGetVariableLabelOrTypeByValue(); -export function VariableAssignerNode({ ...props }: NodeProps) { return ( -
select
+ + {(x, idx) => ( +
+ + + {getLabel(x.variable)} + + {x.operator} + +
+ )} +
); } diff --git a/web/src/pages/agent/constant/index.tsx b/web/src/pages/agent/constant/index.tsx index bdd5a0763..76da5541b 100644 --- a/web/src/pages/agent/constant/index.tsx +++ b/web/src/pages/agent/constant/index.tsx @@ -851,6 +851,13 @@ export enum VariableAssignerLogicalNumberOperator { Divide = '/=', } +export const VariableAssignerLogicalNumberOperatorLabelMap = { + [VariableAssignerLogicalNumberOperator.Add]: 'add', + [VariableAssignerLogicalNumberOperator.Subtract]: 'subtract', + [VariableAssignerLogicalNumberOperator.Multiply]: 'multiply', + [VariableAssignerLogicalNumberOperator.Divide]: 'divide', +}; + export enum VariableAssignerLogicalArrayOperator { Overwrite = VariableAssignerLogicalOperator.Overwrite, Clear = VariableAssignerLogicalOperator.Clear, diff --git a/web/src/pages/agent/form/variable-assigner-form/use-build-logical-options.ts b/web/src/pages/agent/form/variable-assigner-form/use-build-logical-options.ts index a27ea20d5..a7f960e98 100644 --- a/web/src/pages/agent/form/variable-assigner-form/use-build-logical-options.ts +++ b/web/src/pages/agent/form/variable-assigner-form/use-build-logical-options.ts @@ -1,26 +1,57 @@ import { buildOptions } from '@/utils/form'; +import { camelCase } from 'lodash'; import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import { JsonSchemaDataType, VariableAssignerLogicalArrayOperator, VariableAssignerLogicalNumberOperator, + VariableAssignerLogicalNumberOperatorLabelMap, VariableAssignerLogicalOperator, } from '../../constant'; export function useBuildLogicalOptions() { - const buildLogicalOptions = useCallback((type: string) => { - if ( - type?.toLowerCase().startsWith(JsonSchemaDataType.Array.toLowerCase()) - ) { - return buildOptions(VariableAssignerLogicalArrayOperator); - } + const { t } = useTranslation(); - if (type === JsonSchemaDataType.Number) { - return buildOptions(VariableAssignerLogicalNumberOperator); - } + const buildVariableAssignerLogicalOptions = useCallback( + (record: Record) => { + return buildOptions( + record, + t, + 'flow.variableAssignerLogicalOperatorOptions', + true, + ); + }, + [t], + ); - return buildOptions(VariableAssignerLogicalOperator); - }, []); + const buildLogicalOptions = useCallback( + (type: string) => { + if ( + type?.toLowerCase().startsWith(JsonSchemaDataType.Array.toLowerCase()) + ) { + return buildVariableAssignerLogicalOptions( + VariableAssignerLogicalArrayOperator, + ); + } + + if (type === JsonSchemaDataType.Number) { + return Object.values(VariableAssignerLogicalNumberOperator).map( + (val) => ({ + label: t( + `flow.variableAssignerLogicalOperatorOptions.${camelCase(VariableAssignerLogicalNumberOperatorLabelMap[val as keyof typeof VariableAssignerLogicalNumberOperatorLabelMap] || val)}`, + ), + value: val, + }), + ); + } + + return buildVariableAssignerLogicalOptions( + VariableAssignerLogicalOperator, + ); + }, + [buildVariableAssignerLogicalOptions, t], + ); return { buildLogicalOptions, From 50bc53a1f546378b56c0a2f4e2bdb13234b5b610 Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Tue, 18 Nov 2025 20:07:17 +0800 Subject: [PATCH 03/17] Fix: Modify the personal center style #10703 (#11347) ### What problem does this PR solve? Fix: Modify the personal center style ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --- web/src/components/dynamic-form.tsx | 4 +- .../components/originui/password-input.tsx | 5 +-- .../originui/select-with-search.tsx | 4 +- web/src/components/ui/input.tsx | 2 +- web/src/components/ui/textarea.tsx | 2 +- web/src/components/ui/tooltip.tsx | 4 +- web/src/hooks/llm-hooks.tsx | 5 ++- web/src/locales/en.ts | 2 + web/src/locales/zh.ts | 2 + .../data-source/add-datasource-modal.tsx | 12 +++--- .../component/added-source-card.tsx | 21 +++++++---- web/src/pages/user-setting/mcp/index.tsx | 1 + .../pages/user-setting/mcp/mcp-operation.tsx | 37 +++++++++---------- web/src/pages/user-setting/profile/index.tsx | 20 +++++----- .../setting-model/components/modal-card.tsx | 14 ++++--- .../components/system-setting.tsx | 3 +- .../setting-model/components/un-add-model.tsx | 5 ++- .../setting-team/tenant-table.tsx | 2 +- .../user-setting/setting-team/user-table.tsx | 2 +- 19 files changed, 79 insertions(+), 68 deletions(-) diff --git a/web/src/components/dynamic-form.tsx b/web/src/components/dynamic-form.tsx index 5c78a1dcb..41a57ea92 100644 --- a/web/src/components/dynamic-form.tsx +++ b/web/src/components/dynamic-form.tsx @@ -496,7 +496,7 @@ const DynamicForm = {