import React, { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Plus, Server, Check } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { type AppId } from "@/lib/api"; import { McpServer } from "@/types"; import { useMcpActions } from "@/hooks/useMcpActions"; import McpListItem from "./McpListItem"; import McpFormModal from "./McpFormModal"; import { ConfirmDialog } from "../ConfirmDialog"; interface McpPanelProps { open: boolean; onOpenChange: (open: boolean) => void; appId: AppId; } /** * MCP 管理面板 * 采用与主界面一致的设计风格,右上角添加按钮,每个 MCP 占一行 */ const McpPanel: React.FC = ({ open, onOpenChange, appId }) => { const { t } = useTranslation(); const [isFormOpen, setIsFormOpen] = useState(false); const [editingId, setEditingId] = useState(null); const [confirmDialog, setConfirmDialog] = useState<{ isOpen: boolean; title: string; message: string; onConfirm: () => void; } | null>(null); // Use MCP actions hook const { servers, loading, reload, toggleEnabled, saveServer, deleteServer } = useMcpActions(appId); useEffect(() => { const setup = async () => { try { // Initialize: only import existing MCPs from corresponding client if (appId === "claude") { const mcpApi = await import("@/lib/api").then((m) => m.mcpApi); await mcpApi.importFromClaude(); } else if (appId === "codex") { const mcpApi = await import("@/lib/api").then((m) => m.mcpApi); await mcpApi.importFromCodex(); } else if (appId === "gemini") { const mcpApi = await import("@/lib/api").then((m) => m.mcpApi); await mcpApi.importFromGemini(); } } catch (e) { console.warn("MCP initialization import failed (ignored)", e); } finally { await reload(); } }; setup(); // Re-initialize when appId changes }, [appId, reload]); const handleEdit = (id: string) => { setEditingId(id); setIsFormOpen(true); }; const handleAdd = () => { setEditingId(null); setIsFormOpen(true); }; const handleDelete = (id: string) => { setConfirmDialog({ isOpen: true, title: t("mcp.confirm.deleteTitle"), message: t("mcp.confirm.deleteMessage", { id }), onConfirm: async () => { try { await deleteServer(id); setConfirmDialog(null); } catch (e) { // Error already handled by useMcpActions } }, }); }; const handleSave = async ( id: string, server: McpServer, options?: { syncOtherSide?: boolean }, ) => { await saveServer(id, server, options); setIsFormOpen(false); setEditingId(null); }; const handleCloseForm = () => { setIsFormOpen(false); setEditingId(null); }; const serverEntries = useMemo( () => Object.entries(servers) as Array<[string, McpServer]>, [servers], ); const enabledCount = useMemo( () => serverEntries.filter(([_, server]) => server.enabled).length, [serverEntries], ); const panelTitle = appId === "claude" ? t("mcp.claudeTitle") : t("mcp.codexTitle"); return ( <>
{panelTitle}
{/* Info Section */}
{t("mcp.serverCount", { count: Object.keys(servers).length })} ·{" "} {t("mcp.enabledCount", { count: enabledCount })}
{/* Content - Scrollable */}
{loading ? (
{t("mcp.loading")}
) : ( (() => { const hasAny = serverEntries.length > 0; if (!hasAny) { return (

{t("mcp.empty")}

{t("mcp.emptyDescription")}

); } return (
{/* 已安装 */} {serverEntries.map(([id, server]) => ( ))} {/* 预设已移至"新增 MCP"面板中展示与套用 */}
); })() )}
{/* Form Modal */} {isFormOpen && ( )} {/* Confirm Dialog */} {confirmDialog && ( setConfirmDialog(null)} /> )} ); }; export default McpPanel;