mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-19 02:46:09 +00:00
fix: delete mcp market
This commit is contained in:
+1
-1
@@ -168,7 +168,7 @@ export default function MCPMarketComponent({
|
|||||||
</div>
|
</div>
|
||||||
) : installedServers.length === 0 ? (
|
) : installedServers.length === 0 ? (
|
||||||
<div style={{ textAlign: 'center', padding: '20px' }}>
|
<div style={{ textAlign: 'center', padding: '20px' }}>
|
||||||
{t('mcp.noInstalledServers')}
|
{t('mcp.noServerInstalled')}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
installedServers.map((server, index) => (
|
installedServers.map((server, index) => (
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { MCPMarketCardVO } from '@/app/home/plugins/mcp-market/mcp-market-card/MCPMarketCardVO';
|
import { MCPMarketCardVO } from '@/app/home/plugins/mcp-server/mcp-market-card/MCPServerCardVO';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import MarketPage from '@/app/home/plugins/plugin-market/PluginMarketComponent';
|
|||||||
import MCPComponent, {
|
import MCPComponent, {
|
||||||
MCPComponentRef,
|
MCPComponentRef,
|
||||||
} from '@/app/home/plugins/mcp/MCPComponent';
|
} from '@/app/home/plugins/mcp/MCPComponent';
|
||||||
import MCPMarketComponent from '@/app/home/plugins/mcp-market/MCPMarketComponent';
|
import MCPServerComponent from '@/app/home/plugins/mcp-server/MCPServerComponent';
|
||||||
import styles from './plugins.module.css';
|
import styles from './plugins.module.css';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -48,7 +48,7 @@ import {
|
|||||||
SelectItem,
|
SelectItem,
|
||||||
} from "@/components/ui/select"
|
} from "@/components/ui/select"
|
||||||
|
|
||||||
import { useForm } from 'react-hook-form';
|
import { Resolver, useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { DialogDescription } from '@radix-ui/react-dialog';
|
import { DialogDescription } from '@radix-ui/react-dialog';
|
||||||
@@ -121,26 +121,48 @@ export default function PluginConfigPage() {
|
|||||||
form.setValue('extra_args', newArgs);
|
form.setValue('extra_args', newArgs);
|
||||||
};
|
};
|
||||||
const getFormSchema = (t: (key: string) => string) =>
|
const getFormSchema = (t: (key: string) => string) =>
|
||||||
z.object({
|
z.object({
|
||||||
name: z.string().min(1, { message: t('mcp.nameRequired') }),
|
name: z.string({ required_error: t('mcp.nameRequired') }),
|
||||||
timeout: z.number().min(30, { message: t('mcp.timeoutMin30') }),
|
timeout: z
|
||||||
ssereadtimeout: z
|
.number({ invalid_type_error: t('mcp.timeoutMustBeNumber') })
|
||||||
.number()
|
.nonnegative({ message: t('mcp.timeoutNonNegative') })
|
||||||
.min(300, { message: t('mcp.sseTimeoutMin300') }),
|
.default(30),
|
||||||
url: z.string().min(1, { message: t('mcp.requestURLRequired') }),
|
ssereadtimeout: z
|
||||||
extra_args: z.array(getExtraArgSchema(t)).optional(),
|
.number({ invalid_type_error: t('mcp.sseTimeoutMustBeNumber') })
|
||||||
});
|
.nonnegative({ message: t('mcp.sseTimeoutNonNegative') })
|
||||||
const formSchema = getFormSchema(t);
|
.default(300),
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
url: z.string({ required_error: t('models.requestURLRequired') }),
|
||||||
resolver: zodResolver(formSchema),
|
extra_args: z
|
||||||
defaultValues: {
|
.array(
|
||||||
name: '',
|
z.object({
|
||||||
url: '',
|
key: z.string(),
|
||||||
timeout: 30,
|
type: z.enum(['string', 'number', 'boolean']),
|
||||||
ssereadtimeout: 300,
|
value: z.string(),
|
||||||
extra_args: [],
|
})
|
||||||
},
|
)
|
||||||
|
.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 [extraArgs, setExtraArgs] = useState<
|
const [extraArgs, setExtraArgs] = useState<
|
||||||
{ key: string; type: 'string' | 'number' | 'boolean'; value: string }[]
|
{ key: string; type: 'string' | 'number' | 'boolean'; value: string }[]
|
||||||
>([]);
|
>([]);
|
||||||
@@ -274,24 +296,12 @@ export default function PluginConfigPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!mcpSSEModalOpen && !modalOpen && !showDeleteConfirmModal) {
|
if (!mcpSSEModalOpen && !modalOpen && !showDeleteConfirmModal) {
|
||||||
console.log(
|
|
||||||
'[Dialog Debug] All dialogs closed, cleaning up body styles...',
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
'[Dialog Debug] Before cleanup - body.style.pointerEvents:',
|
|
||||||
document.body.style.pointerEvents,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
'[Dialog Debug] Before cleanup - body.style.overflow:',
|
|
||||||
document.body.style.overflow,
|
|
||||||
);
|
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
// 强制移除 body 上可能残留的样式
|
|
||||||
document.body.style.removeProperty('pointer-events');
|
document.body.style.removeProperty('pointer-events');
|
||||||
document.body.style.removeProperty('overflow');
|
document.body.style.removeProperty('overflow');
|
||||||
|
|
||||||
// 如果 removeProperty 不起作用,强制设置为空字符串
|
|
||||||
if (document.body.style.pointerEvents === 'none') {
|
if (document.body.style.pointerEvents === 'none') {
|
||||||
document.body.style.pointerEvents = '';
|
document.body.style.pointerEvents = '';
|
||||||
}
|
}
|
||||||
@@ -334,7 +344,7 @@ export default function PluginConfigPage() {
|
|||||||
}
|
}
|
||||||
}, [mcpSSEModalOpen, modalOpen, showDeleteConfirmModal]);
|
}, [mcpSSEModalOpen, modalOpen, showDeleteConfirmModal]);
|
||||||
|
|
||||||
// 额外的全局清理:定期检查并清理
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
if (!mcpSSEModalOpen && !modalOpen && !showDeleteConfirmModal) {
|
if (!mcpSSEModalOpen && !modalOpen && !showDeleteConfirmModal) {
|
||||||
@@ -454,16 +464,16 @@ export default function PluginConfigPage() {
|
|||||||
async function loadServerForEdit(serverName: string) {
|
async function loadServerForEdit(serverName: string) {
|
||||||
try {
|
try {
|
||||||
const resp = await httpClient.getMCPServer(serverName);
|
const resp = await httpClient.getMCPServer(serverName);
|
||||||
const server = resp.server ?? resp; // 有的接口包了一层,有的直接返回对象
|
const server = resp.server ?? resp;
|
||||||
console.log('Loaded server for edit:', server);
|
console.log('Loaded server for edit:', server);
|
||||||
|
|
||||||
// 填充表单数据
|
|
||||||
form.setValue('name', server.name);
|
form.setValue('name', server.name);
|
||||||
form.setValue('url', server.extra_args?.url || '');
|
form.setValue('url', server.extra_args?.url || '');
|
||||||
form.setValue('timeout', server.extra_args?.timeout || 30);
|
form.setValue('timeout', server.extra_args?.timeout || 30);
|
||||||
form.setValue('ssereadtimeout', server.extra_args?.ssereadtimeout || 300);
|
form.setValue('ssereadtimeout', server.extra_args?.ssereadtimeout || 300);
|
||||||
|
|
||||||
// 填充 headers
|
|
||||||
if (server.extra_args?.headers) {
|
if (server.extra_args?.headers) {
|
||||||
const headers = Object.entries(server.extra_args.headers).map(
|
const headers = Object.entries(server.extra_args.headers).map(
|
||||||
([key, value]) => ({
|
([key, value]) => ({
|
||||||
@@ -476,23 +486,23 @@ export default function PluginConfigPage() {
|
|||||||
form.setValue('extra_args', headers);
|
form.setValue('extra_args', headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置测试状态
|
|
||||||
setMcpTestStatus('testing');
|
setMcpTestStatus('testing');
|
||||||
setMcpToolNames([]);
|
setMcpToolNames([]);
|
||||||
setMcpTestError('');
|
setMcpTestError('');
|
||||||
|
|
||||||
// 打开对话框
|
|
||||||
setEditingServerName(serverName);
|
setEditingServerName(serverName);
|
||||||
setIsEditMode(true);
|
setIsEditMode(true);
|
||||||
setMcpSSEModalOpen(true);
|
setMcpSSEModalOpen(true);
|
||||||
|
|
||||||
// 在这里测试工具连接状态
|
|
||||||
try {
|
try {
|
||||||
const res = await httpClient.testMCPServer(server.name);
|
const res = await httpClient.testMCPServer(server.name);
|
||||||
if (res.task_id) {
|
if (res.task_id) {
|
||||||
const taskId = res.task_id;
|
const taskId = res.task_id;
|
||||||
|
|
||||||
// 监听任务完成
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
httpClient.getAsyncTask(taskId).then((taskResp) => {
|
httpClient.getAsyncTask(taskId).then((taskResp) => {
|
||||||
console.log('Task response:', taskResp);
|
console.log('Task response:', taskResp);
|
||||||
@@ -505,13 +515,13 @@ export default function PluginConfigPage() {
|
|||||||
console.log('Exception:', taskResp.runtime.exception);
|
console.log('Exception:', taskResp.runtime.exception);
|
||||||
|
|
||||||
if (taskResp.runtime.exception) {
|
if (taskResp.runtime.exception) {
|
||||||
// 测试失败
|
|
||||||
console.log('Test failed with exception');
|
console.log('Test failed with exception');
|
||||||
setMcpTestStatus('failed');
|
setMcpTestStatus('failed');
|
||||||
setMcpToolNames([]);
|
setMcpToolNames([]);
|
||||||
setMcpTestError(taskResp.runtime.exception || '未知错误');
|
setMcpTestError(taskResp.runtime.exception || '未知错误');
|
||||||
} else if (taskResp.runtime.result) {
|
} else if (taskResp.runtime.result) {
|
||||||
// 测试成功 - 后端可能返回字符串或对象
|
|
||||||
try {
|
try {
|
||||||
let result: {
|
let result: {
|
||||||
status?: string;
|
status?: string;
|
||||||
@@ -520,7 +530,7 @@ export default function PluginConfigPage() {
|
|||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果result是字符串,需要先解析
|
|
||||||
const rawResult: any = taskResp.runtime.result;
|
const rawResult: any = taskResp.runtime.result;
|
||||||
if (typeof rawResult === 'string') {
|
if (typeof rawResult === 'string') {
|
||||||
console.log('Result is string, parsing...');
|
console.log('Result is string, parsing...');
|
||||||
@@ -614,25 +624,21 @@ export default function PluginConfigPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isEditMode && editingServerName) {
|
if (isEditMode && editingServerName) {
|
||||||
// 编辑模式:更新服务器
|
|
||||||
await httpClient.updateMCPServer(editingServerName, serverConfig);
|
await httpClient.updateMCPServer(editingServerName, serverConfig);
|
||||||
toast.success(t('mcp.updateSuccess'));
|
toast.success(t('mcp.updateSuccess'));
|
||||||
} else {
|
} else {
|
||||||
// 创建模式:新建服务器
|
|
||||||
await httpClient.createMCPServer(serverConfig);
|
await httpClient.createMCPServer(serverConfig);
|
||||||
toast.success(t('mcp.createSuccess'));
|
toast.success(t('mcp.createSuccess'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只有在异步操作成功后才关闭对话框
|
|
||||||
setMcpSSEModalOpen(false);
|
setMcpSSEModalOpen(false);
|
||||||
|
|
||||||
// 重置表单和状态
|
|
||||||
form.reset();
|
form.reset();
|
||||||
setExtraArgs([]);
|
setExtraArgs([]);
|
||||||
setEditingServerName(null);
|
setEditingServerName(null);
|
||||||
setIsEditMode(false);
|
setIsEditMode(false);
|
||||||
|
|
||||||
// 刷新服务器列表
|
|
||||||
setRefreshKey((prev) => prev + 1);
|
setRefreshKey((prev) => prev + 1);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to save MCP server:', error);
|
console.error('Failed to save MCP server:', error);
|
||||||
@@ -706,7 +712,7 @@ export default function PluginConfigPage() {
|
|||||||
if (file) {
|
if (file) {
|
||||||
uploadPluginFile(file);
|
uploadPluginFile(file);
|
||||||
}
|
}
|
||||||
// 清空input值,以便可以重复选择同一个文件
|
|
||||||
event.target.value = '';
|
event.target.value = '';
|
||||||
},
|
},
|
||||||
[uploadPluginFile],
|
[uploadPluginFile],
|
||||||
@@ -748,7 +754,7 @@ export default function PluginConfigPage() {
|
|||||||
[uploadPluginFile, isPluginSystemReady, t],
|
[uploadPluginFile, isPluginSystemReady, t],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 插件系统未启用的状态显示
|
|
||||||
const renderPluginDisabledState = () => (
|
const renderPluginDisabledState = () => (
|
||||||
<div className="flex flex-col items-center justify-center h-[60vh] text-center pt-[10vh]">
|
<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" />
|
<Power className="w-16 h-16 text-gray-400 mb-4" />
|
||||||
@@ -761,7 +767,7 @@ export default function PluginConfigPage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// 插件系统连接异常的状态显示
|
|
||||||
const renderPluginConnectionErrorState = () => (
|
const renderPluginConnectionErrorState = () => (
|
||||||
<div className="flex flex-col items-center justify-center h-[60vh] text-center pt-[10vh]">
|
<div className="flex flex-col items-center justify-center h-[60vh] text-center pt-[10vh]">
|
||||||
<svg
|
<svg
|
||||||
@@ -783,7 +789,6 @@ export default function PluginConfigPage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// 加载状态显示
|
|
||||||
const renderLoadingState = () => (
|
const renderLoadingState = () => (
|
||||||
<div className="flex flex-col items-center justify-center h-[60vh] pt-[10vh]">
|
<div className="flex flex-col items-center justify-center h-[60vh] pt-[10vh]">
|
||||||
<p className="text-gray-500 dark:text-gray-400">
|
<p className="text-gray-500 dark:text-gray-400">
|
||||||
@@ -792,7 +797,6 @@ export default function PluginConfigPage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// 根据状态返回不同的内容
|
|
||||||
if (statusLoading) {
|
if (statusLoading) {
|
||||||
return renderLoadingState();
|
return renderLoadingState();
|
||||||
}
|
}
|
||||||
@@ -930,7 +934,7 @@ export default function PluginConfigPage() {
|
|||||||
<MCPComponent ref={mcpComponentRef} />
|
<MCPComponent ref={mcpComponentRef} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="mcp-servers">
|
<TabsContent value="mcp-servers">
|
||||||
<MCPMarketComponent
|
<MCPServerComponent
|
||||||
key={refreshKey}
|
key={refreshKey}
|
||||||
onEditServer={(serverName) => {
|
onEditServer={(serverName) => {
|
||||||
loadServerForEdit(serverName);
|
loadServerForEdit(serverName);
|
||||||
@@ -1221,6 +1225,7 @@ export default function PluginConfigPage() {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('models.extraParameters')}</FormLabel>
|
<FormLabel>{t('models.extraParameters')}</FormLabel>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user