From b9d3846bb4aef04558be0697075a8cb972bb7c3b Mon Sep 17 00:00:00 2001 From: balibabu Date: Tue, 29 Jul 2025 17:36:36 +0800 Subject: [PATCH] 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) --- web/src/pages/agent/canvas/index.tsx | 3 +- .../node/dropdown/next-step-dropdown.tsx | 3 + web/src/pages/agent/constant.tsx | 30 +++- .../agent/form/duckduckgo-form/index.tsx | 92 +++++++---- web/src/pages/agent/form/email-form/index.tsx | 149 +++++++++++++----- .../pages/agent/form/tool-form/constant.tsx | 6 +- .../form/tool-form/duckduckgo-form/index.tsx | 38 +++++ .../agent/form/tool-form/email-form/index.tsx | 35 ++++ .../form/tool-form/tavily-form/index.tsx | 7 +- .../form/tool-form/wikipedia-form/index.tsx | 38 +++++ .../pages/agent/form/wikipedia-form/index.tsx | 93 +++++++---- .../hooks/use-agent-tool-initial-values.ts | 18 ++- 12 files changed, 403 insertions(+), 109 deletions(-) create mode 100644 web/src/pages/agent/form/tool-form/duckduckgo-form/index.tsx create mode 100644 web/src/pages/agent/form/tool-form/email-form/index.tsx create mode 100644 web/src/pages/agent/form/tool-form/wikipedia-form/index.tsx diff --git a/web/src/pages/agent/canvas/index.tsx b/web/src/pages/agent/canvas/index.tsx index 41495ce28..5c43f3a52 100644 --- a/web/src/pages/agent/canvas/index.tsx +++ b/web/src/pages/agent/canvas/index.tsx @@ -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, diff --git a/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx index 11139417b..f4048fb95 100644 --- a/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx +++ b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx @@ -104,6 +104,9 @@ function AccordionOperators() { Operator.ExeSQL, Operator.Google, Operator.YahooFinance, + Operator.Email, + Operator.DuckDuckGo, + Operator.Wikipedia, ]} > diff --git a/web/src/pages/agent/constant.tsx b/web/src/pages/agent/constant.tsx index c555bf34d..fefbe440d 100644 --- a/web/src/pages/agent/constant.tsx +++ b/web/src/pages/agent/constant.tsx @@ -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', + }, + }, }; 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', diff --git a/web/src/pages/agent/form/duckduckgo-form/index.tsx b/web/src/pages/agent/form/duckduckgo-form/index.tsx index 1595857ac..1ace033e6 100644 --- a/web/src/pages/agent/form/duckduckgo-form/index.tsx +++ b/web/src/pages/agent/form/duckduckgo-form/index.tsx @@ -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 ( + <> + + ( + + {t('channel')} + + + + + + )} + /> + + ); +} + +const outputList = buildOutputList(initialDuckValues.outputs); + +function DuckDuckGoForm({ node }: INextOperatorForm) { + const defaultValues = useFormValues(initialDuckValues, node); + + const form = useForm>({ + defaultValues, + resolver: zodResolver(FormSchema), + }); + return (
- { - e.preventDefault(); - }} - > - - - ( - - {t('channel')} - - - - - - )} - /> - + + + + + + +
+ +
); -}; +} -export default DuckDuckGoForm; +export default memo(DuckDuckGoForm); diff --git a/web/src/pages/agent/form/email-form/index.tsx b/web/src/pages/agent/form/email-form/index.tsx index 9bee7997f..f41f21e50 100644 --- a/web/src/pages/agent/form/email-form/index.tsx +++ b/web/src/pages/agent/form/email-form/index.tsx @@ -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 ( + ( + + {label} + + + + + + )} + /> + ); +} + +export function EmailFormWidgets() { const { t } = useTranslate('flow'); return ( -
- + <> + + + + + + + ); +} - {/* SMTP服务器配置 */} - - - - - - - - - - - - - - - +export const EmailFormPartialSchema = { + smtp_server: z.string(), + smtp_port: z.number(), + email: z.string(), + password: z.string(), + sender_name: z.string(), +}; - {/* 动态参数说明 */} -
-

{t('dynamicParameters')}

-
{t('jsonFormatTip')}
-
-          {`{
-  "to_email": "recipient@example.com",  
-  "cc_email": "cc@example.com",
-  "subject": "Email Subject",           
-  "content": "Email Content"            
-}`}
-        
+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>({ + defaultValues, + resolver: zodResolver(FormSchema), + }); + + return ( + + + + + + + + + + +
+
); diff --git a/web/src/pages/agent/form/tool-form/constant.tsx b/web/src/pages/agent/form/tool-form/constant.tsx index 1a6b8de87..0cbd2001b 100644 --- a/web/src/pages/agent/form/tool-form/constant.tsx +++ b/web/src/pages/agent/form/tool-form/constant.tsx @@ -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 = { diff --git a/web/src/pages/agent/form/tool-form/duckduckgo-form/index.tsx b/web/src/pages/agent/form/tool-form/duckduckgo-form/index.tsx new file mode 100644 index 000000000..db7117de3 --- /dev/null +++ b/web/src/pages/agent/form/tool-form/duckduckgo-form/index.tsx @@ -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>({ + defaultValues: values, + resolver: zodResolver(FormSchema), + }); + + useWatchFormChange(form); + + return ( +
+ + + + + +
+ ); +} + +export default memo(DuckDuckGoForm); diff --git a/web/src/pages/agent/form/tool-form/email-form/index.tsx b/web/src/pages/agent/form/tool-form/email-form/index.tsx new file mode 100644 index 000000000..76f072f98 --- /dev/null +++ b/web/src/pages/agent/form/tool-form/email-form/index.tsx @@ -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>({ + defaultValues: values, + resolver: zodResolver(FormSchema), + }); + + useWatchFormChange(form); + + return ( +
+ + + + + +
+ ); +} + +export default memo(EmailForm); diff --git a/web/src/pages/agent/form/tool-form/tavily-form/index.tsx b/web/src/pages/agent/form/tool-form/tavily-form/index.tsx index 5c0ea70b5..cfe746c26 100644 --- a/web/src/pages/agent/form/tool-form/tavily-form/index.tsx +++ b/web/src/pages/agent/form/tool-form/tavily-form/index.tsx @@ -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 = () => { ); -}; +} -export default TavilyForm; +export default memo(TavilyForm); diff --git a/web/src/pages/agent/form/tool-form/wikipedia-form/index.tsx b/web/src/pages/agent/form/tool-form/wikipedia-form/index.tsx new file mode 100644 index 000000000..a10133026 --- /dev/null +++ b/web/src/pages/agent/form/tool-form/wikipedia-form/index.tsx @@ -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>({ + defaultValues: values, + resolver: zodResolver(FormSchema), + }); + + useWatchFormChange(form); + + return ( +
+ + + + + +
+ ); +} + +export default memo(WikipediaForm); diff --git a/web/src/pages/agent/form/wikipedia-form/index.tsx b/web/src/pages/agent/form/wikipedia-form/index.tsx index 6100e644d..b622c343d 100644 --- a/web/src/pages/agent/form/wikipedia-form/index.tsx +++ b/web/src/pages/agent/form/wikipedia-form/index.tsx @@ -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 ( + <> + + ( + + {t('language')} + + + + + + )} + /> + + ); +} + +const outputList = buildOutputList(initialWikipediaValues.outputs); + +function WikipediaForm({ node }: INextOperatorForm) { + const defaultValues = useFormValues(initialWikipediaValues, node); + + const form = useForm>({ + defaultValues, + resolver: zodResolver(FormSchema), + }); return (
- { - e.preventDefault(); - }} - > - - - - ( - - {t('language')} - - - - - - )} - /> - + + + + + + +
+ +
); -}; +} -export default WikipediaForm; +export default memo(WikipediaForm); diff --git a/web/src/pages/agent/hooks/use-agent-tool-initial-values.ts b/web/src/pages/agent/hooks/use-agent-tool-initial-values.ts index 311c18d16..b1603dd42 100644 --- a/web/src/pages/agent/hooks/use-agent-tool-initial-values.ts +++ b/web/src/pages/agent/hooks/use-agent-tool-initial-values.ts @@ -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; }