Feat: Add variable assignment node #10427 (#11058)

### What problem does this PR solve?

Feat: Add variable assignment node #10427

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-11-06 14:42:47 +08:00
committed by GitHub
parent d469ae6d50
commit ca30ef83bf
14 changed files with 36 additions and 357 deletions

View File

@ -76,7 +76,6 @@ export enum Operator {
Relevant = 'Relevant', Relevant = 'Relevant',
RewriteQuestion = 'RewriteQuestion', RewriteQuestion = 'RewriteQuestion',
KeywordExtract = 'KeywordExtract', KeywordExtract = 'KeywordExtract',
Baidu = 'Baidu',
DuckDuckGo = 'DuckDuckGo', DuckDuckGo = 'DuckDuckGo',
Wikipedia = 'Wikipedia', Wikipedia = 'Wikipedia',
PubMed = 'PubMed', PubMed = 'PubMed',
@ -85,7 +84,6 @@ export enum Operator {
Bing = 'Bing', Bing = 'Bing',
GoogleScholar = 'GoogleScholar', GoogleScholar = 'GoogleScholar',
GitHub = 'GitHub', GitHub = 'GitHub',
BaiduFanyi = 'BaiduFanyi',
QWeather = 'QWeather', QWeather = 'QWeather',
ExeSQL = 'ExeSQL', ExeSQL = 'ExeSQL',
Switch = 'Switch', Switch = 'Switch',
@ -111,6 +109,7 @@ export enum Operator {
SearXNG = 'SearXNG', SearXNG = 'SearXNG',
Placeholder = 'Placeholder', Placeholder = 'Placeholder',
DataOperations = 'DataOperations', DataOperations = 'DataOperations',
VariableAssigner = 'VariableAssigner',
File = 'File', // pipeline File = 'File', // pipeline
Parser = 'Parser', Parser = 'Parser',
Tokenizer = 'Tokenizer', Tokenizer = 'Tokenizer',

View File

@ -27,9 +27,12 @@ export function useBuildSwitchOperatorOptions(
const { t } = useTranslation(); const { t } = useTranslation();
const switchOperatorOptions = useMemo(() => { const switchOperatorOptions = useMemo(() => {
return SwitchOperatorOptions.filter((x) => const filteredOptions =
subset.some((y) => y === x.value), subset.length > 0
).map((x) => ({ ? SwitchOperatorOptions.filter((x) => subset.some((y) => y === x.value))
: SwitchOperatorOptions;
return filteredOptions.map((x) => ({
value: x.value, value: x.value,
icon: ( icon: (
<LogicalOperatorIcon <LogicalOperatorIcon
@ -39,7 +42,7 @@ export function useBuildSwitchOperatorOptions(
), ),
label: t(`flow.switchOperatorOptions.${x.label}`), label: t(`flow.switchOperatorOptions.${x.label}`),
})); }));
}, [t]); }, [subset, t]);
return switchOperatorOptions; return switchOperatorOptions;
} }

View File

@ -1547,6 +1547,9 @@ This delimiter is used to split the input text into several text pieces echo of
codeDescription: 'It allows developers to write custom Python logic.', codeDescription: 'It allows developers to write custom Python logic.',
dataOperations: 'Data operations', dataOperations: 'Data operations',
dataOperationsDescription: 'Perform various operations on a Data object.', dataOperationsDescription: 'Perform various operations on a Data object.',
variableAssigner: 'Variable assigner',
variableAssignerDescription:
'This component performs operations on Data objects, including extracting, filtering, and editing keys and values in the Data.',
inputVariables: 'Input variables', inputVariables: 'Input variables',
runningHintText: 'is running...🕞', runningHintText: 'is running...🕞',
openingSwitch: 'Opening switch', openingSwitch: 'Opening switch',

View File

@ -1468,6 +1468,9 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
codeDescription: '它允许开发人员编写自定义 Python 逻辑。', codeDescription: '它允许开发人员编写自定义 Python 逻辑。',
dataOperations: '数据操作', dataOperations: '数据操作',
dataOperationsDescription: '对数据对象执行各种操作。', dataOperationsDescription: '对数据对象执行各种操作。',
variableAssigner: '变量赋值器',
variableAssignerDescription:
'此组件对数据对象执行操作,包括提取、筛选和编辑数据中的键和值。',
inputVariables: '输入变量', inputVariables: '输入变量',
addVariable: '新增变量', addVariable: '新增变量',
runningHintText: '正在运行中...🕞', runningHintText: '正在运行中...🕞',

View File

@ -72,6 +72,7 @@ import { SwitchNode } from './node/switch-node';
import { TemplateNode } from './node/template-node'; import { TemplateNode } from './node/template-node';
import TokenizerNode from './node/tokenizer-node'; import TokenizerNode from './node/tokenizer-node';
import { ToolNode } from './node/tool-node'; import { ToolNode } from './node/tool-node';
import { VariableAssignerNode } from './node/variable-assigner-node';
export const nodeTypes: NodeTypes = { export const nodeTypes: NodeTypes = {
ragNode: RagNode, ragNode: RagNode,
@ -98,6 +99,7 @@ export const nodeTypes: NodeTypes = {
splitterNode: SplitterNode, splitterNode: SplitterNode,
contextNode: ExtractorNode, contextNode: ExtractorNode,
dataOperationsNode: DataOperationsNode, dataOperationsNode: DataOperationsNode,
variableAssignerNode: VariableAssignerNode,
}; };
const edgeTypes = { const edgeTypes = {

View File

@ -79,6 +79,7 @@ export function AccordionOperators({
Operator.Code, Operator.Code,
Operator.StringTransform, Operator.StringTransform,
Operator.DataOperations, Operator.DataOperations,
Operator.VariableAssigner,
]} ]}
isCustomDropdown={isCustomDropdown} isCustomDropdown={isCustomDropdown}
mousePosition={mousePosition} mousePosition={mousePosition}

View File

@ -0,0 +1,11 @@
import { IRagNode } from '@/interfaces/database/agent';
import { NodeProps } from '@xyflow/react';
import { RagNode } from '.';
export function VariableAssignerNode({ ...props }: NodeProps<IRagNode>) {
return (
<RagNode {...props}>
<section>select</section>
</RagNode>
);
}

View File

@ -66,106 +66,6 @@ export const AgentOperatorList = [
Operator.Agent, Operator.Agent,
]; ];
export const componentMenuList = [
{
name: Operator.Retrieval,
},
{
name: Operator.Categorize,
},
{
name: Operator.Message,
},
{
name: Operator.RewriteQuestion,
},
{
name: Operator.KeywordExtract,
},
{
name: Operator.Switch,
},
{
name: Operator.Iteration,
},
{
name: Operator.Code,
},
{
name: Operator.WaitingDialogue,
},
{
name: Operator.Agent,
},
{
name: Operator.Note,
},
{
name: Operator.DuckDuckGo,
},
{
name: Operator.Baidu,
},
{
name: Operator.Wikipedia,
},
{
name: Operator.PubMed,
},
{
name: Operator.ArXiv,
},
{
name: Operator.Google,
},
{
name: Operator.Bing,
},
{
name: Operator.GoogleScholar,
},
{
name: Operator.GitHub,
},
{
name: Operator.BaiduFanyi,
},
{
name: Operator.QWeather,
},
{
name: Operator.ExeSQL,
},
{
name: Operator.WenCai,
},
{
name: Operator.AkShare,
},
{
name: Operator.YahooFinance,
},
{
name: Operator.Jin10,
},
{
name: Operator.TuShare,
},
{
name: Operator.Crawler,
},
{
name: Operator.Invoke,
},
{
name: Operator.Email,
},
{
name: Operator.SearXNG,
},
];
export const DataOperationsOperatorOptions = [ export const DataOperationsOperatorOptions = [
ComparisonOperator.Equal, ComparisonOperator.Equal,
ComparisonOperator.NotEqual, ComparisonOperator.NotEqual,
@ -281,11 +181,6 @@ export const initialSearXNGValues = {
}, },
}; };
export const initialBaiduValues = {
top_n: 10,
...initialQueryBaseValues,
};
export const initialWikipediaValues = { export const initialWikipediaValues = {
top_n: 10, top_n: 10,
language: 'en', language: 'en',
@ -385,13 +280,6 @@ export const initialGithubValues = {
}, },
}; };
export const initialBaiduFanyiValues = {
appid: 'xxx',
secret_key: 'xxx',
trans_type: 'translate',
...initialQueryBaseValues,
};
export const initialQWeatherValues = { export const initialQWeatherValues = {
web_apikey: 'xxx', web_apikey: 'xxx',
type: 'weather', type: 'weather',
@ -717,6 +605,8 @@ export const initialDataOperationsValues = {
}, },
}; };
export const initialVariableAssignerValues = {};
export const CategorizeAnchorPointPositions = [ export const CategorizeAnchorPointPositions = [
{ top: 1, right: 34 }, { top: 1, right: 34 },
{ top: 8, right: 18 }, { top: 8, right: 18 },
@ -757,7 +647,6 @@ export const RestrictedUpstreamMap = {
Operator.Message, Operator.Message,
Operator.Relevant, Operator.Relevant,
], ],
[Operator.Baidu]: [Operator.Begin, Operator.Retrieval],
[Operator.DuckDuckGo]: [Operator.Begin, Operator.Retrieval], [Operator.DuckDuckGo]: [Operator.Begin, Operator.Retrieval],
[Operator.Wikipedia]: [Operator.Begin, Operator.Retrieval], [Operator.Wikipedia]: [Operator.Begin, Operator.Retrieval],
[Operator.PubMed]: [Operator.Begin, Operator.Retrieval], [Operator.PubMed]: [Operator.Begin, Operator.Retrieval],
@ -766,7 +655,6 @@ export const RestrictedUpstreamMap = {
[Operator.Bing]: [Operator.Begin, Operator.Retrieval], [Operator.Bing]: [Operator.Begin, Operator.Retrieval],
[Operator.GoogleScholar]: [Operator.Begin, Operator.Retrieval], [Operator.GoogleScholar]: [Operator.Begin, Operator.Retrieval],
[Operator.GitHub]: [Operator.Begin, Operator.Retrieval], [Operator.GitHub]: [Operator.Begin, Operator.Retrieval],
[Operator.BaiduFanyi]: [Operator.Begin, Operator.Retrieval],
[Operator.QWeather]: [Operator.Begin, Operator.Retrieval], [Operator.QWeather]: [Operator.Begin, Operator.Retrieval],
[Operator.SearXNG]: [Operator.Begin, Operator.Retrieval], [Operator.SearXNG]: [Operator.Begin, Operator.Retrieval],
[Operator.ExeSQL]: [Operator.Begin], [Operator.ExeSQL]: [Operator.Begin],
@ -798,6 +686,7 @@ export const RestrictedUpstreamMap = {
[Operator.Tokenizer]: [Operator.Begin], [Operator.Tokenizer]: [Operator.Begin],
[Operator.Extractor]: [Operator.Begin], [Operator.Extractor]: [Operator.Begin],
[Operator.File]: [Operator.Begin], [Operator.File]: [Operator.Begin],
[Operator.VariableAssigner]: [Operator.Begin],
}; };
export const NodeMap = { export const NodeMap = {
@ -809,7 +698,6 @@ export const NodeMap = {
[Operator.RewriteQuestion]: 'rewriteNode', [Operator.RewriteQuestion]: 'rewriteNode',
[Operator.KeywordExtract]: 'keywordNode', [Operator.KeywordExtract]: 'keywordNode',
[Operator.DuckDuckGo]: 'ragNode', [Operator.DuckDuckGo]: 'ragNode',
[Operator.Baidu]: 'ragNode',
[Operator.Wikipedia]: 'ragNode', [Operator.Wikipedia]: 'ragNode',
[Operator.PubMed]: 'ragNode', [Operator.PubMed]: 'ragNode',
[Operator.ArXiv]: 'ragNode', [Operator.ArXiv]: 'ragNode',
@ -817,7 +705,6 @@ export const NodeMap = {
[Operator.Bing]: 'ragNode', [Operator.Bing]: 'ragNode',
[Operator.GoogleScholar]: 'ragNode', [Operator.GoogleScholar]: 'ragNode',
[Operator.GitHub]: 'ragNode', [Operator.GitHub]: 'ragNode',
[Operator.BaiduFanyi]: 'ragNode',
[Operator.QWeather]: 'ragNode', [Operator.QWeather]: 'ragNode',
[Operator.SearXNG]: 'ragNode', [Operator.SearXNG]: 'ragNode',
[Operator.ExeSQL]: 'ragNode', [Operator.ExeSQL]: 'ragNode',
@ -849,6 +736,7 @@ export const NodeMap = {
[Operator.HierarchicalMerger]: 'splitterNode', [Operator.HierarchicalMerger]: 'splitterNode',
[Operator.Extractor]: 'contextNode', [Operator.Extractor]: 'contextNode',
[Operator.DataOperations]: 'dataOperationsNode', [Operator.DataOperations]: 'dataOperationsNode',
[Operator.VariableAssigner]: 'variableAssignerNode',
}; };
export enum BeginQueryType { export enum BeginQueryType {

View File

@ -2,8 +2,6 @@ import { Operator } from '../constant';
import AgentForm from '../form/agent-form'; import AgentForm from '../form/agent-form';
import AkShareForm from '../form/akshare-form'; import AkShareForm from '../form/akshare-form';
import ArXivForm from '../form/arxiv-form'; import ArXivForm from '../form/arxiv-form';
import BaiduFanyiForm from '../form/baidu-fanyi-form';
import BaiduForm from '../form/baidu-form';
import BeginForm from '../form/begin-form'; import BeginForm from '../form/begin-form';
import BingForm from '../form/bing-form'; import BingForm from '../form/bing-form';
import CategorizeForm from '../form/categorize-form'; import CategorizeForm from '../form/categorize-form';
@ -72,9 +70,6 @@ export const FormConfigMap = {
[Operator.Agent]: { [Operator.Agent]: {
component: AgentForm, component: AgentForm,
}, },
[Operator.Baidu]: {
component: BaiduForm,
},
[Operator.DuckDuckGo]: { [Operator.DuckDuckGo]: {
component: DuckDuckGoForm, component: DuckDuckGoForm,
}, },
@ -102,9 +97,6 @@ export const FormConfigMap = {
[Operator.GitHub]: { [Operator.GitHub]: {
component: GithubForm, component: GithubForm,
}, },
[Operator.BaiduFanyi]: {
component: BaiduFanyiForm,
},
[Operator.QWeather]: { [Operator.QWeather]: {
component: QWeatherForm, component: QWeatherForm,
}, },

View File

@ -1,71 +0,0 @@
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input, Select } from 'antd';
import { useMemo } from 'react';
import { IOperatorForm } from '../../interface';
import {
BaiduFanyiDomainOptions,
BaiduFanyiSourceLangOptions,
} from '../../options';
import DynamicInputVariable from '../components/dynamic-input-variable';
const BaiduFanyiForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');
const options = useMemo(() => {
return ['translate', 'fieldtranslate'].map((x) => ({
value: x,
label: t(`baiduSecretKeyOptions.${x}`),
}));
}, [t]);
const baiduFanyiOptions = useMemo(() => {
return BaiduFanyiDomainOptions.map((x) => ({
value: x,
label: t(`baiduDomainOptions.${x}`),
}));
}, [t]);
const baiduFanyiSourceLangOptions = useMemo(() => {
return BaiduFanyiSourceLangOptions.map((x) => ({
value: x,
label: t(`baiduSourceLangOptions.${x}`),
}));
}, [t]);
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<Form.Item label={t('appid')} name={'appid'}>
<Input></Input>
</Form.Item>
<Form.Item label={t('secretKey')} name={'secret_key'}>
<Input></Input>
</Form.Item>
<Form.Item label={t('transType')} name={'trans_type'}>
<Select options={options}></Select>
</Form.Item>
<Form.Item noStyle dependencies={['model_type']}>
{({ getFieldValue }) =>
getFieldValue('trans_type') === 'fieldtranslate' && (
<Form.Item label={t('domain')} name={'domain'}>
<Select options={baiduFanyiOptions}></Select>
</Form.Item>
)
}
</Form.Item>
<Form.Item label={t('sourceLang')} name={'source_lang'}>
<Select options={baiduFanyiSourceLangOptions}></Select>
</Form.Item>
<Form.Item label={t('targetLang')} name={'target_lang'}>
<Select options={baiduFanyiSourceLangOptions}></Select>
</Form.Item>
</Form>
);
};
export default BaiduFanyiForm;

View File

@ -1,22 +0,0 @@
import { TopNFormField } from '@/components/top-n-item';
import { Form } from '@/components/ui/form';
import { INextOperatorForm } from '../../interface';
import { DynamicInputVariable } from '../components/next-dynamic-input-variable';
const BaiduForm = ({ form, node }: INextOperatorForm) => {
return (
<Form {...form}>
<form
className="space-y-6"
onSubmit={(e) => {
e.preventDefault();
}}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNFormField></TopNFormField>
</form>
</Form>
);
};
export default BaiduForm;

View File

@ -12,8 +12,6 @@ import {
initialAgentValues, initialAgentValues,
initialAkShareValues, initialAkShareValues,
initialArXivValues, initialArXivValues,
initialBaiduFanyiValues,
initialBaiduValues,
initialBeginValues, initialBeginValues,
initialBingValues, initialBingValues,
initialCategorizeValues, initialCategorizeValues,
@ -50,6 +48,7 @@ import {
initialTokenizerValues, initialTokenizerValues,
initialTuShareValues, initialTuShareValues,
initialUserFillUpValues, initialUserFillUpValues,
initialVariableAssignerValues,
initialWaitingDialogueValues, initialWaitingDialogueValues,
initialWenCaiValues, initialWenCaiValues,
initialWikipediaValues, initialWikipediaValues,
@ -86,7 +85,6 @@ export const useInitializeOperatorParams = () => {
llm_id: llmId, llm_id: llmId,
}, },
[Operator.DuckDuckGo]: initialDuckValues, [Operator.DuckDuckGo]: initialDuckValues,
[Operator.Baidu]: initialBaiduValues,
[Operator.Wikipedia]: initialWikipediaValues, [Operator.Wikipedia]: initialWikipediaValues,
[Operator.PubMed]: initialPubMedValues, [Operator.PubMed]: initialPubMedValues,
[Operator.ArXiv]: initialArXivValues, [Operator.ArXiv]: initialArXivValues,
@ -95,7 +93,6 @@ export const useInitializeOperatorParams = () => {
[Operator.GoogleScholar]: initialGoogleScholarValues, [Operator.GoogleScholar]: initialGoogleScholarValues,
[Operator.SearXNG]: initialSearXNGValues, [Operator.SearXNG]: initialSearXNGValues,
[Operator.GitHub]: initialGithubValues, [Operator.GitHub]: initialGithubValues,
[Operator.BaiduFanyi]: initialBaiduFanyiValues,
[Operator.QWeather]: initialQWeatherValues, [Operator.QWeather]: initialQWeatherValues,
[Operator.ExeSQL]: initialExeSqlValues, [Operator.ExeSQL]: initialExeSqlValues,
[Operator.Switch]: initialSwitchValues, [Operator.Switch]: initialSwitchValues,
@ -131,6 +128,7 @@ export const useInitializeOperatorParams = () => {
prompts: t('flow.prompts.user.summary'), prompts: t('flow.prompts.user.summary'),
}, },
[Operator.DataOperations]: initialDataOperationsValues, [Operator.DataOperations]: initialDataOperationsValues,
[Operator.VariableAssigner]: initialVariableAssignerValues,
}; };
}, [llmId]); }, [llmId]);

View File

@ -14,7 +14,7 @@ import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.s
import { IconFont } from '@/components/icon-font'; import { IconFont } from '@/components/icon-font';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { FileCode, HousePlus } from 'lucide-react'; import { Equal, FileCode, HousePlus } from 'lucide-react';
import { Operator } from './constant'; import { Operator } from './constant';
interface IProps { interface IProps {
@ -58,6 +58,7 @@ export const SVGIconMap = {
export const LucideIconMap = { export const LucideIconMap = {
[Operator.DataOperations]: FileCode, [Operator.DataOperations]: FileCode,
[Operator.VariableAssigner]: Equal,
}; };
const Empty = () => { const Empty = () => {

View File

@ -1,129 +0,0 @@
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { SwitchOperatorOptions } from '@/constants/agent';
import { useBuildSwitchOperatorOptions } from '@/hooks/logic-hooks/use-build-operator-options';
import { useFetchKnowledgeMetadata } from '@/hooks/use-knowledge-request';
import { Plus, X } from 'lucide-react';
import { useCallback } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
export function MetadataFilterConditions({ kbIds }: { kbIds: string[] }) {
const { t } = useTranslation();
const form = useFormContext();
const name = 'meta_data_filter.manual';
const metadata = useFetchKnowledgeMetadata(kbIds);
const switchOperatorOptions = useBuildSwitchOperatorOptions();
const { fields, remove, append } = useFieldArray({
name,
control: form.control,
});
const add = useCallback(
(key: string) => () => {
append({
key,
value: '',
op: SwitchOperatorOptions[0].value,
});
},
[append],
);
return (
<section className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<FormLabel>{t('chat.conditions')}</FormLabel>
<DropdownMenu>
<DropdownMenuTrigger>
<Button variant={'ghost'} type="button">
<Plus />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{Object.keys(metadata.data).map((key, idx) => {
return (
<DropdownMenuItem key={idx} onClick={add(key)}>
{key}
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="space-y-5">
{fields.map((field, index) => {
const typeField = `${name}.${index}.key`;
return (
<div key={field.id} className="flex w-full items-center gap-2">
<FormField
control={form.control}
name={typeField}
render={({ field }) => (
<FormItem className="flex-1 overflow-hidden">
<FormControl>
<Input
{...field}
placeholder={t('common.pleaseInput')}
></Input>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Separator className="w-3 text-text-secondary" />
<FormField
control={form.control}
name={`${name}.${index}.op`}
render={({ field }) => (
<FormItem className="flex-1 overflow-hidden">
<FormControl>
<SelectWithSearch
{...field}
options={switchOperatorOptions}
></SelectWithSearch>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Separator className="w-3 text-text-secondary" />
<FormField
control={form.control}
name={`${name}.${index}.value`}
render={({ field }) => (
<FormItem className="flex-1 overflow-hidden">
<FormControl>
<Input placeholder={t('common.pleaseInput')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button variant={'ghost'} onClick={() => remove(index)}>
<X className="text-text-sub-title-invert " />
</Button>
</div>
);
})}
</div>
</section>
);
}