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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
import { FormContainer } from '@/components/form-container';
import { TopNFormField } from '@/components/top-n-item';
import {
Form,
@ -9,44 +10,79 @@ import {
} from '@/components/ui/form';
import { RAGFlowSelect } from '@/components/ui/select';
import { useTranslate } from '@/hooks/common-hooks';
import { useMemo } from 'react';
import { Channel } from '../../constant';
import { zodResolver } from '@hookform/resolvers/zod';
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 { 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 form = useFormContext();
const options = useMemo(() => {
return Object.values(Channel).map((x) => ({ value: x, label: t(x) }));
}, [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 (
<Form {...form}>
<form
className="space-y-6"
onSubmit={(e) => {
e.preventDefault();
}}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<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>
)}
/>
</form>
<FormWrapper>
<FormContainer>
<QueryVariable></QueryVariable>
<DuckDuckGoWidgets></DuckDuckGoWidgets>
</FormContainer>
</FormWrapper>
<div className="p-5">
<Output list={outputList}></Output>
</div>
</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 { Form, Input } from 'antd';
import { IOperatorForm } from '../../interface';
import DynamicInputVariable from '../components/dynamic-input-variable';
import { zodResolver } from '@hookform/resolvers/zod';
import { ReactNode } from 'react';
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');
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<>
<InputFormField
name="smtp_server"
label={t('smtpServer')}
></InputFormField>
<InputFormField
name="smtp_port"
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服务器配置 */}
<Form.Item label={t('smtpServer')} name={'smtp_server'}>
<Input placeholder="smtp.example.com" />
</Form.Item>
<Form.Item label={t('smtpPort')} name={'smtp_port'}>
<Input type="number" placeholder="587" />
</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>
export const EmailFormPartialSchema = {
smtp_server: z.string(),
smtp_port: z.number(),
email: z.string(),
password: z.string(),
sender_name: z.string(),
};
{/* 动态参数说明 */}
<div style={{ marginBottom: 24 }}>
<h4>{t('dynamicParameters')}</h4>
<div>{t('jsonFormatTip')}</div>
<pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4 }}>
{`{
"to_email": "recipient@example.com",
"cc_email": "cc@example.com",
"subject": "Email Subject",
"content": "Email Content"
}`}
</pre>
const FormSchema = z.object({
to_email: z.string(),
cc_email: z.string(),
content: z.string(),
subject: z.string(),
...EmailFormPartialSchema,
});
const outputList = buildOutputList(initialEmailValues.outputs);
const EmailForm = ({ node }: INextOperatorForm) => {
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>
</Form>
);

View File

@ -2,17 +2,17 @@ import { Operator } from '../../constant';
import AkShareForm from '../akshare-form';
import ArXivForm from '../arxiv-form';
import DeepLForm from '../deepl-form';
import DuckDuckGoForm from '../duckduckgo-form';
import EmailForm from '../email-form';
import GithubForm from '../github-form';
import GoogleScholarForm from '../google-scholar-form';
import PubMedForm from '../pubmed-form';
import WikipediaForm from '../wikipedia-form';
import BingForm from './bing-form';
import CrawlerForm from './crawler-form';
import DuckDuckGoForm from './duckduckgo-form';
import EmailForm from './email-form';
import ExeSQLForm from './exesql-form';
import RetrievalForm from './retrieval-form';
import TavilyForm from './tavily-form';
import WikipediaForm from './wikipedia-form';
import YahooFinanceForm from './yahoo-finance-form';
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 { 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 { ApiKeyField } from '../../components/api-key-field';
@ -9,7 +10,7 @@ import { TavilyFormSchema } from '../../tavily-form';
import { useValues } from '../use-values';
import { useWatchFormChange } from '../use-watch-change';
const TavilyForm = () => {
function TavilyForm() {
const values = useValues();
const FormSchema = z.object(TavilyFormSchema);
@ -30,6 +31,6 @@ const TavilyForm = () => {
</FormWrapper>
</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 {
Form,
@ -7,42 +9,77 @@ import {
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { RAGFlowSelect } from '@/components/ui/select';
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 { 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 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 (
<Form {...form}>
<form
className="space-y-6"
onSubmit={(e) => {
e.preventDefault();
}}
>
<DynamicInputVariable node={node}></DynamicInputVariable>
<TopNFormField></TopNFormField>
<FormField
control={form.control}
name="language"
render={({ field }) => (
<FormItem>
<FormLabel>{t('language')}</FormLabel>
<FormControl>
<RAGFlowSelect {...field} options={LanguageOptions} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
<FormWrapper>
<FormContainer>
<QueryVariable></QueryVariable>
<WikipediaFormWidgets></WikipediaFormWidgets>
</FormContainer>
</FormWrapper>
<div className="p-5">
<Output list={outputList}></Output>
</div>
</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 { Operator } from '../constant';
import { useInitializeOperatorParams } from './use-add-node';
@ -27,6 +27,22 @@ export function useAgentToolInitialValues() {
case Operator.YahooFinance:
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:
return initialValues;
}