diff --git a/web/src/components/collapse.tsx b/web/src/components/collapse.tsx index 35e85e257..81d7f8a76 100644 --- a/web/src/components/collapse.tsx +++ b/web/src/components/collapse.tsx @@ -3,17 +3,31 @@ import { CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible'; +import { CollapsibleProps } from '@radix-ui/react-collapsible'; import { ListCollapse } from 'lucide-react'; import { PropsWithChildren, ReactNode } from 'react'; -type CollapseProps = { +type CollapseProps = Omit & { title?: ReactNode; rightContent?: ReactNode; } & PropsWithChildren; -export function Collapse({ title, children, rightContent }: CollapseProps) { +export function Collapse({ + title, + children, + rightContent, + open, + defaultOpen = true, + onOpenChange, + disabled, +}: CollapseProps) { return ( - +
diff --git a/web/src/hooks/use-mcp-request.ts b/web/src/hooks/use-mcp-request.ts index 4bf905728..bf4dd3a90 100644 --- a/web/src/hooks/use-mcp-request.ts +++ b/web/src/hooks/use-mcp-request.ts @@ -1,10 +1,13 @@ import message from '@/components/ui/message'; -import { IMcpServerListResponse, IMCPTool } from '@/interfaces/database/mcp'; +import { + IMcpServer, + IMcpServerListResponse, + IMCPTool, +} from '@/interfaces/database/mcp'; import { ITestMcpRequestBody } from '@/interfaces/request/mcp'; import i18n from '@/locales/config'; import mcpServerService from '@/services/mcp-server-service'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { useState } from 'react'; export const enum McpApiAction { ListMcpServer = 'listMcpServer', @@ -34,20 +37,19 @@ export const useListMcpServer = () => { return { data, loading }; }; -export const useGetMcpServer = () => { - const [id, setId] = useState(''); - const { data, isFetching: loading } = useQuery({ +export const useGetMcpServer = (id: string) => { + const { data, isFetching: loading } = useQuery({ queryKey: [McpApiAction.GetMcpServer, id], - initialData: {}, + initialData: {} as IMcpServer, gcTime: 0, enabled: !!id, queryFn: async () => { - const { data } = await mcpServerService.get(); + const { data } = await mcpServerService.get({ mcp_id: id }); return data?.data ?? {}; }, }); - return { data, loading, setId, id }; + return { data, loading, id }; }; export const useCreateMcpServer = () => { diff --git a/web/src/pages/profile-setting/mcp/edit-mcp-dialog.tsx b/web/src/pages/profile-setting/mcp/edit-mcp-dialog.tsx index 69687a81f..6f81e310a 100644 --- a/web/src/pages/profile-setting/mcp/edit-mcp-dialog.tsx +++ b/web/src/pages/profile-setting/mcp/edit-mcp-dialog.tsx @@ -7,15 +7,22 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; -import { useTestMcpServer } from '@/hooks/use-mcp-request'; +import { useGetMcpServer, useTestMcpServer } from '@/hooks/use-mcp-request'; import { IModalProps } from '@/interfaces/common'; import { IMCPTool, IMCPToolObject } from '@/interfaces/database/mcp'; -import { omit } from 'lodash'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { isEmpty, omit, pick } from 'lodash'; import { RefreshCw } from 'lucide-react'; -import { MouseEventHandler, useCallback, useState } from 'react'; +import { MouseEventHandler, useCallback, useMemo, useState } from 'react'; +import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { z } from 'zod'; -import { EditMcpForm, FormId, useBuildFormSchema } from './edit-mcp-form'; +import { + EditMcpForm, + FormId, + ServerType, + useBuildFormSchema, +} from './edit-mcp-form'; import { McpToolCard } from './tool-card'; function transferToolToObject(tools: IMCPTool[] = []) { @@ -25,11 +32,37 @@ function transferToolToObject(tools: IMCPTool[] = []) { }, {}); } -export function EditMcpDialog({ hideModal, loading, onOk }: IModalProps) { +function transferToolToArray(tools: IMCPToolObject) { + return Object.entries(tools).reduce((pre, [name, tool]) => { + pre.push({ ...tool, name }); + return pre; + }, []); +} + +export function EditMcpDialog({ + hideModal, + loading, + onOk, + id, +}: IModalProps & { id: string }) { const { t } = useTranslation(); - const { testMcpServer, data: tools } = useTestMcpServer(); + const { + testMcpServer, + data: tools, + loading: testLoading, + } = useTestMcpServer(); const [isTriggeredBySaving, setIsTriggeredBySaving] = useState(false); const FormSchema = useBuildFormSchema(); + const [collapseOpen, setCollapseOpen] = useState(true); + const { data } = useGetMcpServer(id); + + const form = useForm>({ + resolver: zodResolver(FormSchema), + values: isEmpty(data) + ? { name: '', server_type: ServerType.SSE, url: '' } + : pick(data, ['name', 'server_type', 'url']), + }); + console.log('🚀 ~ form:', form.formState.dirtyFields); const handleTest: MouseEventHandler = useCallback((e) => { e.stopPropagation(); @@ -54,15 +87,25 @@ export function EditMcpDialog({ hideModal, loading, onOk }: IModalProps) { } }; + const nextTools = useMemo(() => { + return tools || transferToolToArray(data.variables?.tools || {}); + }, [data.variables?.tools, tools]); + + const dirtyFields = form.formState.dirtyFields; + const fieldChanged = 'server_type' in dirtyFields || 'url' in dirtyFields; + const disabled = !!!tools?.length || testLoading || fieldChanged; + return ( Edit profile - + {tools?.length || 0} tools available
} + open={collapseOpen} + onOpenChange={setCollapseOpen} rightContent={ } > -
- {tools?.map((x) => ( +
+ {nextTools?.map((x) => ( ))}
@@ -86,7 +129,7 @@ export function EditMcpDialog({ hideModal, loading, onOk }: IModalProps) { form={FormId} loading={loading} onClick={handleSave} - disabled={!!!tools?.length} + disabled={disabled} > {t('common.save')} diff --git a/web/src/pages/profile-setting/mcp/edit-mcp-form.tsx b/web/src/pages/profile-setting/mcp/edit-mcp-form.tsx index e71cdf631..0e063743c 100644 --- a/web/src/pages/profile-setting/mcp/edit-mcp-form.tsx +++ b/web/src/pages/profile-setting/mcp/edit-mcp-form.tsx @@ -1,7 +1,6 @@ 'use client'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; +import { UseFormReturn } from 'react-hook-form'; import { z } from 'zod'; import { @@ -16,12 +15,11 @@ import { Input } from '@/components/ui/input'; import { RAGFlowSelect } from '@/components/ui/select'; import { IModalProps } from '@/interfaces/common'; import { buildOptions } from '@/utils/form'; -import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; export const FormId = 'EditMcpForm'; -enum ServerType { +export enum ServerType { SSE = 'sse', StreamableHttp = 'streamable-http', } @@ -57,28 +55,16 @@ export function useBuildFormSchema() { } export function EditMcpForm({ - initialName, + form, onOk, -}: IModalProps & { initialName?: string }) { +}: IModalProps & { form: UseFormReturn }) { const { t } = useTranslation(); - const FormSchema = useBuildFormSchema(); - const form = useForm>({ - resolver: zodResolver(FormSchema), - defaultValues: { name: '', server_type: ServerType.SSE, url: '' }, - }); - function onSubmit(data: z.infer) { onOk?.(data); } - useEffect(() => { - if (initialName) { - form.setValue('name', initialName); - } - }, [form, initialName]); - return (
))}
@@ -49,6 +51,7 @@ export default function McpServer() { )} diff --git a/web/src/pages/profile-setting/mcp/mcp-card.tsx b/web/src/pages/profile-setting/mcp/mcp-card.tsx index bf6023c97..b83c20c5e 100644 --- a/web/src/pages/profile-setting/mcp/mcp-card.tsx +++ b/web/src/pages/profile-setting/mcp/mcp-card.tsx @@ -7,15 +7,18 @@ import { isPlainObject } from 'lodash'; import { useMemo } from 'react'; import { McpDropdown } from './mcp-dropdown'; import { UseBulkOperateMCPReturnType } from './use-bulk-operate-mcp'; +import { UseEditMcpReturnType } from './use-edit-mcp'; export type DatasetCardProps = { data: IMcpServer; -} & Pick; +} & Pick & + Pick; export function McpCard({ data, selectedList, handleSelectChange, + showEditModal, }: DatasetCardProps) { const toolLength = useMemo(() => { const tools = data.variables?.tools; @@ -35,7 +38,7 @@ export function McpCard({

{data.name}

- + ) { const { t } = useTranslation(); const { deleteMcpServer } = useDeleteMcpServer(); - const handleShowAgentRenameModal: MouseEventHandler = - useCallback((e) => { - e.stopPropagation(); - }, []); - const handleDelete: MouseEventHandler = useCallback(() => { deleteMcpServer([mcpId]); }, [deleteMcpServer, mcpId]); @@ -31,7 +31,7 @@ export function McpDropdown({ {children} - + {t('common.edit')} diff --git a/web/src/pages/profile-setting/mcp/use-edit-mcp.ts b/web/src/pages/profile-setting/mcp/use-edit-mcp.ts index 903929555..94895d935 100644 --- a/web/src/pages/profile-setting/mcp/use-edit-mcp.ts +++ b/web/src/pages/profile-setting/mcp/use-edit-mcp.ts @@ -1,10 +1,9 @@ import { useSetModalState } from '@/hooks/common-hooks'; import { useCreateMcpServer, - useGetMcpServer, useUpdateMcpServer, } from '@/hooks/use-mcp-request'; -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; export const useEditMcp = () => { const { @@ -13,14 +12,13 @@ export const useEditMcp = () => { showModal: showEditModal, } = useSetModalState(); const { createMcpServer, loading } = useCreateMcpServer(); - const { data, setId, id } = useGetMcpServer(); + const [id, setId] = useState(''); + const { updateMcpServer } = useUpdateMcpServer(); const handleShowModal = useCallback( - (id?: string) => () => { - if (id) { - setId(id); - } + (id: string) => () => { + setId(id); showEditModal(); }, [setId, showEditModal], @@ -47,7 +45,9 @@ export const useEditMcp = () => { showEditModal: handleShowModal, loading, createMcpServer, - detail: data, handleOk, + id, }; }; + +export type UseEditMcpReturnType = ReturnType; diff --git a/web/src/services/mcp-server-service.ts b/web/src/services/mcp-server-service.ts index df4232813..7cb22a090 100644 --- a/web/src/services/mcp-server-service.ts +++ b/web/src/services/mcp-server-service.ts @@ -23,7 +23,7 @@ const methods = { }, get: { url: getMcpServer, - method: 'post', + method: 'get', }, create: { url: createMcpServer,