fix: run lint

This commit is contained in:
WangCham
2025-10-28 16:14:31 +08:00
parent 4d3610cdf7
commit d32f783392
12 changed files with 207 additions and 497 deletions

View File

@@ -115,7 +115,6 @@ export default function BotForm({
useEffect(() => {
setBotFormValues();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
function setBotFormValues() {

View File

@@ -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) {

View File

@@ -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<string, number>;
}) {
const { t } = useTranslation();
// const [marketServerList, setMarketServerList] = useState<MCPMarketCardVO[]>(
// [],
// );
const [installedServers, setInstalledServers] = useState<MCPCardVO[]>([]);
// const [totalCount, setTotalCount] = useState(0);
// const [nowPage, setNowPage] = useState(1);
// const [searchKeyword, setSearchKeyword] = useState('');
const [loading, setLoading] = useState(false);
// const [sortByValue, setSortByValue] = useState<string>('pushed_at');
// const [sortOrderValue, setSortOrderValue] = useState<string>('DESC');
// const searchTimeout = useRef<NodeJS.Timeout | null>(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 (
<div className={`${styles.marketComponentBody}`}>
@@ -187,126 +87,6 @@ export default function MCPMarketComponent({
)}
</div>
</div>
{/* GitHub 市场功能暂时注释 */}
{/* <div className="flex items-center justify-start mb-2 mt-2 pl-[0.8rem] pr-[0.8rem]">
<Input
style={{
width: '300px',
}}
value={searchKeyword}
placeholder={t('mcp.searchServer')}
onChange={(e) => onInputSearchKeyword(e.target.value)}
/>
<Select
value={`${sortByValue},${sortOrderValue}`}
onValueChange={handleSortChange}
>
<SelectTrigger className="w-[180px] ml-2 cursor-pointer">
<SelectValue placeholder={t('mcp.sortBy')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="stars,DESC">{t('mcp.mostStars')}</SelectItem>
<SelectItem value="created_at,DESC">
{t('mcp.recentlyAdded')}
</SelectItem>
<SelectItem value="pushed_at,DESC">
{t('mcp.recentlyUpdated')}
</SelectItem>
</SelectContent>
</Select>
<div className="flex items-center justify-end ml-2">
{totalCount > 0 && (
<Pagination>
<PaginationContent>
<PaginationItem className="cursor-pointer">
<PaginationPrevious
onClick={() => handlePageChange(nowPage - 1)}
className={
nowPage <= 1 ? 'pointer-events-none opacity-50' : ''
}
/>
</PaginationItem>
{(() => {
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 (
<PaginationItem
key={pageNum}
className="cursor-pointer"
>
<PaginationLink
isActive={pageNum === nowPage}
onClick={() => handlePageChange(pageNum)}
>
<span className="text-black select-none">
{pageNum}
</span>
</PaginationLink>
</PaginationItem>
);
},
);
})()}
<PaginationItem className="cursor-pointer">
<PaginationNext
onClick={() => handlePageChange(nowPage + 1)}
className={
nowPage >= Math.ceil(totalCount / pageSize)
? 'pointer-events-none opacity-50'
: ''
}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
)}
</div>
</div>
<div className={`${styles.pluginListContainer}`}>
{loading ? (
<div style={{ textAlign: 'center', padding: '20px' }}>
{t('mcp.loading')}
</div>
) : marketServerList.length === 0 ? (
<div style={{ textAlign: 'center', padding: '20px' }}>
{t('mcp.noMatchingServers')}
</div>
) : (
marketServerList.map((vo, index) => (
<div key={`${vo.serverId}-${index}`}>
<MCPMarketCardComponent
cardVO={vo}
installServer={(githubURL) => {
askInstallServer(githubURL);
}}
/>
</div>
))
)}
</div> */}
</div>
);
}

View File

@@ -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;

View File

@@ -30,7 +30,6 @@ export interface MCPComponentRef {
createServer: () => void;
}
// eslint-disable-next-line react/display-name
const MCPComponent = forwardRef<MCPComponentRef>((_props, ref) => {
const { t } = useTranslation();
const [serverList, setServerList] = useState<MCPCardVO[]>([]);
@@ -40,12 +39,10 @@ const MCPComponent = forwardRef<MCPComponentRef>((_props, ref) => {
const [serverToDelete, setServerToDelete] = useState<MCPCardVO | null>(null);
const [deleting, setDeleting] = useState<boolean>(false);
const [autoTestTriggered, setAutoTestTriggered] = useState<boolean>(false);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [testingServers, setTestingServers] = useState<Set<string>>(new Set());
useEffect(() => {
initData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
function initData() {
@@ -274,7 +271,6 @@ const MCPComponent = forwardRef<MCPComponentRef>((_props, ref) => {
</div>
)}
{/* 编辑配置对话框 */}
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="w-[700px] max-h-[80vh] p-0 flex flex-col">
<DialogHeader className="px-6 pt-6 pb-2">
@@ -298,7 +294,7 @@ const MCPComponent = forwardRef<MCPComponentRef>((_props, ref) => {
</DialogContent>
</Dialog>
{/* 删除确认对话框 */}
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>

View File

@@ -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,

View File

@@ -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() {

View File

@@ -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<typeof formSchema> & {
timeout: number;
ssereadtimeout: number;
};
const form = useForm<FormValues>({
resolver: zodResolver(formSchema) as unknown as Resolver<FormValues>,
defaultValues: {
name: '',
url: '',
timeout: 30,
ssereadtimeout: 300,
extra_args: [],
},
});
const formSchema = getFormSchema(t);
type FormValues = z.infer<typeof formSchema> & {
timeout: number;
ssereadtimeout: number;
};
const form = useForm<FormValues>({
resolver: zodResolver(formSchema) as unknown as Resolver<FormValues>,
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<FormValues>({
}
});
}, 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<PluginInstalledComponentRef>(null);
const mcpComponentRef = useRef<MCPComponentRef>(null);
@@ -280,12 +207,16 @@ const form = useForm<FormValues>({
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<string[]>([]);
const [mcpTestError, setMcpTestError] = useState<string>('');
// 缓存每个服务器测试后的工具数量
const [serverToolsCache, setServerToolsCache] = useState<Record<string, number>>({});
const [serverToolsCache, setServerToolsCache] = useState<
Record<string, number>
>({});
// 强制清理 body 样式以修复 Dialog 关闭后点击失效的问题
useEffect(() => {
@@ -297,11 +228,9 @@ const form = useForm<FormValues>({
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<FormValues>({
}
}, [mcpSSEModalOpen, modalOpen, showDeleteConfirmModal]);
useEffect(() => {
const interval = setInterval(() => {
if (!mcpSSEModalOpen && !modalOpen && !showDeleteConfirmModal) {
@@ -391,51 +319,52 @@ const form = useForm<FormValues>({
}, [mcpSSEModalOpen, modalOpen, showDeleteConfirmModal]);
function handleModalConfirm() {
installPlugin(installSource, installInfo as Record<string, any>); // eslint-disable-line @typescript-eslint/no-explicit-any
}
function installPlugin(
installSource: string,
installInfo: Record<string, any>, // 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<string, unknown>);
}
const installPlugin = useCallback(
(installSource: string, installInfo: Record<string, unknown>) => {
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<FormValues>({
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<string, unknown>
| 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<string, unknown>,
).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<FormValues>({
};
if (isEditMode && editingServerName) {
await httpClient.updateMCPServer(editingServerName, serverConfig);
toast.success(t('mcp.updateSuccess'));
} else {
@@ -697,7 +638,7 @@ const form = useForm<FormValues>({
setInstallError(null);
installPlugin('local', { file });
},
[t, pluginSystemStatus],
[t, pluginSystemStatus, installPlugin],
);
const handleFileSelect = useCallback(() => {
@@ -712,7 +653,7 @@ const form = useForm<FormValues>({
if (file) {
uploadPluginFile(file);
}
event.target.value = '';
},
[uploadPluginFile],
@@ -754,7 +695,6 @@ const form = useForm<FormValues>({
[uploadPluginFile, isPluginSystemReady, t],
);
const renderPluginDisabledState = () => (
<div className="flex flex-col items-center justify-center h-[60vh] text-center pt-[10vh]">
<Power className="w-16 h-16 text-gray-400 mb-4" />
@@ -767,7 +707,6 @@ const form = useForm<FormValues>({
</div>
);
const renderPluginConnectionErrorState = () => (
<div className="flex flex-col items-center justify-center h-[60vh] text-center pt-[10vh]">
<svg
@@ -1058,7 +997,6 @@ const form = useForm<FormValues>({
}
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>
@@ -1113,7 +1051,8 @@ const form = useForm<FormValues>({
/>
</svg>
<span className="font-medium">
{t('mcp.connectionSuccess')} - {mcpToolNames.length} {t('mcp.toolsFound')}
{t('mcp.connectionSuccess')} - {mcpToolNames.length}{' '}
{t('mcp.toolsFound')}
</span>
</div>
<div className="flex flex-wrap gap-1 mt-2">
@@ -1146,7 +1085,9 @@ const form = useForm<FormValues>({
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span className="font-medium">{t('mcp.connectionFailed')}</span>
<span className="font-medium">
{t('mcp.connectionFailed')}
</span>
</div>
{mcpTestError && (
<div className="text-sm text-red-500 pl-7">
@@ -1217,7 +1158,9 @@ const form = useForm<FormValues>({
type="number"
placeholder={t('mcp.sseTimeoutDescription')}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
onChange={(e) =>
field.onChange(Number(e.target.value))
}
/>
</FormControl>
<FormMessage />
@@ -1225,7 +1168,6 @@ const form = useForm<FormValues>({
)}
/>
<FormItem>
<FormLabel>{t('models.extraParameters')}</FormLabel>
<div className="space-y-2">

View File

@@ -61,7 +61,6 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
useEffect(() => {
initData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
function initData() {

View File

@@ -172,7 +172,6 @@ function MarketPageContent({
// 初始加载
useEffect(() => {
fetchPlugins(1, false, true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// 搜索功能

View File

@@ -319,7 +319,7 @@ export interface ApiRespMCPServer {
}
export interface MCPServer {
extra_args: any;
extra_args: Record<string, unknown>;
name: string;
mode: 'stdio' | 'sse';
enable: boolean;

View File

@@ -353,7 +353,7 @@ const zhHans = {
sseTimeout: 'SSE超时时间',
sseTimeoutDescription: '用于建立SSE连接的超时时间',
extraParametersDescription: '额外参数用于配置MCP服务器的特定行为',
updateSuccess:'更新成功',
updateSuccess: '更新成功',
},
pipelines: {
title: '流水线',