Feat: Add Email and DuckDuckGo and Wikipedia Operator #3221 (#9090)

### What problem does this PR solve?

Feat: Add Email and DuckDuckGo and Wikipedia Operator #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-07-29 17:36:36 +08:00
committed by GitHub
parent ec51508f3e
commit b9d3846bb4
12 changed files with 403 additions and 109 deletions

View File

@ -41,7 +41,6 @@ import { RagNode } from './node';
import { AgentNode } from './node/agent-node'; import { AgentNode } from './node/agent-node';
import { BeginNode } from './node/begin-node'; import { BeginNode } from './node/begin-node';
import { CategorizeNode } from './node/categorize-node'; import { CategorizeNode } from './node/categorize-node';
import { EmailNode } from './node/email-node';
import { GenerateNode } from './node/generate-node'; import { GenerateNode } from './node/generate-node';
import { InvokeNode } from './node/invoke-node'; import { InvokeNode } from './node/invoke-node';
import { IterationNode, IterationStartNode } from './node/iteration-node'; import { IterationNode, IterationStartNode } from './node/iteration-node';
@ -71,7 +70,7 @@ export const nodeTypes: NodeTypes = {
keywordNode: KeywordNode, keywordNode: KeywordNode,
invokeNode: InvokeNode, invokeNode: InvokeNode,
templateNode: TemplateNode, templateNode: TemplateNode,
emailNode: EmailNode, // emailNode: EmailNode,
group: IterationNode, group: IterationNode,
iterationStartNode: IterationStartNode, iterationStartNode: IterationStartNode,
agentNode: AgentNode, agentNode: AgentNode,

View File

@ -104,6 +104,9 @@ function AccordionOperators() {
Operator.ExeSQL, Operator.ExeSQL,
Operator.Google, Operator.Google,
Operator.YahooFinance, Operator.YahooFinance,
Operator.Email,
Operator.DuckDuckGo,
Operator.Wikipedia,
]} ]}
></OperatorItemList> ></OperatorItemList>
</AccordionContent> </AccordionContent>

View File

@ -341,7 +341,17 @@ export const initialKeywordExtractValues = {
export const initialDuckValues = { export const initialDuckValues = {
top_n: 10, top_n: 10,
channel: Channel.Text, channel: Channel.Text,
...initialQueryBaseValues, query: AgentGlobals.SysQuery,
outputs: {
formalized_content: {
value: '',
type: 'string',
},
json: {
value: [],
type: 'Array<Object>',
},
},
}; };
export const initialBaiduValues = { export const initialBaiduValues = {
@ -352,7 +362,13 @@ export const initialBaiduValues = {
export const initialWikipediaValues = { export const initialWikipediaValues = {
top_n: 10, top_n: 10,
language: 'en', language: 'en',
...initialQueryBaseValues, query: AgentGlobals.SysQuery,
outputs: {
formalized_content: {
value: '',
type: 'string',
},
},
}; };
export const initialPubMedValues = { export const initialPubMedValues = {
@ -526,7 +542,7 @@ export const initialTemplateValues = {
export const initialEmailValues = { export const initialEmailValues = {
smtp_server: '', smtp_server: '',
smtp_port: 587, smtp_port: 465,
email: '', email: '',
password: '', password: '',
sender_name: '', sender_name: '',
@ -534,6 +550,12 @@ export const initialEmailValues = {
cc_email: '', cc_email: '',
subject: '', subject: '',
content: '', content: '',
outputs: {
success: {
value: true,
type: 'boolean',
},
},
}; };
export const initialIterationValues = { export const initialIterationValues = {
@ -815,7 +837,7 @@ export const NodeMap = {
[Operator.Crawler]: 'ragNode', [Operator.Crawler]: 'ragNode',
[Operator.Invoke]: 'invokeNode', [Operator.Invoke]: 'invokeNode',
[Operator.Template]: 'templateNode', [Operator.Template]: 'templateNode',
[Operator.Email]: 'emailNode', [Operator.Email]: 'ragNode',
[Operator.Iteration]: 'group', [Operator.Iteration]: 'group',
[Operator.IterationStart]: 'iterationStartNode', [Operator.IterationStart]: 'iterationStartNode',
[Operator.Code]: 'ragNode', [Operator.Code]: 'ragNode',

View File

@ -1,3 +1,4 @@
import { FormContainer } from '@/components/form-container';
import { TopNFormField } from '@/components/top-n-item'; import { TopNFormField } from '@/components/top-n-item';
import { import {
Form, Form,
@ -9,44 +10,79 @@ import {
} from '@/components/ui/form'; } from '@/components/ui/form';
import { RAGFlowSelect } from '@/components/ui/select'; import { RAGFlowSelect } from '@/components/ui/select';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { useMemo } from 'react'; import { zodResolver } from '@hookform/resolvers/zod';
import { Channel } from '../../constant'; import { memo, useMemo } from 'react';
import { useForm, useFormContext } from 'react-hook-form';
import { z } from 'zod';
import { Channel, initialDuckValues } from '../../constant';
import { useFormValues } from '../../hooks/use-form-values';
import { INextOperatorForm } from '../../interface'; import { INextOperatorForm } from '../../interface';
import { DynamicInputVariable } from '../components/next-dynamic-input-variable'; import { buildOutputList } from '../../utils/build-output-list';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output';
import { QueryVariable } from '../components/query-variable';
const DuckDuckGoForm = ({ form, node }: INextOperatorForm) => { export const DuckDuckGoFormPartialSchema = {
top_n: z.string(),
channel: z.string(),
};
const FormSchema = z.object({
query: z.string(),
...DuckDuckGoFormPartialSchema,
});
export function DuckDuckGoWidgets() {
const { t } = useTranslate('flow'); const { t } = useTranslate('flow');
const form = useFormContext();
const options = useMemo(() => { const options = useMemo(() => {
return Object.values(Channel).map((x) => ({ value: x, label: t(x) })); return Object.values(Channel).map((x) => ({ value: x, label: t(x) }));
}, [t]); }, [t]);
return (
<>
<TopNFormField></TopNFormField>
<FormField
control={form.control}
name={'channel'}
render={({ field }) => (
<FormItem>
<FormLabel tooltip={t('channelTip')}>{t('channel')}</FormLabel>
<FormControl>
<RAGFlowSelect {...field} options={options} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
);
}
const outputList = buildOutputList(initialDuckValues.outputs);
function DuckDuckGoForm({ node }: INextOperatorForm) {
const defaultValues = useFormValues(initialDuckValues, node);
const form = useForm<z.infer<typeof FormSchema>>({
defaultValues,
resolver: zodResolver(FormSchema),
});
return ( return (
<Form {...form}> <Form {...form}>
<form <FormWrapper>
className="space-y-6" <FormContainer>
onSubmit={(e) => { <QueryVariable></QueryVariable>
e.preventDefault(); <DuckDuckGoWidgets></DuckDuckGoWidgets>
}} </FormContainer>
> </FormWrapper>
<DynamicInputVariable node={node}></DynamicInputVariable> <div className="p-5">
<TopNFormField></TopNFormField> <Output list={outputList}></Output>
<FormField </div>
control={form.control}
name="channel"
render={({ field }) => (
<FormItem>
<FormLabel tooltip={t('channelTip')}>{t('channel')}</FormLabel>
<FormControl>
<RAGFlowSelect {...field} options={options} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form> </Form>
); );
}; }
export default DuckDuckGoForm; export default memo(DuckDuckGoForm);

View File

@ -1,50 +1,119 @@
import { FormContainer } from '@/components/form-container';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input } from 'antd'; import { zodResolver } from '@hookform/resolvers/zod';
import { IOperatorForm } from '../../interface'; import { ReactNode } from 'react';
import DynamicInputVariable from '../components/dynamic-input-variable'; import { useForm, useFormContext } from 'react-hook-form';
import { z } from 'zod';
import { initialEmailValues } from '../../constant';
import { useFormValues } from '../../hooks/use-form-values';
import { INextOperatorForm } from '../../interface';
import { buildOutputList } from '../../utils/build-output-list';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output';
const EmailForm = ({ onValuesChange, form, node }: IOperatorForm) => { interface InputFormFieldProps {
name: string;
label: ReactNode;
type?: string;
}
function InputFormField({ name, label, type }: InputFormFieldProps) {
const form = useFormContext();
return (
<FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItem>
<FormLabel>{label}</FormLabel>
<FormControl>
<Input {...field} type={type}></Input>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
);
}
export function EmailFormWidgets() {
const { t } = useTranslate('flow'); const { t } = useTranslate('flow');
return ( return (
<Form <>
name="basic" <InputFormField
autoComplete="off" name="smtp_server"
form={form} label={t('smtpServer')}
onValuesChange={onValuesChange} ></InputFormField>
layout={'vertical'} <InputFormField
> name="smtp_port"
<DynamicInputVariable node={node}></DynamicInputVariable> label={t('smtpPort')}
type="number"
></InputFormField>
<InputFormField name="email" label={t('senderEmail')}></InputFormField>
<InputFormField
name="password"
label={t('authCode')}
type="password"
></InputFormField>
<InputFormField
name="sender_name"
label={t('senderName')}
></InputFormField>
</>
);
}
{/* SMTP服务器配置 */} export const EmailFormPartialSchema = {
<Form.Item label={t('smtpServer')} name={'smtp_server'}> smtp_server: z.string(),
<Input placeholder="smtp.example.com" /> smtp_port: z.number(),
</Form.Item> email: z.string(),
<Form.Item label={t('smtpPort')} name={'smtp_port'}> password: z.string(),
<Input type="number" placeholder="587" /> sender_name: z.string(),
</Form.Item> };
<Form.Item label={t('senderEmail')} name={'email'}>
<Input placeholder="sender@example.com" />
</Form.Item>
<Form.Item label={t('authCode')} name={'password'}>
<Input.Password placeholder="your_password" />
</Form.Item>
<Form.Item label={t('senderName')} name={'sender_name'}>
<Input placeholder="Sender Name" />
</Form.Item>
{/* 动态参数说明 */} const FormSchema = z.object({
<div style={{ marginBottom: 24 }}> to_email: z.string(),
<h4>{t('dynamicParameters')}</h4> cc_email: z.string(),
<div>{t('jsonFormatTip')}</div> content: z.string(),
<pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4 }}> subject: z.string(),
{`{ ...EmailFormPartialSchema,
"to_email": "recipient@example.com", });
"cc_email": "cc@example.com",
"subject": "Email Subject", const outputList = buildOutputList(initialEmailValues.outputs);
"content": "Email Content"
}`} const EmailForm = ({ node }: INextOperatorForm) => {
</pre> const { t } = useTranslate('flow');
const defaultValues = useFormValues(initialEmailValues, node);
const form = useForm<z.infer<typeof FormSchema>>({
defaultValues,
resolver: zodResolver(FormSchema),
});
return (
<Form {...form}>
<FormWrapper>
<FormContainer>
<InputFormField name="to_email" label={t('toEmail')}></InputFormField>
<InputFormField name="cc_email" label={t('ccEmail')}></InputFormField>
<InputFormField name="content" label={t('content')}></InputFormField>
<InputFormField name="subject" label={t('subject')}></InputFormField>
<EmailFormWidgets></EmailFormWidgets>
</FormContainer>
</FormWrapper>
<div className="p-5">
<Output list={outputList}></Output>
</div> </div>
</Form> </Form>
); );

View File

@ -2,17 +2,17 @@ import { Operator } from '../../constant';
import AkShareForm from '../akshare-form'; import AkShareForm from '../akshare-form';
import ArXivForm from '../arxiv-form'; import ArXivForm from '../arxiv-form';
import DeepLForm from '../deepl-form'; import DeepLForm from '../deepl-form';
import DuckDuckGoForm from '../duckduckgo-form';
import EmailForm from '../email-form';
import GithubForm from '../github-form'; import GithubForm from '../github-form';
import GoogleScholarForm from '../google-scholar-form'; import GoogleScholarForm from '../google-scholar-form';
import PubMedForm from '../pubmed-form'; import PubMedForm from '../pubmed-form';
import WikipediaForm from '../wikipedia-form';
import BingForm from './bing-form'; import BingForm from './bing-form';
import CrawlerForm from './crawler-form'; import CrawlerForm from './crawler-form';
import DuckDuckGoForm from './duckduckgo-form';
import EmailForm from './email-form';
import ExeSQLForm from './exesql-form'; import ExeSQLForm from './exesql-form';
import RetrievalForm from './retrieval-form'; import RetrievalForm from './retrieval-form';
import TavilyForm from './tavily-form'; import TavilyForm from './tavily-form';
import WikipediaForm from './wikipedia-form';
import YahooFinanceForm from './yahoo-finance-form'; import YahooFinanceForm from './yahoo-finance-form';
export const ToolFormConfigMap = { export const ToolFormConfigMap = {

View File

@ -0,0 +1,38 @@
import { FormContainer } from '@/components/form-container';
import { Form } from '@/components/ui/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { FormWrapper } from '../../components/form-wrapper';
import {
DuckDuckGoFormPartialSchema,
DuckDuckGoWidgets,
} from '../../duckduckgo-form';
import { useValues } from '../use-values';
import { useWatchFormChange } from '../use-watch-change';
function DuckDuckGoForm() {
const values = useValues();
const FormSchema = z.object(DuckDuckGoFormPartialSchema);
const form = useForm<z.infer<typeof FormSchema>>({
defaultValues: values,
resolver: zodResolver(FormSchema),
});
useWatchFormChange(form);
return (
<Form {...form}>
<FormWrapper>
<FormContainer>
<DuckDuckGoWidgets></DuckDuckGoWidgets>
</FormContainer>
</FormWrapper>
</Form>
);
}
export default memo(DuckDuckGoForm);

View File

@ -0,0 +1,35 @@
import { FormContainer } from '@/components/form-container';
import { Form } from '@/components/ui/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { FormWrapper } from '../../components/form-wrapper';
import { EmailFormPartialSchema, EmailFormWidgets } from '../../email-form';
import { useValues } from '../use-values';
import { useWatchFormChange } from '../use-watch-change';
function EmailForm() {
const values = useValues();
const FormSchema = z.object(EmailFormPartialSchema);
const form = useForm<z.infer<typeof FormSchema>>({
defaultValues: values,
resolver: zodResolver(FormSchema),
});
useWatchFormChange(form);
return (
<Form {...form}>
<FormWrapper>
<FormContainer>
<EmailFormWidgets></EmailFormWidgets>
</FormContainer>
</FormWrapper>
</Form>
);
}
export default memo(EmailForm);

View File

@ -1,6 +1,7 @@
import { FormContainer } from '@/components/form-container'; import { FormContainer } from '@/components/form-container';
import { Form } from '@/components/ui/form'; import { Form } from '@/components/ui/form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { ApiKeyField } from '../../components/api-key-field'; import { ApiKeyField } from '../../components/api-key-field';
@ -9,7 +10,7 @@ import { TavilyFormSchema } from '../../tavily-form';
import { useValues } from '../use-values'; import { useValues } from '../use-values';
import { useWatchFormChange } from '../use-watch-change'; import { useWatchFormChange } from '../use-watch-change';
const TavilyForm = () => { function TavilyForm() {
const values = useValues(); const values = useValues();
const FormSchema = z.object(TavilyFormSchema); const FormSchema = z.object(TavilyFormSchema);
@ -30,6 +31,6 @@ const TavilyForm = () => {
</FormWrapper> </FormWrapper>
</Form> </Form>
); );
}; }
export default TavilyForm; export default memo(TavilyForm);

View File

@ -0,0 +1,38 @@
import { FormContainer } from '@/components/form-container';
import { Form } from '@/components/ui/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { FormWrapper } from '../../components/form-wrapper';
import {
WikipediaFormPartialSchema,
WikipediaFormWidgets,
} from '../../wikipedia-form';
import { useValues } from '../use-values';
import { useWatchFormChange } from '../use-watch-change';
function WikipediaForm() {
const values = useValues();
const FormSchema = z.object(WikipediaFormPartialSchema);
const form = useForm<z.infer<typeof FormSchema>>({
defaultValues: values,
resolver: zodResolver(FormSchema),
});
useWatchFormChange(form);
return (
<Form {...form}>
<FormWrapper>
<FormContainer>
<WikipediaFormWidgets></WikipediaFormWidgets>
</FormContainer>
</FormWrapper>
</Form>
);
}
export default memo(WikipediaForm);

View File

@ -1,3 +1,5 @@
import { FormContainer } from '@/components/form-container';
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { TopNFormField } from '@/components/top-n-item'; import { TopNFormField } from '@/components/top-n-item';
import { import {
Form, Form,
@ -7,42 +9,77 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from '@/components/ui/form'; } from '@/components/ui/form';
import { RAGFlowSelect } from '@/components/ui/select';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import { memo } from 'react';
import { useForm, useFormContext } from 'react-hook-form';
import { z } from 'zod';
import { initialWikipediaValues } from '../../constant';
import { useFormValues } from '../../hooks/use-form-values';
import { INextOperatorForm } from '../../interface'; import { INextOperatorForm } from '../../interface';
import { LanguageOptions } from '../../options'; import { LanguageOptions } from '../../options';
import { DynamicInputVariable } from '../components/next-dynamic-input-variable'; import { buildOutputList } from '../../utils/build-output-list';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output';
import { QueryVariable } from '../components/query-variable';
const WikipediaForm = ({ form, node }: INextOperatorForm) => { export const WikipediaFormPartialSchema = {
top_n: z.string(),
language: z.string(),
};
const FormSchema = z.object({
query: z.string(),
...WikipediaFormPartialSchema,
});
export function WikipediaFormWidgets() {
const { t } = useTranslate('common'); const { t } = useTranslate('common');
const form = useFormContext();
return (
<>
<TopNFormField></TopNFormField>
<FormField
control={form.control}
name="language"
render={({ field }) => (
<FormItem>
<FormLabel>{t('language')}</FormLabel>
<FormControl>
<SelectWithSearch {...field} options={LanguageOptions} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
);
}
const outputList = buildOutputList(initialWikipediaValues.outputs);
function WikipediaForm({ node }: INextOperatorForm) {
const defaultValues = useFormValues(initialWikipediaValues, node);
const form = useForm<z.infer<typeof FormSchema>>({
defaultValues,
resolver: zodResolver(FormSchema),
});
return ( return (
<Form {...form}> <Form {...form}>
<form <FormWrapper>
className="space-y-6" <FormContainer>
onSubmit={(e) => { <QueryVariable></QueryVariable>
e.preventDefault(); <WikipediaFormWidgets></WikipediaFormWidgets>
}} </FormContainer>
> </FormWrapper>
<DynamicInputVariable node={node}></DynamicInputVariable> <div className="p-5">
<TopNFormField></TopNFormField> <Output list={outputList}></Output>
</div>
<FormField
control={form.control}
name="language"
render={({ field }) => (
<FormItem>
<FormLabel>{t('language')}</FormLabel>
<FormControl>
<RAGFlowSelect {...field} options={LanguageOptions} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form> </Form>
); );
}; }
export default WikipediaForm; export default memo(WikipediaForm);

View File

@ -1,4 +1,4 @@
import { omit } from 'lodash'; import { omit, pick } from 'lodash';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { Operator } from '../constant'; import { Operator } from '../constant';
import { useInitializeOperatorParams } from './use-add-node'; import { useInitializeOperatorParams } from './use-add-node';
@ -27,6 +27,22 @@ export function useAgentToolInitialValues() {
case Operator.YahooFinance: case Operator.YahooFinance:
return omit(initialValues, 'stock_code'); return omit(initialValues, 'stock_code');
case Operator.Email:
return pick(
initialValues,
'smtp_server',
'smtp_port',
'email',
'password',
'sender_name',
);
case Operator.DuckDuckGo:
return pick(initialValues, 'top_n', 'channel');
case Operator.Wikipedia:
return pick(initialValues, 'top_n', 'language');
default: default:
return initialValues; return initialValues;
} }