feat(frontend): add MCP import dialog for v3.7.0

Implement import functionality to migrate MCP servers from existing configs:

**New Component:**
- src/components/mcp/McpImportDialog.tsx: Import dialog with card-based source selection

**Features:**
- Import from Claude (~/.claude/claude.json or settings.json)
- Import from Codex (~/.codex/config.toml)
- Import from Gemini (config file)
- Card-based UI with icons and descriptions
- Loading states with spinner animation
- Auto-refresh after successful import

**Integration:**
- Add import button to UnifiedMcpPanel header
- Handle import completion with refetch
- Toast notifications for success/info/error cases

**I18n:**
- Add mcp.unifiedPanel.import namespace (zh/en)
- Import button, dialog title, descriptions
- Success/error messages with count interpolation

**UX:**
- Smart disable: other sources disabled during import
- Clear feedback: count of imported servers
- Friendly messages: "No servers found" when empty

TypeScript type check passes 
This commit is contained in:
Jason
2025-11-14 15:29:16 +08:00
parent 9e8abf5f26
commit 9663b4251e
4 changed files with 223 additions and 2 deletions

View File

@@ -1,6 +1,6 @@
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Plus, Server, Check, RefreshCw } from "lucide-react";
import { Plus, Server, Check, RefreshCw, Download } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -14,6 +14,7 @@ import { useAllMcpServers, useToggleMcpApp, useSyncAllMcpServers } from "@/hooks
import type { McpServer } from "@/types";
import type { AppId } from "@/lib/api/types";
import McpFormModal from "./McpFormModal";
import McpImportDialog from "./McpImportDialog";
import { ConfirmDialog } from "../ConfirmDialog";
import { useDeleteMcpServer } from "@/hooks/useMcp";
import { Edit3, Trash2 } from "lucide-react";
@@ -36,6 +37,7 @@ const UnifiedMcpPanel: React.FC<UnifiedMcpPanelProps> = ({
}) => {
const { t } = useTranslation();
const [isFormOpen, setIsFormOpen] = useState(false);
const [isImportOpen, setIsImportOpen] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null);
const [confirmDialog, setConfirmDialog] = useState<{
isOpen: boolean;
@@ -45,7 +47,7 @@ const UnifiedMcpPanel: React.FC<UnifiedMcpPanelProps> = ({
} | null>(null);
// Queries and Mutations
const { data: serversMap, isLoading } = useAllMcpServers();
const { data: serversMap, isLoading, refetch } = useAllMcpServers();
const toggleAppMutation = useToggleMcpApp();
const deleteServerMutation = useDeleteMcpServer();
const syncAllMutation = useSyncAllMcpServers();
@@ -126,6 +128,11 @@ const UnifiedMcpPanel: React.FC<UnifiedMcpPanelProps> = ({
setEditingId(null);
};
const handleImportComplete = () => {
// Refresh the servers list after import
refetch();
};
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -134,6 +141,15 @@ const UnifiedMcpPanel: React.FC<UnifiedMcpPanelProps> = ({
<div className="flex items-center justify-between pr-8">
<DialogTitle>{t("mcp.unifiedPanel.title")}</DialogTitle>
<div className="flex gap-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => setIsImportOpen(true)}
>
<Download size={16} />
{t("mcp.unifiedPanel.import.button")}
</Button>
<Button
type="button"
variant="outline"
@@ -231,6 +247,13 @@ const UnifiedMcpPanel: React.FC<UnifiedMcpPanelProps> = ({
/>
)}
{/* Import Dialog */}
<McpImportDialog
open={isImportOpen}
onOpenChange={setIsImportOpen}
onImportComplete={handleImportComplete}
/>
{/* Confirm Dialog */}
{confirmDialog && (
<ConfirmDialog