From d2df66913590d0767807d6f4b3be333b7b55b29d Mon Sep 17 00:00:00 2001 From: balibabu Date: Wed, 16 Jul 2025 16:25:50 +0800 Subject: [PATCH] Feat: Add sql form #3221 (#8874) ### What problem does this PR solve? Feat: Add sql form #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/hooks/use-agent-request.ts | 26 +- .../node/dropdown/next-step-dropdown.tsx | 6 +- web/src/pages/agent/constant.tsx | 13 +- .../tool-popover/use-update-tools.ts | 11 +- .../agent/form/components/form-wrapper.tsx | 16 ++ .../pages/agent/form/exesql-form/index.tsx | 224 ++++++++++++------ .../agent/form/exesql-form/use-submit-form.ts | 33 +++ .../pages/agent/form/tool-form/constant.tsx | 2 +- .../form/tool-form/crawler-form/index.tsx | 10 +- .../form/tool-form/exesql-form/index.tsx | 39 +++ .../pages/agent/form/tool-form/use-values.ts | 18 +- web/src/pages/agent/hooks/use-add-node.ts | 4 +- .../hooks/use-agent-tool-initial-values.ts | 34 +++ 13 files changed, 323 insertions(+), 113 deletions(-) create mode 100644 web/src/pages/agent/form/components/form-wrapper.tsx create mode 100644 web/src/pages/agent/form/exesql-form/use-submit-form.ts create mode 100644 web/src/pages/agent/form/tool-form/exesql-form/index.tsx create mode 100644 web/src/pages/agent/hooks/use-agent-tool-initial-values.ts diff --git a/web/src/hooks/use-agent-request.ts b/web/src/hooks/use-agent-request.ts index 772cfc847..60cec288d 100644 --- a/web/src/hooks/use-agent-request.ts +++ b/web/src/hooks/use-agent-request.ts @@ -1,3 +1,4 @@ +import message from '@/components/ui/message'; import { AgentGlobals } from '@/constants/agent'; import { ITraceData } from '@/interfaces/database/agent'; import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow'; @@ -8,7 +9,6 @@ import flowService from '@/services/flow-service'; import { buildMessageListWithUuid } from '@/utils/chat'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useDebounce } from 'ahooks'; -import { message } from 'antd'; import { get, set } from 'lodash'; import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -29,6 +29,7 @@ export const enum AgentApiAction { FetchAgentTemplates = 'fetchAgentTemplates', UploadCanvasFile = 'uploadCanvasFile', Trace = 'trace', + TestDbConnect = 'testDbConnect', } export const EmptyDsl = { @@ -127,7 +128,7 @@ export const useFetchAgentListByPage = () => { const onInputChange: React.ChangeEventHandler = useCallback( (e) => { - // setPagination({ page: 1 }); // TODO: 这里导致重复请求 + // setPagination({ page: 1 }); handleInputChange(e); }, [handleInputChange], @@ -331,3 +332,24 @@ export const useFetchMessageTrace = () => { return { data, loading, refetch, setMessageId }; }; + +export const useTestDbConnect = () => { + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: [AgentApiAction.TestDbConnect], + mutationFn: async (params: any) => { + const ret = await flowService.testDbConnect(params); + if (ret?.data?.code === 0) { + message.success(ret?.data?.data); + } else { + message.error(ret?.data?.data); + } + return ret; + }, + }); + + return { data, loading, testDbConnect: mutateAsync }; +}; 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 c2b2996b8..e9333a50f 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 @@ -98,7 +98,11 @@ function AccordionOperators() { Tools diff --git a/web/src/pages/agent/constant.tsx b/web/src/pages/agent/constant.tsx index 8e8bb2d68..9f56347d1 100644 --- a/web/src/pages/agent/constant.tsx +++ b/web/src/pages/agent/constant.tsx @@ -20,7 +20,6 @@ import { import { ModelVariableType } from '@/constants/knowledge'; import i18n from '@/locales/config'; import { setInitialChatVariableEnabledFieldValue } from '@/utils/chat'; -import { omit } from 'lodash'; // DuckDuckGo's channel options export enum Channel { @@ -562,7 +561,7 @@ export const initialExeSqlValues = { password: '', loop: 3, top_n: 30, - ...initialQueryBaseValues, + query: '', }; export const initialSwitchValues = { @@ -960,16 +959,6 @@ export enum VariableType { File = 'file', } -export const DefaultAgentToolValuesMap = { - [Operator.Retrieval]: { - ...omit(initialRetrievalValues, 'query'), - description: '', - }, - [Operator.TavilySearch]: { - api_key: '', - }, -}; - export enum AgentExceptionMethod { Comment = 'comment', Goto = 'goto', diff --git a/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts b/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts index 90948235a..db579561a 100644 --- a/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts +++ b/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts @@ -1,6 +1,7 @@ import { IAgentForm } from '@/interfaces/database/agent'; -import { DefaultAgentToolValuesMap } from '@/pages/agent/constant'; +import { Operator } from '@/pages/agent/constant'; import { AgentFormContext } from '@/pages/agent/context'; +import { useAgentToolInitialValues } from '@/pages/agent/hooks/use-agent-tool-initial-values'; import useGraphStore from '@/pages/agent/store'; import { get } from 'lodash'; import { useCallback, useContext, useMemo } from 'react'; @@ -18,6 +19,7 @@ export function useUpdateAgentNodeTools() { const { updateNodeForm } = useGraphStore((state) => state); const node = useContext(AgentFormContext); const tools = useGetNodeTools(); + const { initializeAgentToolValues } = useAgentToolInitialValues(); const updateNodeTools = useCallback( (value: string[]) => { @@ -30,10 +32,7 @@ export function useUpdateAgentNodeTools() { : { component_name: cur, name: cur, - params: - DefaultAgentToolValuesMap[ - cur as keyof typeof DefaultAgentToolValuesMap - ] || {}, + params: initializeAgentToolValues(cur as Operator), }, ); return pre; @@ -42,7 +41,7 @@ export function useUpdateAgentNodeTools() { updateNodeForm(node?.id, nextValue, ['tools']); } }, - [node?.id, tools, updateNodeForm], + [initializeAgentToolValues, node?.id, tools, updateNodeForm], ); return { updateNodeTools }; diff --git a/web/src/pages/agent/form/components/form-wrapper.tsx b/web/src/pages/agent/form/components/form-wrapper.tsx new file mode 100644 index 000000000..d1985b18e --- /dev/null +++ b/web/src/pages/agent/form/components/form-wrapper.tsx @@ -0,0 +1,16 @@ +type FormProps = React.ComponentProps<'form'>; + +export function FormWrapper({ children, ...props }: FormProps) { + return ( +
{ + e.preventDefault(); + }} + {...props} + > + {children} +
+ ); +} diff --git a/web/src/pages/agent/form/exesql-form/index.tsx b/web/src/pages/agent/form/exesql-form/index.tsx index 11d0ce331..2674a652c 100644 --- a/web/src/pages/agent/form/exesql-form/index.tsx +++ b/web/src/pages/agent/form/exesql-form/index.tsx @@ -1,86 +1,158 @@ -import LLMSelect from '@/components/llm-select'; -import TopNItem from '@/components/top-n-item'; +import { LargeModelFormField } from '@/components/large-model-form-field'; +import { SelectWithSearch } from '@/components/originui/select-with-search'; +import { TopNFormField } from '@/components/top-n-item'; +import { ButtonLoading } from '@/components/ui/button'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input, NumberInput } from '@/components/ui/input'; import { useTranslate } from '@/hooks/common-hooks'; -import { useTestDbConnect } from '@/hooks/flow-hooks'; -import { Button, Flex, Form, Input, InputNumber, Select } from 'antd'; -import { useCallback } from 'react'; -import { IOperatorForm } from '../../interface'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm, useFormContext } from 'react-hook-form'; +import { z } from 'zod'; +import { initialExeSqlValues } from '../../constant'; +import { useFormValues } from '../../hooks/use-form-values'; +import { useWatchFormChange } from '../../hooks/use-watch-form-change'; +import { INextOperatorForm } from '../../interface'; import { ExeSQLOptions } from '../../options'; -import DynamicInputVariable from '../components/dynamic-input-variable'; +import { FormWrapper } from '../components/form-wrapper'; +import { QueryVariable } from '../components/query-variable'; +import { FormSchema, useSubmitForm } from './use-submit-form'; -const ExeSQLForm = ({ onValuesChange, form, node }: IOperatorForm) => { +export function ExeSQLFormWidgets({ loading }: { loading: boolean }) { + const form = useFormContext(); const { t } = useTranslate('flow'); - const { testDbConnect, loading } = useTestDbConnect(); - - const handleTest = useCallback(async () => { - const ret = await form?.validateFields(); - testDbConnect(ret); - }, [form, testDbConnect]); return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + ); +} + +const ExeSQLForm = ({ node }: INextOperatorForm) => { + const defaultValues = useFormValues(initialExeSqlValues, node); + + const { onSubmit, loading } = useSubmitForm(); + + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues, + }); + + useWatchFormChange(node?.id, form); + + return ( + + + + + ); }; diff --git a/web/src/pages/agent/form/exesql-form/use-submit-form.ts b/web/src/pages/agent/form/exesql-form/use-submit-form.ts new file mode 100644 index 000000000..6472052f9 --- /dev/null +++ b/web/src/pages/agent/form/exesql-form/use-submit-form.ts @@ -0,0 +1,33 @@ +import { useTestDbConnect } from '@/hooks/use-agent-request'; +import { useCallback } from 'react'; +import { z } from 'zod'; + +export const ExeSQLFormSchema = { + llm_id: z.string().min(1), + db_type: z.string().min(1), + database: z.string().min(1), + username: z.string().min(1), + host: z.string().min(1), + port: z.number(), + password: z.string().min(1), + loop: z.number(), + top_n: z.number(), +}; + +export const FormSchema = z.object({ + query: z.string().optional(), + ...ExeSQLFormSchema, +}); + +export function useSubmitForm() { + const { testDbConnect, loading } = useTestDbConnect(); + + const onSubmit = useCallback( + async (data: z.infer) => { + testDbConnect(data); + }, + [testDbConnect], + ); + + return { loading, onSubmit }; +} diff --git a/web/src/pages/agent/form/tool-form/constant.tsx b/web/src/pages/agent/form/tool-form/constant.tsx index 7adcb93a1..acbd2171c 100644 --- a/web/src/pages/agent/form/tool-form/constant.tsx +++ b/web/src/pages/agent/form/tool-form/constant.tsx @@ -5,7 +5,6 @@ import BingForm from '../bing-form'; import DeepLForm from '../deepl-form'; import DuckDuckGoForm from '../duckduckgo-form'; import EmailForm from '../email-form'; -import ExeSQLForm from '../exesql-form'; import GithubForm from '../github-form'; import GoogleForm from '../google-form'; import GoogleScholarForm from '../google-scholar-form'; @@ -13,6 +12,7 @@ import PubMedForm from '../pubmed-form'; import WikipediaForm from '../wikipedia-form'; import YahooFinanceForm from '../yahoo-finance-form'; import CrawlerForm from './crawler-form'; +import ExeSQLForm from './exesql-form'; import RetrievalForm from './retrieval-form'; import TavilyForm from './tavily-form'; diff --git a/web/src/pages/agent/form/tool-form/crawler-form/index.tsx b/web/src/pages/agent/form/tool-form/crawler-form/index.tsx index 338a492c3..d6182b997 100644 --- a/web/src/pages/agent/form/tool-form/crawler-form/index.tsx +++ b/web/src/pages/agent/form/tool-form/crawler-form/index.tsx @@ -2,6 +2,7 @@ import { Form } from '@/components/ui/form'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; +import { FormWrapper } from '../../components/form-wrapper'; import { CrawlerExtractTypeFormField, CrawlerFormSchema, @@ -27,15 +28,10 @@ const CrawlerForm = () => { return (
- { - e.preventDefault(); - }} - > + - +
); }; diff --git a/web/src/pages/agent/form/tool-form/exesql-form/index.tsx b/web/src/pages/agent/form/tool-form/exesql-form/index.tsx new file mode 100644 index 000000000..55721ef79 --- /dev/null +++ b/web/src/pages/agent/form/tool-form/exesql-form/index.tsx @@ -0,0 +1,39 @@ +import { Form } from '@/components/ui/form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { FormWrapper } from '../../components/form-wrapper'; +import { ExeSQLFormWidgets } from '../../exesql-form'; +import { + ExeSQLFormSchema, + useSubmitForm, +} from '../../exesql-form/use-submit-form'; +import { useValues } from '../use-values'; +import { useWatchFormChange } from '../use-watch-change'; + +const FormSchema = z.object(ExeSQLFormSchema); + +type FormType = z.infer; + +const ExeSQLForm = () => { + const { onSubmit, loading } = useSubmitForm(); + + const defaultValues = useValues(); + + const form = useForm({ + resolver: zodResolver(FormSchema), + defaultValues: defaultValues as FormType, + }); + + useWatchFormChange(form); + + return ( +
+ + + +
+ ); +}; + +export default ExeSQLForm; diff --git a/web/src/pages/agent/form/tool-form/use-values.ts b/web/src/pages/agent/form/tool-form/use-values.ts index fd97f0768..59a2e090f 100644 --- a/web/src/pages/agent/form/tool-form/use-values.ts +++ b/web/src/pages/agent/form/tool-form/use-values.ts @@ -1,6 +1,7 @@ import { isEmpty } from 'lodash'; import { useMemo } from 'react'; -import { DefaultAgentToolValuesMap } from '../../constant'; +import { Operator } from '../../constant'; +import { useAgentToolInitialValues } from '../../hooks/use-agent-tool-initial-values'; import useGraphStore from '../../store'; import { getAgentNodeTools } from '../../utils'; @@ -18,6 +19,7 @@ export function useValues() { const { clickedToolId, clickedNodeId, findUpstreamNodeById } = useGraphStore( (state) => state, ); + const { initializeAgentToolValues } = useAgentToolInitialValues(); const values = useMemo(() => { const agentNode = findUpstreamNodeById(clickedNodeId); @@ -28,10 +30,9 @@ export function useValues() { )?.params; if (isEmpty(formData)) { - const defaultValues = - DefaultAgentToolValuesMap[ - clickedToolId as keyof typeof DefaultAgentToolValuesMap - ]; + const defaultValues = initializeAgentToolValues( + clickedNodeId as Operator, + ); return defaultValues; } @@ -39,7 +40,12 @@ export function useValues() { return { ...formData, }; - }, [clickedNodeId, clickedToolId, findUpstreamNodeById]); + }, [ + clickedNodeId, + clickedToolId, + findUpstreamNodeById, + initializeAgentToolValues, + ]); return values; } diff --git a/web/src/pages/agent/hooks/use-add-node.ts b/web/src/pages/agent/hooks/use-add-node.ts index b01089329..acd7811ca 100644 --- a/web/src/pages/agent/hooks/use-add-node.ts +++ b/web/src/pages/agent/hooks/use-add-node.ts @@ -135,7 +135,7 @@ export const useInitializeOperatorParams = () => { [initialFormValuesMap], ); - return initializeOperatorParams; + return { initializeOperatorParams, initialFormValuesMap }; }; export const useGetNodeName = () => { @@ -287,7 +287,7 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance) { (state) => state, ); const getNodeName = useGetNodeName(); - const initializeOperatorParams = useInitializeOperatorParams(); + const { initializeOperatorParams } = useInitializeOperatorParams(); const { calculateNewlyBackChildPosition } = useCalculateNewlyChildPosition(); const { addChildEdge } = useAddChildEdge(); const { addToolNode } = useAddToolNode(); 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 new file mode 100644 index 000000000..24ff6e2b4 --- /dev/null +++ b/web/src/pages/agent/hooks/use-agent-tool-initial-values.ts @@ -0,0 +1,34 @@ +import { omit } from 'lodash'; +import { useCallback } from 'react'; +import { Operator } from '../constant'; +import { useInitializeOperatorParams } from './use-add-node'; + +export function useAgentToolInitialValues() { + const { initialFormValuesMap } = useInitializeOperatorParams(); + + const initializeAgentToolValues = useCallback( + (operatorName: Operator) => { + const initialValues = initialFormValuesMap[operatorName]; + + switch (operatorName) { + case Operator.Retrieval: + return { + ...omit(initialValues, 'query'), + description: '', + }; + case Operator.TavilySearch: + return { + api_key: '', + }; + case Operator.ExeSQL: + return omit(initialValues, 'query'); + + default: + return initialValues; + } + }, + [initialFormValuesMap], + ); + + return { initializeAgentToolValues }; +}