diff --git a/web/src/app/home/bots/components/bot-form/BotForm.tsx b/web/src/app/home/bots/components/bot-form/BotForm.tsx index f6aa21c0..d493b8d1 100644 --- a/web/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web/src/app/home/bots/components/bot-form/BotForm.tsx @@ -115,7 +115,6 @@ export default function BotForm({ useEffect(() => { setBotFormValues(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); function setBotFormValues() { diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index b9489668..76f232e4 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -65,7 +65,6 @@ export default function HomeSidebar({ console.error('Failed to fetch GitHub star count:', error); }); return () => console.log('sidebar.unmounted'); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); function handleChildClick(child: SidebarChildVO) { diff --git a/web/src/app/home/plugins/mcp-server/MCPServerComponent.tsx b/web/src/app/home/plugins/mcp-server/MCPServerComponent.tsx index 83fc0851..1647b3cf 100644 --- a/web/src/app/home/plugins/mcp-server/MCPServerComponent.tsx +++ b/web/src/app/home/plugins/mcp-server/MCPServerComponent.tsx @@ -2,28 +2,9 @@ import { useEffect, useState } from 'react'; import styles from '@/app/home/plugins/plugins.module.css'; -// import { MCPMarketCardVO } from '@/app/home/plugins/mcp-market/mcp-market-card/MCPMarketCardVO'; -// import MCPMarketCardComponent from '@/app/home/plugins/mcp-market/mcp-market-card/MCPMarketCardComponent'; import MCPCardComponent from '@/app/home/plugins/mcp/mcp-card/MCPCardComponent'; import { MCPCardVO } from '@/app/home/plugins/mcp/MCPCardVO'; -// import { spaceClient } from '@/app/infra/http/HttpClient'; import { useTranslation } from 'react-i18next'; -// import { Input } from '@/components/ui/input'; -// import { -// Pagination, -// PaginationContent, -// PaginationItem, -// PaginationLink, -// PaginationNext, -// PaginationPrevious, -// } from '@/components/ui/pagination'; -// import { -// Select, -// SelectContent, -// SelectItem, -// SelectTrigger, -// SelectValue, -// } from '@/components/ui/select'; import { httpClient } from '@/app/infra/http/HttpClient'; @@ -36,33 +17,20 @@ export default function MCPMarketComponent({ toolsCountCache?: Record; }) { const { t } = useTranslation(); - // const [marketServerList, setMarketServerList] = useState( - // [], - // ); const [installedServers, setInstalledServers] = useState([]); - // const [totalCount, setTotalCount] = useState(0); - // const [nowPage, setNowPage] = useState(1); - // const [searchKeyword, setSearchKeyword] = useState(''); const [loading, setLoading] = useState(false); - // const [sortByValue, setSortByValue] = useState('pushed_at'); - // const [sortOrderValue, setSortOrderValue] = useState('DESC'); - // const searchTimeout = useRef(null); - // const pageSize = 12; + useEffect(() => { initData(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // 当工具数量缓存变化时,重新获取服务器列表 useEffect(() => { fetchInstalledServers(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [toolsCountCache]); function initData() { fetchInstalledServers(); - // getServerList(); // GitHub 市场功能暂时注释 } function fetchInstalledServers() { @@ -72,7 +40,7 @@ export default function MCPMarketComponent({ .then((resp) => { const servers = resp.servers.map((server) => { const vo = new MCPCardVO(server); - // 如果缓存中有工具数量,使用缓存值覆盖 + if (toolsCountCache[server.name] !== undefined) { vo.tools = toolsCountCache[server.name]; } @@ -87,75 +55,7 @@ export default function MCPMarketComponent({ }); } - // GitHub 市场功能暂时注释 - // function onInputSearchKeyword(keyword: string) { - // setSearchKeyword(keyword); - // if (searchTimeout.current) { - // clearTimeout(searchTimeout.current); - // } - // searchTimeout.current = setTimeout(() => { - // setNowPage(1); - // getServerList(1, keyword); - // }, 500); - // } - - // function getServerList( - // page: number = nowPage, - // keyword: string = searchKeyword, - // sortBy: string = sortByValue, - // sortOrder: string = sortOrderValue, - // ) { - // // GitHub 安装功能暂时注释 - // // spaceClient - // // .getMCPMarketServers(page, pageSize, keyword, sortBy, sortOrder) - // // .then((res) => { - // // setMarketServerList( - // // res.servers.map((marketServer) => { - // // let repository = marketServer.repository; - // // if (repository.startsWith('https://github.com/')) { - // // repository = repository.replace('https://github.com/', ''); - // // } - // // if (repository.startsWith('github.com/')) { - // // repository = repository.replace('github.com/', ''); - // // } - // // const author = repository.split('/')[0]; - // // const name = repository.split('/')[1]; - // // return new MCPMarketCardVO({ - // // author: author, - // // description: marketServer.description, - // // githubURL: `https://github.com/${repository}`, - // // name: name, - // // serverId: String(marketServer.ID), - // // starCount: marketServer.stars, - // // version: - // // 'version' in marketServer - // // ? String(marketServer.version) - // // : '1.0.0', - // // }); - // // }), - // // ); - // // setTotalCount(res.total); - // // setLoading(false); - // // console.log('market servers:', res); - // // }) - // // .catch((error) => { - // // console.error(t('mcp.getServerListError'), error); - // // setLoading(false); - // // }); - // } - - // function handlePageChange(page: number) { - // setNowPage(page); - // getServerList(page); - // } - - // function handleSortChange(value: string) { - // const [newSortBy, newSortOrder] = value.split(',').map((s) => s.trim()); - // setSortByValue(newSortBy); - // setSortOrderValue(newSortOrder); - // setNowPage(1); - // getServerList(1, searchKeyword, newSortBy, newSortOrder); - // } + return (
@@ -187,126 +87,6 @@ export default function MCPMarketComponent({ )}
- - {/* GitHub 市场功能暂时注释 */} - {/*
- onInputSearchKeyword(e.target.value)} - /> - - - -
- {totalCount > 0 && ( - - - - handlePageChange(nowPage - 1)} - className={ - nowPage <= 1 ? 'pointer-events-none opacity-50' : '' - } - /> - - - {(() => { - const totalPages = Math.ceil(totalCount / pageSize); - const maxVisiblePages = 5; - let startPage = Math.max( - 1, - nowPage - Math.floor(maxVisiblePages / 2), - ); - const endPage = Math.min( - totalPages, - startPage + maxVisiblePages - 1, - ); - - if (endPage - startPage + 1 < maxVisiblePages) { - startPage = Math.max(1, endPage - maxVisiblePages + 1); - } - - return Array.from( - { length: endPage - startPage + 1 }, - (_, i) => { - const pageNum = startPage + i; - return ( - - handlePageChange(pageNum)} - > - - {pageNum} - - - - ); - }, - ); - })()} - - - handlePageChange(nowPage + 1)} - className={ - nowPage >= Math.ceil(totalCount / pageSize) - ? 'pointer-events-none opacity-50' - : '' - } - /> - - - - )} -
-
- -
- {loading ? ( -
- {t('mcp.loading')} -
- ) : marketServerList.length === 0 ? ( -
- {t('mcp.noMatchingServers')} -
- ) : ( - marketServerList.map((vo, index) => ( -
- { - askInstallServer(githubURL); - }} - /> -
- )) - )} -
*/} ); } diff --git a/web/src/app/home/plugins/mcp/MCPCardVO.ts b/web/src/app/home/plugins/mcp/MCPCardVO.ts index 67330b61..43982a58 100644 --- a/web/src/app/home/plugins/mcp/MCPCardVO.ts +++ b/web/src/app/home/plugins/mcp/MCPCardVO.ts @@ -13,11 +13,9 @@ export class MCPCardVO { this.name = data.name; this.mode = data.mode; this.enable = data.enable; - // 将后端返回的 "enabled" 状态映射为 "connected" - this.status = (data.status as string) === 'enabled' - ? 'connected' - : data.status; - // tools可能是数组或数字 + + this.status = + (data.status as string) === 'enabled' ? 'connected' : data.status; this.tools = Array.isArray(data.tools) ? data.tools.length : data.tools || 0; diff --git a/web/src/app/home/plugins/mcp/MCPComponent.tsx b/web/src/app/home/plugins/mcp/MCPComponent.tsx index b89b743f..00cf6619 100644 --- a/web/src/app/home/plugins/mcp/MCPComponent.tsx +++ b/web/src/app/home/plugins/mcp/MCPComponent.tsx @@ -30,7 +30,6 @@ export interface MCPComponentRef { createServer: () => void; } -// eslint-disable-next-line react/display-name const MCPComponent = forwardRef((_props, ref) => { const { t } = useTranslation(); const [serverList, setServerList] = useState([]); @@ -40,12 +39,10 @@ const MCPComponent = forwardRef((_props, ref) => { const [serverToDelete, setServerToDelete] = useState(null); const [deleting, setDeleting] = useState(false); const [autoTestTriggered, setAutoTestTriggered] = useState(false); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const [testingServers, setTestingServers] = useState>(new Set()); useEffect(() => { initData(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); function initData() { @@ -274,7 +271,6 @@ const MCPComponent = forwardRef((_props, ref) => { )} - {/* 编辑配置对话框 */} @@ -298,7 +294,7 @@ const MCPComponent = forwardRef((_props, ref) => { - {/* 删除确认对话框 */} + diff --git a/web/src/app/home/plugins/mcp/mcp-card/MCPCardComponent.tsx b/web/src/app/home/plugins/mcp/mcp-card/MCPCardComponent.tsx index 1dd5cb2e..2cffe435 100644 --- a/web/src/app/home/plugins/mcp/mcp-card/MCPCardComponent.tsx +++ b/web/src/app/home/plugins/mcp/mcp-card/MCPCardComponent.tsx @@ -24,7 +24,6 @@ export default function MCPCardComponent({ const [status, setStatus] = useState(cardVO.status); const [error, setError] = useState(cardVO.error); - // 响应cardVO的变化,更新本地状态 useEffect(() => { console.log(`[MCPCard ${cardVO.name}] Status updated:`, { status: cardVO.status, diff --git a/web/src/app/home/plugins/mcp/mcp-form/MCPForm.tsx b/web/src/app/home/plugins/mcp/mcp-form/MCPForm.tsx index 33ee60b3..2e990235 100644 --- a/web/src/app/home/plugins/mcp/mcp-form/MCPForm.tsx +++ b/web/src/app/home/plugins/mcp/mcp-form/MCPForm.tsx @@ -43,7 +43,6 @@ export default function MCPForm({ if (isEdit && serverName) { loadServerConfig(); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [isEdit, serverName]); async function loadServerConfig() { diff --git a/web/src/app/home/plugins/page.tsx b/web/src/app/home/plugins/page.tsx index 8793daa3..a6948536 100644 --- a/web/src/app/home/plugins/page.tsx +++ b/web/src/app/home/plugins/page.tsx @@ -46,7 +46,7 @@ import { SelectValue, SelectContent, SelectItem, -} from "@/components/ui/select" +} from '@/components/ui/select'; import { Resolver, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -88,81 +88,52 @@ export default function PluginConfigPage() { const addExtraArg = () => { setExtraArgs([...extraArgs, { key: '', type: 'string', value: '' }]); }; - const getExtraArgSchema = (t: (key: string) => string) => - z - .object({ - key: z.string().min(1, { message: t('models.keyNameRequired') }), - type: z.enum(['string', 'number', 'boolean']), - value: z.string(), - }) - .superRefine((data, ctx) => { - if (data.type === 'number' && isNaN(Number(data.value))) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: t('models.mustBeValidNumber'), - path: ['value'], - }); - } - if ( - data.type === 'boolean' && - data.value !== 'true' && - data.value !== 'false' - ) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: t('models.mustBeTrueOrFalse'), - path: ['value'], - }); - } - }); const removeExtraArg = (index: number) => { const newArgs = extraArgs.filter((_, i) => i !== index); setExtraArgs(newArgs); form.setValue('extra_args', newArgs); }; const getFormSchema = (t: (key: string) => string) => - z.object({ - name: z.string({ required_error: t('mcp.nameRequired') }), - timeout: z - .number({ invalid_type_error: t('mcp.timeoutMustBeNumber') }) - .nonnegative({ message: t('mcp.timeoutNonNegative') }) - .default(30), - ssereadtimeout: z - .number({ invalid_type_error: t('mcp.sseTimeoutMustBeNumber') }) - .nonnegative({ message: t('mcp.sseTimeoutNonNegative') }) - .default(300), - url: z.string({ required_error: t('models.requestURLRequired') }), - extra_args: z - .array( - z.object({ - key: z.string(), - type: z.enum(['string', 'number', 'boolean']), - value: z.string(), - }) - ) - .optional(), + z.object({ + name: z.string({ required_error: t('mcp.nameRequired') }), + timeout: z + .number({ invalid_type_error: t('mcp.timeoutMustBeNumber') }) + .nonnegative({ message: t('mcp.timeoutNonNegative') }) + .default(30), + ssereadtimeout: z + .number({ invalid_type_error: t('mcp.sseTimeoutMustBeNumber') }) + .nonnegative({ message: t('mcp.sseTimeoutNonNegative') }) + .default(300), + url: z.string({ required_error: t('models.requestURLRequired') }), + extra_args: z + .array( + z.object({ + key: z.string(), + type: z.enum(['string', 'number', 'boolean']), + value: z.string(), + }), + ) + .optional(), + }); + + const formSchema = getFormSchema(t); + + type FormValues = z.infer & { + timeout: number; + ssereadtimeout: number; + }; + + const form = useForm({ + resolver: zodResolver(formSchema) as unknown as Resolver, + defaultValues: { + name: '', + url: '', + timeout: 30, + ssereadtimeout: 300, + extra_args: [], + }, }); -const formSchema = getFormSchema(t); - - -type FormValues = z.infer & { - timeout: number; - ssereadtimeout: number; -}; - -const form = useForm({ - resolver: zodResolver(formSchema) as unknown as Resolver, - defaultValues: { - name: '', - url: '', - timeout: 30, - ssereadtimeout: 300, - extra_args: [], - }, -}); - - const [extraArgs, setExtraArgs] = useState< { key: string; type: 'string' | 'number' | 'boolean'; value: string }[] >([]); @@ -223,52 +194,8 @@ const form = useForm({ } }); }, 1000); - } - function watchTestMCPTask(taskId: number) { - let alreadyHandled = false; - console.log('Watching MCP test task:', taskId); - - const interval = setInterval(() => { - httpClient.getAsyncTask(taskId).then((resp) => { - console.log('task status:', resp); - - // 若任务已完成 - if (resp.runtime && resp.runtime.done) { - clearInterval(interval); - - if (resp.runtime.exception) { - // 任务失败 - toast.error(`测试失败: ${resp.runtime.exception}`); - } else if (resp.runtime.result) { - // 任务成功 - const result = resp.runtime.result as { - status?: string; - tools_count?: number; - tools_names_lists?: string[]; - error?: string; - }; - const names = result.tools_names_lists || []; - - if (!alreadyHandled) { - alreadyHandled = true; - const names = result.tools_names_lists || []; - toast.success(`连接成功,找到 ${names.length} 个工具`); - console.log('工具列表:', names); - } - } else { - // 没结果但标记为完成 - toast.error('测试任务完成但未返回结果'); - } - } - }).catch((err) => { - console.error('任务状态获取失败:', err); - toast.error('获取任务状态失败'); - clearInterval(interval); - }); - }, 1000); -} const pluginInstalledRef = useRef(null); const mcpComponentRef = useRef(null); @@ -280,12 +207,16 @@ const form = useForm({ const [refreshKey, setRefreshKey] = useState(0); // MCP测试结果状态 - const [mcpTestStatus, setMcpTestStatus] = useState<'idle' | 'testing' | 'success' | 'failed'>('idle'); + const [mcpTestStatus, setMcpTestStatus] = useState< + 'idle' | 'testing' | 'success' | 'failed' + >('idle'); const [mcpToolNames, setMcpToolNames] = useState([]); const [mcpTestError, setMcpTestError] = useState(''); // 缓存每个服务器测试后的工具数量 - const [serverToolsCache, setServerToolsCache] = useState>({}); + const [serverToolsCache, setServerToolsCache] = useState< + Record + >({}); // 强制清理 body 样式以修复 Dialog 关闭后点击失效的问题 useEffect(() => { @@ -297,11 +228,9 @@ const form = useForm({ if (!mcpSSEModalOpen && !modalOpen && !showDeleteConfirmModal) { const cleanup = () => { - document.body.style.removeProperty('pointer-events'); document.body.style.removeProperty('overflow'); - if (document.body.style.pointerEvents === 'none') { document.body.style.pointerEvents = ''; } @@ -344,7 +273,6 @@ const form = useForm({ } }, [mcpSSEModalOpen, modalOpen, showDeleteConfirmModal]); - useEffect(() => { const interval = setInterval(() => { if (!mcpSSEModalOpen && !modalOpen && !showDeleteConfirmModal) { @@ -391,51 +319,52 @@ const form = useForm({ }, [mcpSSEModalOpen, modalOpen, showDeleteConfirmModal]); function handleModalConfirm() { - installPlugin(installSource, installInfo as Record); // eslint-disable-line @typescript-eslint/no-explicit-any - } - function installPlugin( - installSource: string, - installInfo: Record, // eslint-disable-line @typescript-eslint/no-explicit-any - ) { - setPluginInstallStatus(PluginInstallStatus.INSTALLING); - if (installSource === 'github') { - httpClient - .installPluginFromGithub(installInfo.url) - .then((resp) => { - const taskId = resp.task_id; - watchTask(taskId); - }) - .catch((err) => { - console.log('error when install plugin:', err); - setInstallError(err.message); - setPluginInstallStatus(PluginInstallStatus.ERROR); - }); - } else if (installSource === 'local') { - httpClient - .installPluginFromLocal(installInfo.file) - .then((resp) => { - const taskId = resp.task_id; - watchTask(taskId); - }) - .catch((err) => { - console.log('error when install plugin:', err); - setInstallError(err.message); - setPluginInstallStatus(PluginInstallStatus.ERROR); - }); - } else if (installSource === 'marketplace') { - httpClient - .installPluginFromMarketplace( - installInfo.plugin_author, - installInfo.plugin_name, - installInfo.plugin_version, - ) - .then((resp) => { - const taskId = resp.task_id; - watchTask(taskId); - }); - } + installPlugin(installSource, installInfo as Record); } + const installPlugin = useCallback( + (installSource: string, installInfo: Record) => { + setPluginInstallStatus(PluginInstallStatus.INSTALLING); + if (installSource === 'github') { + httpClient + .installPluginFromGithub((installInfo as { url: string }).url) + .then((resp) => { + const taskId = resp.task_id; + watchTask(taskId); + }) + .catch((err) => { + console.log('error when install plugin:', err); + setInstallError(err.message); + setPluginInstallStatus(PluginInstallStatus.ERROR); + }); + } else if (installSource === 'local') { + httpClient + .installPluginFromLocal((installInfo as { file: File }).file) + .then((resp) => { + const taskId = resp.task_id; + watchTask(taskId); + }) + .catch((err) => { + console.log('error when install plugin:', err); + setInstallError(err.message); + setPluginInstallStatus(PluginInstallStatus.ERROR); + }); + } else if (installSource === 'marketplace') { + httpClient + .installPluginFromMarketplace( + (installInfo as { plugin_author: string }).plugin_author, + (installInfo as { plugin_name: string }).plugin_name, + (installInfo as { plugin_version: string }).plugin_version, + ) + .then((resp) => { + const taskId = resp.task_id; + watchTask(taskId); + }); + } + }, + [watchTask], + ); + async function deleteMCPServer() { if (!editingServerName) return; @@ -464,121 +393,134 @@ const form = useForm({ async function loadServerForEdit(serverName: string) { try { const resp = await httpClient.getMCPServer(serverName); - const server = resp.server ?? resp; + const server = resp.server ?? resp; console.log('Loaded server for edit:', server); - + const extraArgs = server.extra_args as + | Record + | undefined; form.setValue('name', server.name); - form.setValue('url', server.extra_args?.url || ''); - form.setValue('timeout', server.extra_args?.timeout || 30); - form.setValue('ssereadtimeout', server.extra_args?.ssereadtimeout || 300); + form.setValue('url', (extraArgs?.url as string) || ''); + form.setValue('timeout', (extraArgs?.timeout as number) || 30); + form.setValue( + 'ssereadtimeout', + (extraArgs?.ssereadtimeout as number) || 300, + ); - - if (server.extra_args?.headers) { - const headers = Object.entries(server.extra_args.headers).map( - ([key, value]) => ({ - key, - type: 'string' as const, - value: String(value), - }), - ); + if (extraArgs?.headers) { + const headers = Object.entries( + extraArgs.headers as Record, + ).map(([key, value]) => ({ + key, + type: 'string' as const, + value: String(value), + })); setExtraArgs(headers); form.setValue('extra_args', headers); } - setMcpTestStatus('testing'); setMcpToolNames([]); setMcpTestError(''); - setEditingServerName(serverName); setIsEditMode(true); setMcpSSEModalOpen(true); - try { const res = await httpClient.testMCPServer(server.name); if (res.task_id) { const taskId = res.task_id; - const interval = setInterval(() => { - httpClient.getAsyncTask(taskId).then((taskResp) => { - console.log('Task response:', taskResp); + httpClient + .getAsyncTask(taskId) + .then((taskResp) => { + console.log('Task response:', taskResp); - if (taskResp.runtime && taskResp.runtime.done) { - clearInterval(interval); + if (taskResp.runtime && taskResp.runtime.done) { + clearInterval(interval); - console.log('Task completed. Runtime:', taskResp.runtime); - console.log('Result:', taskResp.runtime.result); - console.log('Exception:', taskResp.runtime.exception); + console.log('Task completed. Runtime:', taskResp.runtime); + console.log('Result:', taskResp.runtime.result); + console.log('Exception:', taskResp.runtime.exception); - if (taskResp.runtime.exception) { - - console.log('Test failed with exception'); - setMcpTestStatus('failed'); - setMcpToolNames([]); - setMcpTestError(taskResp.runtime.exception || '未知错误'); - } else if (taskResp.runtime.result) { - - try { - let result: { - status?: string; - tools_count?: number; - tools_names_lists?: string[]; - error?: string; - }; - - - const rawResult: any = taskResp.runtime.result; - if (typeof rawResult === 'string') { - console.log('Result is string, parsing...'); - result = JSON.parse(rawResult.replace(/'/g, '"')); - } else { - result = rawResult as typeof result; - } - - console.log('Parsed result:', result); - console.log('tools_names_lists:', result.tools_names_lists); - console.log('tools_names_lists length:', result.tools_names_lists?.length); - - if (result.tools_names_lists && result.tools_names_lists.length > 0) { - console.log('Test success with', result.tools_names_lists.length, 'tools'); - setMcpTestStatus('success'); - setMcpToolNames(result.tools_names_lists); - // 保存工具数量到缓存 - setServerToolsCache(prev => ({ - ...prev, - [server.name]: result.tools_names_lists!.length - })); - } else { - console.log('Test failed: no tools found'); - setMcpTestStatus('failed'); - setMcpToolNames([]); - setMcpTestError('未找到任何工具'); - } - } catch (parseError) { - console.error('Failed to parse result:', parseError); + if (taskResp.runtime.exception) { + console.log('Test failed with exception'); setMcpTestStatus('failed'); setMcpToolNames([]); - setMcpTestError('解析测试结果失败'); + setMcpTestError(taskResp.runtime.exception || '未知错误'); + } else if (taskResp.runtime.result) { + try { + let result: { + status?: string; + tools_count?: number; + tools_names_lists?: string[]; + error?: string; + }; + + const rawResult: unknown = taskResp.runtime.result; + if (typeof rawResult === 'string') { + console.log('Result is string, parsing...'); + result = JSON.parse(rawResult.replace(/'/g, '"')); + } else { + result = rawResult as typeof result; + } + + console.log('Parsed result:', result); + console.log( + 'tools_names_lists:', + result.tools_names_lists, + ); + console.log( + 'tools_names_lists length:', + result.tools_names_lists?.length, + ); + + if ( + result.tools_names_lists && + result.tools_names_lists.length > 0 + ) { + console.log( + 'Test success with', + result.tools_names_lists.length, + 'tools', + ); + setMcpTestStatus('success'); + setMcpToolNames(result.tools_names_lists); + // 保存工具数量到缓存 + setServerToolsCache((prev) => ({ + ...prev, + [server.name]: result.tools_names_lists!.length, + })); + } else { + console.log('Test failed: no tools found'); + setMcpTestStatus('failed'); + setMcpToolNames([]); + setMcpTestError('未找到任何工具'); + } + } catch (parseError) { + console.error('Failed to parse result:', parseError); + setMcpTestStatus('failed'); + setMcpToolNames([]); + setMcpTestError('解析测试结果失败'); + } + } else { + // 没结果 + console.log('Test failed: no result'); + setMcpTestStatus('failed'); + setMcpToolNames([]); + setMcpTestError('测试未返回结果'); } - } else { - // 没结果 - console.log('Test failed: no result'); - setMcpTestStatus('failed'); - setMcpToolNames([]); - setMcpTestError('测试未返回结果'); } - } - }).catch((err) => { - console.error('获取任务状态失败:', err); - clearInterval(interval); - setMcpTestStatus('failed'); - setMcpToolNames([]); - setMcpTestError(err.message || '获取任务状态失败'); - }); + }) + .catch((err) => { + console.error('获取任务状态失败:', err); + clearInterval(interval); + setMcpTestStatus('failed'); + setMcpToolNames([]); + setMcpTestError(err.message || '获取任务状态失败'); + }); }, 1000); } else { setMcpTestStatus('failed'); @@ -624,7 +566,6 @@ const form = useForm({ }; if (isEditMode && editingServerName) { - await httpClient.updateMCPServer(editingServerName, serverConfig); toast.success(t('mcp.updateSuccess')); } else { @@ -697,7 +638,7 @@ const form = useForm({ setInstallError(null); installPlugin('local', { file }); }, - [t, pluginSystemStatus], + [t, pluginSystemStatus, installPlugin], ); const handleFileSelect = useCallback(() => { @@ -712,7 +653,7 @@ const form = useForm({ if (file) { uploadPluginFile(file); } - + event.target.value = ''; }, [uploadPluginFile], @@ -754,7 +695,6 @@ const form = useForm({ [uploadPluginFile, isPluginSystemReady, t], ); - const renderPluginDisabledState = () => (
@@ -767,7 +707,6 @@ const form = useForm({
); - const renderPluginConnectionErrorState = () => (
({ } }} > - @@ -1113,7 +1051,8 @@ const form = useForm({ /> - {t('mcp.connectionSuccess')} - {mcpToolNames.length} {t('mcp.toolsFound')} + {t('mcp.connectionSuccess')} - {mcpToolNames.length}{' '} + {t('mcp.toolsFound')}
@@ -1146,7 +1085,9 @@ const form = useForm({ d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /> - {t('mcp.connectionFailed')} + + {t('mcp.connectionFailed')} +
{mcpTestError && (
@@ -1217,7 +1158,9 @@ const form = useForm({ type="number" placeholder={t('mcp.sseTimeoutDescription')} {...field} - onChange={(e) => field.onChange(Number(e.target.value))} + onChange={(e) => + field.onChange(Number(e.target.value)) + } /> @@ -1225,7 +1168,6 @@ const form = useForm({ )} /> - {t('models.extraParameters')}
diff --git a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx index 5581fc7a..3ef8b748 100644 --- a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx +++ b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx @@ -61,7 +61,6 @@ const PluginInstalledComponent = forwardRef( useEffect(() => { initData(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); function initData() { diff --git a/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx b/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx index dbde8774..b9835253 100644 --- a/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx +++ b/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx @@ -172,7 +172,6 @@ function MarketPageContent({ // 初始加载 useEffect(() => { fetchPlugins(1, false, true); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // 搜索功能 diff --git a/web/src/app/infra/entities/api/index.ts b/web/src/app/infra/entities/api/index.ts index 677c91ab..8a6c85cb 100644 --- a/web/src/app/infra/entities/api/index.ts +++ b/web/src/app/infra/entities/api/index.ts @@ -319,7 +319,7 @@ export interface ApiRespMCPServer { } export interface MCPServer { - extra_args: any; + extra_args: Record; name: string; mode: 'stdio' | 'sse'; enable: boolean; diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index bd0a6513..d473a578 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -353,7 +353,7 @@ const zhHans = { sseTimeout: 'SSE超时时间', sseTimeoutDescription: '用于建立SSE连接的超时时间', extraParametersDescription: '额外参数,用于配置MCP服务器的特定行为', - updateSuccess:'更新成功', + updateSuccess: '更新成功', }, pipelines: { title: '流水线',