refactor(mcp): complete v3.7.0 cleanup - remove legacy code and warnings
This commit finalizes the v3.7.0 unified MCP architecture migration by
removing all deprecated code paths and eliminating compiler warnings.
Frontend Changes (~950 lines removed):
- Remove deprecated components: McpPanel, McpListItem, McpToggle
- Remove deprecated hook: useMcpActions
- Remove unused API methods: importFrom*, syncEnabledTo*, syncAllServers
- Simplify McpFormModal by removing dual-mode logic (unified/legacy)
- Remove syncOtherSide checkbox and conflict detection
- Clean up unused imports and state variables
- Delete associated test files
Backend Changes (~400 lines cleaned):
- Remove unused Tauri commands: import_mcp_from_*, sync_enabled_mcp_to_*
- Delete unused Gemini MCP functions: get_mcp_status, upsert/delete_mcp_server
- Add #[allow(deprecated)] to compatibility layer commands
- Add #[allow(dead_code)] to legacy helper functions for future migration
- Simplify boolean expression in mcp.rs per Clippy suggestion
API Deprecation:
- Mark legacy APIs with @deprecated JSDoc (getConfig, upsertServerInConfig, etc.)
- Preserve backward compatibility for v3.x, planned removal in v4.0
Verification:
- ✅ Zero TypeScript errors (pnpm typecheck)
- ✅ Zero Clippy warnings (cargo clippy)
- ✅ All code formatted (prettier + cargo fmt)
- ✅ Builds successfully
Total cleanup: ~1,350 lines of code removed/marked
Breaking changes: None (all legacy APIs still functional)
2025-11-14 22:43:25 +08:00
|
|
|
|
import React, { useMemo, useState } from "react";
|
2025-10-09 11:04:36 +08:00
|
|
|
|
import { useTranslation } from "react-i18next";
|
2025-10-17 17:49:16 +08:00
|
|
|
|
import { toast } from "sonner";
|
feat: add model configuration support and fix Gemini deeplink bug (#251)
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
2025-11-19 09:03:18 +08:00
|
|
|
|
import {
|
|
|
|
|
|
Save,
|
|
|
|
|
|
Plus,
|
|
|
|
|
|
AlertCircle,
|
|
|
|
|
|
ChevronDown,
|
|
|
|
|
|
ChevronUp,
|
|
|
|
|
|
Wand2,
|
|
|
|
|
|
} from "lucide-react";
|
2025-10-16 12:13:51 +08:00
|
|
|
|
import { Button } from "@/components/ui/button";
|
2025-11-15 23:47:35 +08:00
|
|
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
2025-10-16 16:20:45 +08:00
|
|
|
|
import {
|
|
|
|
|
|
Dialog,
|
|
|
|
|
|
DialogContent,
|
|
|
|
|
|
DialogHeader,
|
|
|
|
|
|
DialogTitle,
|
|
|
|
|
|
DialogFooter,
|
|
|
|
|
|
} from "@/components/ui/dialog";
|
2025-10-16 12:13:51 +08:00
|
|
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
|
|
import { Textarea } from "@/components/ui/textarea";
|
refactor(mcp): complete v3.7.0 cleanup - remove legacy code and warnings
This commit finalizes the v3.7.0 unified MCP architecture migration by
removing all deprecated code paths and eliminating compiler warnings.
Frontend Changes (~950 lines removed):
- Remove deprecated components: McpPanel, McpListItem, McpToggle
- Remove deprecated hook: useMcpActions
- Remove unused API methods: importFrom*, syncEnabledTo*, syncAllServers
- Simplify McpFormModal by removing dual-mode logic (unified/legacy)
- Remove syncOtherSide checkbox and conflict detection
- Clean up unused imports and state variables
- Delete associated test files
Backend Changes (~400 lines cleaned):
- Remove unused Tauri commands: import_mcp_from_*, sync_enabled_mcp_to_*
- Delete unused Gemini MCP functions: get_mcp_status, upsert/delete_mcp_server
- Add #[allow(deprecated)] to compatibility layer commands
- Add #[allow(dead_code)] to legacy helper functions for future migration
- Simplify boolean expression in mcp.rs per Clippy suggestion
API Deprecation:
- Mark legacy APIs with @deprecated JSDoc (getConfig, upsertServerInConfig, etc.)
- Preserve backward compatibility for v3.x, planned removal in v4.0
Verification:
- ✅ Zero TypeScript errors (pnpm typecheck)
- ✅ Zero Clippy warnings (cargo clippy)
- ✅ All code formatted (prettier + cargo fmt)
- ✅ Builds successfully
Total cleanup: ~1,350 lines of code removed/marked
Breaking changes: None (all legacy APIs still functional)
2025-11-14 22:43:25 +08:00
|
|
|
|
import type { AppId } from "@/lib/api/types";
|
2025-10-17 16:35:12 +08:00
|
|
|
|
import { McpServer, McpServerSpec } from "@/types";
|
|
|
|
|
|
import { mcpPresets, getMcpPresetWithDescription } from "@/config/mcpPresets";
|
2025-10-09 11:30:28 +08:00
|
|
|
|
import McpWizardModal from "./McpWizardModal";
|
2025-10-18 16:52:02 +08:00
|
|
|
|
import {
|
|
|
|
|
|
extractErrorMessage,
|
|
|
|
|
|
translateMcpBackendError,
|
|
|
|
|
|
} from "@/utils/errorUtils";
|
|
|
|
|
|
import {
|
|
|
|
|
|
tomlToMcpServer,
|
|
|
|
|
|
extractIdFromToml,
|
|
|
|
|
|
mcpServerToToml,
|
|
|
|
|
|
} from "@/utils/tomlUtils";
|
2025-11-10 14:35:55 +08:00
|
|
|
|
import { normalizeTomlText } from "@/utils/textNormalization";
|
2025-11-16 20:08:04 +08:00
|
|
|
|
import { formatJSON, parseSmartMcpJson } from "@/utils/formatters";
|
2025-10-17 15:10:04 +08:00
|
|
|
|
import { useMcpValidation } from "./useMcpValidation";
|
2025-11-14 15:24:48 +08:00
|
|
|
|
import { useUpsertMcpServer } from "@/hooks/useMcp";
|
2025-10-09 11:04:36 +08:00
|
|
|
|
|
|
|
|
|
|
interface McpFormModalProps {
|
|
|
|
|
|
editingId?: string;
|
|
|
|
|
|
initialData?: McpServer;
|
refactor(mcp): complete v3.7.0 cleanup - remove legacy code and warnings
This commit finalizes the v3.7.0 unified MCP architecture migration by
removing all deprecated code paths and eliminating compiler warnings.
Frontend Changes (~950 lines removed):
- Remove deprecated components: McpPanel, McpListItem, McpToggle
- Remove deprecated hook: useMcpActions
- Remove unused API methods: importFrom*, syncEnabledTo*, syncAllServers
- Simplify McpFormModal by removing dual-mode logic (unified/legacy)
- Remove syncOtherSide checkbox and conflict detection
- Clean up unused imports and state variables
- Delete associated test files
Backend Changes (~400 lines cleaned):
- Remove unused Tauri commands: import_mcp_from_*, sync_enabled_mcp_to_*
- Delete unused Gemini MCP functions: get_mcp_status, upsert/delete_mcp_server
- Add #[allow(deprecated)] to compatibility layer commands
- Add #[allow(dead_code)] to legacy helper functions for future migration
- Simplify boolean expression in mcp.rs per Clippy suggestion
API Deprecation:
- Mark legacy APIs with @deprecated JSDoc (getConfig, upsertServerInConfig, etc.)
- Preserve backward compatibility for v3.x, planned removal in v4.0
Verification:
- ✅ Zero TypeScript errors (pnpm typecheck)
- ✅ Zero Clippy warnings (cargo clippy)
- ✅ All code formatted (prettier + cargo fmt)
- ✅ Builds successfully
Total cleanup: ~1,350 lines of code removed/marked
Breaking changes: None (all legacy APIs still functional)
2025-11-14 22:43:25 +08:00
|
|
|
|
onSave: () => Promise<void>; // v3.7.0: 简化为仅用于关闭表单的回调
|
2025-10-09 11:04:36 +08:00
|
|
|
|
onClose: () => void;
|
2025-10-10 11:17:40 +08:00
|
|
|
|
existingIds?: string[];
|
2025-11-15 23:47:35 +08:00
|
|
|
|
defaultFormat?: "json" | "toml"; // 默认配置格式(可选,默认为 JSON)
|
2025-11-16 16:50:07 +08:00
|
|
|
|
defaultEnabledApps?: AppId[]; // 默认启用到哪些应用(可选,默认为全部应用)
|
2025-10-09 11:04:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-15 23:47:35 +08:00
|
|
|
|
* MCP 表单模态框组件(v3.7.0 完整重构版)
|
|
|
|
|
|
* - 支持 JSON 和 TOML 两种格式
|
|
|
|
|
|
* - 统一管理,通过复选框选择启用到哪些应用
|
2025-10-09 11:04:36 +08:00
|
|
|
|
*/
|
|
|
|
|
|
const McpFormModal: React.FC<McpFormModalProps> = ({
|
|
|
|
|
|
editingId,
|
|
|
|
|
|
initialData,
|
|
|
|
|
|
onSave,
|
|
|
|
|
|
onClose,
|
2025-10-10 11:17:40 +08:00
|
|
|
|
existingIds = [],
|
2025-11-15 23:47:35 +08:00
|
|
|
|
defaultFormat = "json",
|
2025-11-16 16:50:07 +08:00
|
|
|
|
defaultEnabledApps = ["claude", "codex", "gemini"],
|
2025-10-09 11:04:36 +08:00
|
|
|
|
}) => {
|
|
|
|
|
|
const { t } = useTranslation();
|
2025-10-17 15:10:04 +08:00
|
|
|
|
const { formatTomlError, validateTomlConfig, validateJsonConfig } =
|
|
|
|
|
|
useMcpValidation();
|
2025-10-11 15:34:58 +08:00
|
|
|
|
|
2025-11-14 15:24:48 +08:00
|
|
|
|
const upsertMutation = useUpsertMcpServer();
|
|
|
|
|
|
|
2025-10-12 00:08:37 +08:00
|
|
|
|
const [formId, setFormId] = useState(
|
|
|
|
|
|
() => editingId || initialData?.id || "",
|
|
|
|
|
|
);
|
|
|
|
|
|
const [formName, setFormName] = useState(initialData?.name || "");
|
2025-10-09 23:13:33 +08:00
|
|
|
|
const [formDescription, setFormDescription] = useState(
|
2025-10-12 00:08:37 +08:00
|
|
|
|
initialData?.description || "",
|
2025-10-09 23:13:33 +08:00
|
|
|
|
);
|
2025-10-12 00:08:37 +08:00
|
|
|
|
const [formHomepage, setFormHomepage] = useState(initialData?.homepage || "");
|
|
|
|
|
|
const [formDocs, setFormDocs] = useState(initialData?.docs || "");
|
|
|
|
|
|
const [formTags, setFormTags] = useState(initialData?.tags?.join(", ") || "");
|
2025-10-11 15:34:58 +08:00
|
|
|
|
|
2025-11-15 23:47:35 +08:00
|
|
|
|
// 启用状态:编辑模式使用现有值,新增模式使用默认值
|
|
|
|
|
|
const [enabledApps, setEnabledApps] = useState<{
|
|
|
|
|
|
claude: boolean;
|
|
|
|
|
|
codex: boolean;
|
|
|
|
|
|
gemini: boolean;
|
|
|
|
|
|
}>(() => {
|
|
|
|
|
|
if (initialData?.apps) {
|
|
|
|
|
|
return { ...initialData.apps };
|
|
|
|
|
|
}
|
|
|
|
|
|
// 新增模式:根据 defaultEnabledApps 设置初始值
|
|
|
|
|
|
return {
|
|
|
|
|
|
claude: defaultEnabledApps.includes("claude"),
|
|
|
|
|
|
codex: defaultEnabledApps.includes("codex"),
|
|
|
|
|
|
gemini: defaultEnabledApps.includes("gemini"),
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-12 11:01:49 +08:00
|
|
|
|
// 编辑模式下禁止修改 ID
|
|
|
|
|
|
const isEditing = !!editingId;
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否在编辑模式下有附加信息
|
|
|
|
|
|
const hasAdditionalInfo = !!(
|
|
|
|
|
|
initialData?.description ||
|
|
|
|
|
|
initialData?.tags?.length ||
|
|
|
|
|
|
initialData?.homepage ||
|
|
|
|
|
|
initialData?.docs
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 附加信息展开状态(编辑模式下有值时默认展开)
|
|
|
|
|
|
const [showMetadata, setShowMetadata] = useState(
|
|
|
|
|
|
isEditing ? hasAdditionalInfo : false,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-11-15 23:47:35 +08:00
|
|
|
|
// 配置格式:优先使用 defaultFormat,编辑模式下可从现有数据推断
|
|
|
|
|
|
const useTomlFormat = useMemo(() => {
|
|
|
|
|
|
if (initialData?.server) {
|
|
|
|
|
|
// 编辑模式:尝试从现有数据推断格式(这里简化处理,默认 JSON)
|
|
|
|
|
|
return defaultFormat === "toml";
|
|
|
|
|
|
}
|
|
|
|
|
|
return defaultFormat === "toml";
|
|
|
|
|
|
}, [defaultFormat, initialData]);
|
|
|
|
|
|
|
|
|
|
|
|
// 根据格式决定初始配置
|
2025-10-11 15:34:58 +08:00
|
|
|
|
const [formConfig, setFormConfig] = useState(() => {
|
2025-10-12 00:08:37 +08:00
|
|
|
|
const spec = initialData?.server;
|
|
|
|
|
|
if (!spec) return "";
|
2025-11-15 23:47:35 +08:00
|
|
|
|
if (useTomlFormat) {
|
2025-10-12 00:08:37 +08:00
|
|
|
|
return mcpServerToToml(spec);
|
2025-10-11 15:34:58 +08:00
|
|
|
|
}
|
2025-10-12 00:08:37 +08:00
|
|
|
|
return JSON.stringify(spec, null, 2);
|
2025-10-11 15:34:58 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const [configError, setConfigError] = useState("");
|
2025-10-09 11:04:36 +08:00
|
|
|
|
const [saving, setSaving] = useState(false);
|
2025-10-09 11:30:28 +08:00
|
|
|
|
const [isWizardOpen, setIsWizardOpen] = useState(false);
|
2025-10-10 11:17:40 +08:00
|
|
|
|
const [idError, setIdError] = useState("");
|
2025-10-09 11:04:36 +08:00
|
|
|
|
|
2025-11-15 23:47:35 +08:00
|
|
|
|
// 判断是否使用 TOML 格式(向后兼容,后续可扩展为格式切换按钮)
|
|
|
|
|
|
const useToml = useTomlFormat;
|
2025-10-14 10:22:57 +08:00
|
|
|
|
|
2025-10-13 23:37:33 +08:00
|
|
|
|
const wizardInitialSpec = useMemo(() => {
|
|
|
|
|
|
const fallback = initialData?.server;
|
|
|
|
|
|
if (!formConfig.trim()) {
|
|
|
|
|
|
return fallback;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (useToml) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return tomlToMcpServer(formConfig);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return fallback;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(formConfig);
|
|
|
|
|
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
|
|
|
|
return parsed as McpServerSpec;
|
|
|
|
|
|
}
|
|
|
|
|
|
return fallback;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return fallback;
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [formConfig, initialData, useToml]);
|
|
|
|
|
|
|
2025-10-10 22:34:38 +08:00
|
|
|
|
// 预设选择状态(仅新增模式显示;-1 表示自定义)
|
|
|
|
|
|
const [selectedPreset, setSelectedPreset] = useState<number | null>(
|
|
|
|
|
|
isEditing ? null : -1,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-10-10 11:17:40 +08:00
|
|
|
|
const handleIdChange = (value: string) => {
|
|
|
|
|
|
setFormId(value);
|
|
|
|
|
|
if (!isEditing) {
|
|
|
|
|
|
const exists = existingIds.includes(value.trim());
|
|
|
|
|
|
setIdError(exists ? t("mcp.error.idExists") : "");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-10 22:34:38 +08:00
|
|
|
|
const ensureUniqueId = (base: string): string => {
|
|
|
|
|
|
let candidate = base.trim();
|
|
|
|
|
|
if (!candidate) candidate = "mcp-server";
|
|
|
|
|
|
if (!existingIds.includes(candidate)) return candidate;
|
|
|
|
|
|
let i = 1;
|
|
|
|
|
|
while (existingIds.includes(`${candidate}-${i}`)) i++;
|
|
|
|
|
|
return `${candidate}-${i}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 应用预设(写入表单但不落库)
|
|
|
|
|
|
const applyPreset = (index: number) => {
|
|
|
|
|
|
if (index < 0 || index >= mcpPresets.length) return;
|
2025-10-12 16:34:32 +08:00
|
|
|
|
const preset = mcpPresets[index];
|
|
|
|
|
|
const presetWithDesc = getMcpPresetWithDescription(preset, t);
|
|
|
|
|
|
|
|
|
|
|
|
const id = ensureUniqueId(presetWithDesc.id);
|
2025-10-10 22:34:38 +08:00
|
|
|
|
setFormId(id);
|
2025-10-12 16:34:32 +08:00
|
|
|
|
setFormName(presetWithDesc.name || presetWithDesc.id);
|
|
|
|
|
|
setFormDescription(presetWithDesc.description || "");
|
|
|
|
|
|
setFormHomepage(presetWithDesc.homepage || "");
|
|
|
|
|
|
setFormDocs(presetWithDesc.docs || "");
|
|
|
|
|
|
setFormTags(presetWithDesc.tags?.join(", ") || "");
|
2025-10-11 15:34:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 根据格式转换配置
|
|
|
|
|
|
if (useToml) {
|
2025-10-12 16:34:32 +08:00
|
|
|
|
const toml = mcpServerToToml(presetWithDesc.server);
|
2025-10-11 15:34:58 +08:00
|
|
|
|
setFormConfig(toml);
|
2025-10-17 15:10:04 +08:00
|
|
|
|
setConfigError(validateTomlConfig(toml));
|
2025-10-11 15:34:58 +08:00
|
|
|
|
} else {
|
2025-10-12 16:34:32 +08:00
|
|
|
|
const json = JSON.stringify(presetWithDesc.server, null, 2);
|
2025-10-11 15:34:58 +08:00
|
|
|
|
setFormConfig(json);
|
2025-10-17 15:10:04 +08:00
|
|
|
|
setConfigError(validateJsonConfig(json));
|
2025-10-11 15:34:58 +08:00
|
|
|
|
}
|
2025-10-10 22:34:38 +08:00
|
|
|
|
setSelectedPreset(index);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 切回自定义
|
|
|
|
|
|
const applyCustom = () => {
|
|
|
|
|
|
setSelectedPreset(-1);
|
|
|
|
|
|
// 恢复到空白模板
|
|
|
|
|
|
setFormId("");
|
2025-10-12 00:08:37 +08:00
|
|
|
|
setFormName("");
|
2025-10-10 22:34:38 +08:00
|
|
|
|
setFormDescription("");
|
2025-10-12 00:08:37 +08:00
|
|
|
|
setFormHomepage("");
|
|
|
|
|
|
setFormDocs("");
|
|
|
|
|
|
setFormTags("");
|
2025-10-11 15:34:58 +08:00
|
|
|
|
setFormConfig("");
|
|
|
|
|
|
setConfigError("");
|
2025-10-10 22:34:38 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-11 15:34:58 +08:00
|
|
|
|
const handleConfigChange = (value: string) => {
|
2025-11-10 14:35:55 +08:00
|
|
|
|
// 若为 TOML 模式,先做引号归一化,避免中文输入法导致的格式错误
|
|
|
|
|
|
const nextValue = useToml ? normalizeTomlText(value) : value;
|
|
|
|
|
|
setFormConfig(nextValue);
|
2025-10-09 17:21:03 +08:00
|
|
|
|
|
2025-10-11 15:34:58 +08:00
|
|
|
|
if (useToml) {
|
2025-10-17 15:10:04 +08:00
|
|
|
|
// TOML validation (use hook's complete validation)
|
2025-11-10 14:35:55 +08:00
|
|
|
|
const err = validateTomlConfig(nextValue);
|
2025-10-11 15:34:58 +08:00
|
|
|
|
if (err) {
|
2025-10-17 15:10:04 +08:00
|
|
|
|
setConfigError(err);
|
2025-10-11 15:34:58 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-09 17:21:03 +08:00
|
|
|
|
|
2025-10-17 15:10:04 +08:00
|
|
|
|
// Try to extract ID (if user hasn't filled it yet)
|
2025-11-10 14:35:55 +08:00
|
|
|
|
if (nextValue.trim() && !formId.trim()) {
|
|
|
|
|
|
const extractedId = extractIdFromToml(nextValue);
|
2025-10-17 15:10:04 +08:00
|
|
|
|
if (extractedId) {
|
|
|
|
|
|
setFormId(extractedId);
|
2025-10-11 15:34:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2025-11-16 20:08:04 +08:00
|
|
|
|
// JSON validation with smart parsing
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = parseSmartMcpJson(value);
|
|
|
|
|
|
|
|
|
|
|
|
// 验证解析后的配置对象
|
|
|
|
|
|
const configJson = JSON.stringify(result.config);
|
|
|
|
|
|
const validationErr = validateJsonConfig(configJson);
|
|
|
|
|
|
|
|
|
|
|
|
if (validationErr) {
|
|
|
|
|
|
setConfigError(validationErr);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 自动填充提取的 id(仅当表单 id 为空且不在编辑模式时)
|
|
|
|
|
|
if (result.id && !formId.trim() && !isEditing) {
|
|
|
|
|
|
const uniqueId = ensureUniqueId(result.id);
|
|
|
|
|
|
setFormId(uniqueId);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果 name 也为空,同时填充 name
|
|
|
|
|
|
if (!formName.trim()) {
|
|
|
|
|
|
setFormName(result.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-16 20:40:16 +08:00
|
|
|
|
// 不在输入时自动格式化,保持用户输入的原样
|
|
|
|
|
|
// 格式清理将在提交时进行
|
2025-11-16 20:08:04 +08:00
|
|
|
|
|
|
|
|
|
|
setConfigError("");
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
const errorMessage = err?.message || String(err);
|
|
|
|
|
|
setConfigError(t("mcp.error.jsonInvalid") + ": " + errorMessage);
|
2025-10-11 15:34:58 +08:00
|
|
|
|
}
|
2025-10-09 17:21:03 +08:00
|
|
|
|
}
|
2025-10-09 11:30:28 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-16 16:50:07 +08:00
|
|
|
|
const handleFormatJson = () => {
|
|
|
|
|
|
if (!formConfig.trim()) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const formatted = formatJSON(formConfig);
|
|
|
|
|
|
setFormConfig(formatted);
|
|
|
|
|
|
toast.success(t("common.formatSuccess"));
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
const errorMessage =
|
|
|
|
|
|
error instanceof Error ? error.message : String(error);
|
|
|
|
|
|
toast.error(
|
|
|
|
|
|
t("common.formatError", {
|
|
|
|
|
|
error: errorMessage,
|
|
|
|
|
|
}),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-11 11:43:32 +08:00
|
|
|
|
const handleWizardApply = (title: string, json: string) => {
|
|
|
|
|
|
setFormId(title);
|
2025-10-12 00:08:37 +08:00
|
|
|
|
if (!formName.trim()) {
|
|
|
|
|
|
setFormName(title);
|
|
|
|
|
|
}
|
2025-10-17 15:10:04 +08:00
|
|
|
|
// Wizard returns JSON, convert based on format if needed
|
2025-10-11 15:34:58 +08:00
|
|
|
|
if (useToml) {
|
|
|
|
|
|
try {
|
2025-10-12 00:08:37 +08:00
|
|
|
|
const server = JSON.parse(json) as McpServerSpec;
|
2025-10-11 15:34:58 +08:00
|
|
|
|
const toml = mcpServerToToml(server);
|
|
|
|
|
|
setFormConfig(toml);
|
2025-10-17 15:10:04 +08:00
|
|
|
|
setConfigError(validateTomlConfig(toml));
|
2025-10-11 15:34:58 +08:00
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
setConfigError(t("mcp.error.jsonInvalid"));
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setFormConfig(json);
|
2025-10-17 15:10:04 +08:00
|
|
|
|
setConfigError(validateJsonConfig(json));
|
2025-10-11 15:34:58 +08:00
|
|
|
|
}
|
2025-10-09 11:04:36 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSubmit = async () => {
|
2025-10-12 00:08:37 +08:00
|
|
|
|
const trimmedId = formId.trim();
|
|
|
|
|
|
if (!trimmedId) {
|
2025-10-17 17:49:16 +08:00
|
|
|
|
toast.error(t("mcp.error.idRequired"), { duration: 3000 });
|
2025-10-09 11:04:36 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-09 11:30:28 +08:00
|
|
|
|
|
2025-10-10 11:17:40 +08:00
|
|
|
|
// 新增模式:阻止提交重名 ID
|
2025-10-12 00:08:37 +08:00
|
|
|
|
if (!isEditing && existingIds.includes(trimmedId)) {
|
2025-10-10 11:17:40 +08:00
|
|
|
|
setIdError(t("mcp.error.idExists"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 15:10:04 +08:00
|
|
|
|
// Validate configuration format
|
2025-10-12 00:08:37 +08:00
|
|
|
|
let serverSpec: McpServerSpec;
|
2025-10-09 11:04:36 +08:00
|
|
|
|
|
2025-10-11 15:34:58 +08:00
|
|
|
|
if (useToml) {
|
2025-10-17 15:10:04 +08:00
|
|
|
|
// TOML mode
|
|
|
|
|
|
const tomlError = validateTomlConfig(formConfig);
|
|
|
|
|
|
setConfigError(tomlError);
|
2025-10-11 15:34:58 +08:00
|
|
|
|
if (tomlError) {
|
2025-10-17 17:49:16 +08:00
|
|
|
|
toast.error(t("mcp.error.tomlInvalid"), { duration: 3000 });
|
2025-10-11 15:34:58 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!formConfig.trim()) {
|
2025-10-17 15:10:04 +08:00
|
|
|
|
// Empty configuration
|
2025-10-12 00:08:37 +08:00
|
|
|
|
serverSpec = {
|
2025-10-11 15:34:58 +08:00
|
|
|
|
type: "stdio",
|
|
|
|
|
|
command: "",
|
|
|
|
|
|
args: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
try {
|
2025-10-12 00:08:37 +08:00
|
|
|
|
serverSpec = tomlToMcpServer(formConfig);
|
2025-10-11 15:34:58 +08:00
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
const msg = e?.message || String(e);
|
|
|
|
|
|
setConfigError(formatTomlError(msg));
|
2025-10-17 17:49:16 +08:00
|
|
|
|
toast.error(t("mcp.error.tomlInvalid"), { duration: 4000 });
|
2025-10-09 17:21:03 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-11 15:34:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2025-10-17 15:10:04 +08:00
|
|
|
|
// JSON mode
|
2025-10-11 15:34:58 +08:00
|
|
|
|
if (!formConfig.trim()) {
|
2025-10-17 15:10:04 +08:00
|
|
|
|
// Empty configuration
|
2025-10-12 00:08:37 +08:00
|
|
|
|
serverSpec = {
|
2025-10-09 11:30:28 +08:00
|
|
|
|
type: "stdio",
|
|
|
|
|
|
command: "",
|
|
|
|
|
|
args: [],
|
|
|
|
|
|
};
|
2025-10-11 15:34:58 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
try {
|
2025-11-16 20:40:16 +08:00
|
|
|
|
// 使用智能解析器,支持带外层键的格式
|
|
|
|
|
|
const result = parseSmartMcpJson(formConfig);
|
|
|
|
|
|
serverSpec = result.config as McpServerSpec;
|
2025-10-11 15:34:58 +08:00
|
|
|
|
} catch (e: any) {
|
2025-11-16 20:40:16 +08:00
|
|
|
|
const errorMessage = e?.message || String(e);
|
|
|
|
|
|
setConfigError(t("mcp.error.jsonInvalid") + ": " + errorMessage);
|
2025-10-17 17:49:16 +08:00
|
|
|
|
toast.error(t("mcp.error.jsonInvalid"), { duration: 4000 });
|
2025-10-11 15:34:58 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-09 11:30:28 +08:00
|
|
|
|
}
|
2025-10-11 15:34:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 前置必填校验
|
2025-10-12 00:08:37 +08:00
|
|
|
|
if (serverSpec?.type === "stdio" && !serverSpec?.command?.trim()) {
|
2025-10-17 17:49:16 +08:00
|
|
|
|
toast.error(t("mcp.error.commandRequired"), { duration: 3000 });
|
2025-10-11 15:34:58 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-11-16 16:15:17 +08:00
|
|
|
|
if (
|
|
|
|
|
|
(serverSpec?.type === "http" || serverSpec?.type === "sse") &&
|
|
|
|
|
|
!serverSpec?.url?.trim()
|
|
|
|
|
|
) {
|
2025-10-17 17:49:16 +08:00
|
|
|
|
toast.error(t("mcp.wizard.urlRequired"), { duration: 3000 });
|
2025-10-11 15:34:58 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-09 11:30:28 +08:00
|
|
|
|
|
2025-10-11 15:34:58 +08:00
|
|
|
|
setSaving(true);
|
|
|
|
|
|
try {
|
2025-11-14 15:24:48 +08:00
|
|
|
|
// 先处理 name 字段(必填)
|
|
|
|
|
|
const nameTrimmed = (formName || trimmedId).trim();
|
|
|
|
|
|
const finalName = nameTrimmed || trimmedId;
|
|
|
|
|
|
|
2025-10-12 00:08:37 +08:00
|
|
|
|
const entry: McpServer = {
|
|
|
|
|
|
...(initialData ? { ...initialData } : {}),
|
|
|
|
|
|
id: trimmedId,
|
2025-11-14 15:24:48 +08:00
|
|
|
|
name: finalName,
|
2025-10-12 00:08:37 +08:00
|
|
|
|
server: serverSpec,
|
2025-11-15 23:47:35 +08:00
|
|
|
|
// 使用表单中的启用状态(v3.7.0 完整重构)
|
|
|
|
|
|
apps: enabledApps,
|
2025-10-12 00:08:37 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const descriptionTrimmed = formDescription.trim();
|
|
|
|
|
|
if (descriptionTrimmed) {
|
|
|
|
|
|
entry.description = descriptionTrimmed;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
delete entry.description;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const homepageTrimmed = formHomepage.trim();
|
|
|
|
|
|
if (homepageTrimmed) {
|
|
|
|
|
|
entry.homepage = homepageTrimmed;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
delete entry.homepage;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const docsTrimmed = formDocs.trim();
|
|
|
|
|
|
if (docsTrimmed) {
|
|
|
|
|
|
entry.docs = docsTrimmed;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
delete entry.docs;
|
2025-10-09 11:30:28 +08:00
|
|
|
|
}
|
2025-10-09 11:04:36 +08:00
|
|
|
|
|
2025-10-12 00:08:37 +08:00
|
|
|
|
const parsedTags = formTags
|
|
|
|
|
|
.split(",")
|
|
|
|
|
|
.map((tag) => tag.trim())
|
|
|
|
|
|
.filter((tag) => tag.length > 0);
|
|
|
|
|
|
if (parsedTags.length > 0) {
|
|
|
|
|
|
entry.tags = parsedTags;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
delete entry.tags;
|
2025-10-09 23:13:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
refactor(mcp): complete v3.7.0 cleanup - remove legacy code and warnings
This commit finalizes the v3.7.0 unified MCP architecture migration by
removing all deprecated code paths and eliminating compiler warnings.
Frontend Changes (~950 lines removed):
- Remove deprecated components: McpPanel, McpListItem, McpToggle
- Remove deprecated hook: useMcpActions
- Remove unused API methods: importFrom*, syncEnabledTo*, syncAllServers
- Simplify McpFormModal by removing dual-mode logic (unified/legacy)
- Remove syncOtherSide checkbox and conflict detection
- Clean up unused imports and state variables
- Delete associated test files
Backend Changes (~400 lines cleaned):
- Remove unused Tauri commands: import_mcp_from_*, sync_enabled_mcp_to_*
- Delete unused Gemini MCP functions: get_mcp_status, upsert/delete_mcp_server
- Add #[allow(deprecated)] to compatibility layer commands
- Add #[allow(dead_code)] to legacy helper functions for future migration
- Simplify boolean expression in mcp.rs per Clippy suggestion
API Deprecation:
- Mark legacy APIs with @deprecated JSDoc (getConfig, upsertServerInConfig, etc.)
- Preserve backward compatibility for v3.x, planned removal in v4.0
Verification:
- ✅ Zero TypeScript errors (pnpm typecheck)
- ✅ Zero Clippy warnings (cargo clippy)
- ✅ All code formatted (prettier + cargo fmt)
- ✅ Builds successfully
Total cleanup: ~1,350 lines of code removed/marked
Breaking changes: None (all legacy APIs still functional)
2025-11-14 22:43:25 +08:00
|
|
|
|
// 保存到统一配置
|
|
|
|
|
|
await upsertMutation.mutateAsync(entry);
|
|
|
|
|
|
toast.success(t("common.success"));
|
|
|
|
|
|
await onSave(); // 通知父组件关闭表单
|
2025-10-09 16:44:28 +08:00
|
|
|
|
} catch (error: any) {
|
2025-10-09 17:21:03 +08:00
|
|
|
|
const detail = extractErrorMessage(error);
|
2025-10-11 16:20:12 +08:00
|
|
|
|
const mapped = translateMcpBackendError(detail, t);
|
|
|
|
|
|
const msg = mapped || detail || t("mcp.error.saveFailed");
|
2025-10-17 17:49:16 +08:00
|
|
|
|
toast.error(msg, { duration: mapped || detail ? 6000 : 4000 });
|
2025-10-09 11:04:36 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
setSaving(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-10 20:52:16 +08:00
|
|
|
|
const getFormTitle = () => {
|
2025-11-15 23:47:35 +08:00
|
|
|
|
return isEditing ? t("mcp.editServer") : t("mcp.addServer");
|
2025-10-10 20:52:16 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-09 11:04:36 +08:00
|
|
|
|
return (
|
2025-10-16 16:20:45 +08:00
|
|
|
|
<>
|
|
|
|
|
|
<Dialog open={true} onOpenChange={(open) => !open && onClose()}>
|
|
|
|
|
|
<DialogContent className="max-w-3xl max-h-[90vh] flex flex-col">
|
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
|
<DialogTitle>{getFormTitle()}</DialogTitle>
|
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Content - Scrollable */}
|
2025-10-18 16:52:02 +08:00
|
|
|
|
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-4">
|
2025-10-16 16:20:45 +08:00
|
|
|
|
{/* 预设选择(仅新增时展示) */}
|
|
|
|
|
|
{!isEditing && (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
|
|
|
|
|
|
{t("mcp.presets.title")}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={applyCustom}
|
|
|
|
|
|
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
|
|
|
|
|
selectedPreset === -1
|
|
|
|
|
|
? "bg-emerald-500 text-white dark:bg-emerald-600"
|
|
|
|
|
|
: "bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{t("presetSelector.custom")}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
{mcpPresets.map((preset, idx) => {
|
|
|
|
|
|
const descriptionKey = `mcp.presets.${preset.id}.description`;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={preset.id}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => applyPreset(idx)}
|
|
|
|
|
|
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
|
|
|
|
|
selectedPreset === idx
|
|
|
|
|
|
? "bg-emerald-500 text-white dark:bg-emerald-600"
|
|
|
|
|
|
: "bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
title={t(descriptionKey)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{preset.id}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{/* ID (标题) */}
|
2025-10-11 09:55:54 +08:00
|
|
|
|
<div>
|
2025-10-16 16:20:45 +08:00
|
|
|
|
<div className="flex items-center justify-between mb-2">
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
|
|
|
|
{t("mcp.form.title")} <span className="text-red-500">*</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
{!isEditing && idError && (
|
|
|
|
|
|
<span className="text-xs text-red-500 dark:text-red-400">
|
|
|
|
|
|
{idError}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
2025-10-10 22:34:38 +08:00
|
|
|
|
</div>
|
2025-10-16 16:20:45 +08:00
|
|
|
|
<Input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder={t("mcp.form.titlePlaceholder")}
|
|
|
|
|
|
value={formId}
|
|
|
|
|
|
onChange={(e) => handleIdChange(e.target.value)}
|
|
|
|
|
|
disabled={isEditing}
|
|
|
|
|
|
/>
|
2025-10-10 22:34:38 +08:00
|
|
|
|
</div>
|
2025-10-16 16:20:45 +08:00
|
|
|
|
|
|
|
|
|
|
{/* Name */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
|
|
|
|
{t("mcp.form.name")}
|
2025-10-10 11:17:40 +08:00
|
|
|
|
</label>
|
2025-10-16 16:20:45 +08:00
|
|
|
|
<Input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder={t("mcp.form.namePlaceholder")}
|
|
|
|
|
|
value={formName}
|
|
|
|
|
|
onChange={(e) => setFormName(e.target.value)}
|
|
|
|
|
|
/>
|
2025-10-10 11:17:40 +08:00
|
|
|
|
</div>
|
2025-10-12 00:08:37 +08:00
|
|
|
|
|
2025-11-15 23:47:35 +08:00
|
|
|
|
{/* 启用到哪些应用(v3.7.0 新增) */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
|
|
|
|
|
{t("mcp.form.enabledApps")}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div className="flex flex-wrap gap-4">
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<Checkbox
|
|
|
|
|
|
id="enable-claude"
|
|
|
|
|
|
checked={enabledApps.claude}
|
|
|
|
|
|
onCheckedChange={(checked: boolean) =>
|
|
|
|
|
|
setEnabledApps({ ...enabledApps, claude: checked })
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<label
|
|
|
|
|
|
htmlFor="enable-claude"
|
|
|
|
|
|
className="text-sm text-gray-700 dark:text-gray-300 cursor-pointer select-none"
|
|
|
|
|
|
>
|
|
|
|
|
|
{t("mcp.unifiedPanel.apps.claude")}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<Checkbox
|
|
|
|
|
|
id="enable-codex"
|
|
|
|
|
|
checked={enabledApps.codex}
|
|
|
|
|
|
onCheckedChange={(checked: boolean) =>
|
|
|
|
|
|
setEnabledApps({ ...enabledApps, codex: checked })
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<label
|
|
|
|
|
|
htmlFor="enable-codex"
|
|
|
|
|
|
className="text-sm text-gray-700 dark:text-gray-300 cursor-pointer select-none"
|
|
|
|
|
|
>
|
|
|
|
|
|
{t("mcp.unifiedPanel.apps.codex")}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<Checkbox
|
|
|
|
|
|
id="enable-gemini"
|
|
|
|
|
|
checked={enabledApps.gemini}
|
|
|
|
|
|
onCheckedChange={(checked: boolean) =>
|
|
|
|
|
|
setEnabledApps({ ...enabledApps, gemini: checked })
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<label
|
|
|
|
|
|
htmlFor="enable-gemini"
|
|
|
|
|
|
className="text-sm text-gray-700 dark:text-gray-300 cursor-pointer select-none"
|
|
|
|
|
|
>
|
|
|
|
|
|
{t("mcp.unifiedPanel.apps.gemini")}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-16 16:20:45 +08:00
|
|
|
|
{/* 可折叠的附加信息按钮 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => setShowMetadata(!showMetadata)}
|
|
|
|
|
|
className="flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
{showMetadata ? (
|
|
|
|
|
|
<ChevronUp size={16} />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<ChevronDown size={16} />
|
|
|
|
|
|
)}
|
|
|
|
|
|
{t("mcp.form.additionalInfo")}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2025-10-09 11:04:36 +08:00
|
|
|
|
|
2025-10-16 16:20:45 +08:00
|
|
|
|
{/* 附加信息区域(可折叠) */}
|
|
|
|
|
|
{showMetadata && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{/* Description (描述) */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
|
|
|
|
{t("mcp.form.description")}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder={t("mcp.form.descriptionPlaceholder")}
|
|
|
|
|
|
value={formDescription}
|
|
|
|
|
|
onChange={(e) => setFormDescription(e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Tags */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
|
|
|
|
{t("mcp.form.tags")}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder={t("mcp.form.tagsPlaceholder")}
|
|
|
|
|
|
value={formTags}
|
|
|
|
|
|
onChange={(e) => setFormTags(e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Homepage */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
|
|
|
|
{t("mcp.form.homepage")}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder={t("mcp.form.homepagePlaceholder")}
|
|
|
|
|
|
value={formHomepage}
|
|
|
|
|
|
onChange={(e) => setFormHomepage(e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Docs */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
|
|
|
|
{t("mcp.form.docs")}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder={t("mcp.form.docsPlaceholder")}
|
|
|
|
|
|
value={formDocs}
|
|
|
|
|
|
onChange={(e) => setFormDocs(e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
2025-10-12 00:08:37 +08:00
|
|
|
|
|
2025-10-16 16:20:45 +08:00
|
|
|
|
{/* 配置输入框(根据格式显示 JSON 或 TOML) */}
|
|
|
|
|
|
<div>
|
2025-11-16 16:59:23 +08:00
|
|
|
|
<div className="flex items-center justify-between mb-2">
|
2025-11-16 16:50:07 +08:00
|
|
|
|
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
2025-10-16 16:20:45 +08:00
|
|
|
|
{useToml
|
2025-11-16 16:59:23 +08:00
|
|
|
|
? t("mcp.form.tomlConfig")
|
|
|
|
|
|
: t("mcp.form.jsonConfig")}
|
2025-10-12 11:01:49 +08:00
|
|
|
|
</label>
|
2025-10-16 16:20:45 +08:00
|
|
|
|
{(isEditing || selectedPreset === -1) && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => setIsWizardOpen(true)}
|
|
|
|
|
|
className="text-sm text-blue-500 dark:text-blue-400 hover:text-blue-600 dark:hover:text-blue-300 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
{t("mcp.form.useWizard")}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
2025-10-12 11:01:49 +08:00
|
|
|
|
</div>
|
2025-10-16 16:20:45 +08:00
|
|
|
|
<Textarea
|
|
|
|
|
|
className="h-48 resize-none font-mono text-xs"
|
|
|
|
|
|
placeholder={
|
|
|
|
|
|
useToml
|
|
|
|
|
|
? t("mcp.form.tomlPlaceholder")
|
|
|
|
|
|
: t("mcp.form.jsonPlaceholder")
|
|
|
|
|
|
}
|
|
|
|
|
|
value={formConfig}
|
|
|
|
|
|
onChange={(e) => handleConfigChange(e.target.value)}
|
|
|
|
|
|
/>
|
2025-11-16 16:50:07 +08:00
|
|
|
|
{/* 格式化按钮(仅 JSON 模式) */}
|
|
|
|
|
|
{!useToml && (
|
|
|
|
|
|
<div className="flex items-center justify-between mt-2">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={handleFormatJson}
|
|
|
|
|
|
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Wand2 className="w-3.5 h-3.5" />
|
|
|
|
|
|
{t("common.format")}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-10-16 16:20:45 +08:00
|
|
|
|
{configError && (
|
|
|
|
|
|
<div className="flex items-center gap-2 mt-2 text-red-500 dark:text-red-400 text-sm">
|
|
|
|
|
|
<AlertCircle size={16} />
|
|
|
|
|
|
<span>{configError}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-12 00:08:37 +08:00
|
|
|
|
|
2025-10-16 16:20:45 +08:00
|
|
|
|
{/* Footer */}
|
refactor(mcp): complete v3.7.0 cleanup - remove legacy code and warnings
This commit finalizes the v3.7.0 unified MCP architecture migration by
removing all deprecated code paths and eliminating compiler warnings.
Frontend Changes (~950 lines removed):
- Remove deprecated components: McpPanel, McpListItem, McpToggle
- Remove deprecated hook: useMcpActions
- Remove unused API methods: importFrom*, syncEnabledTo*, syncAllServers
- Simplify McpFormModal by removing dual-mode logic (unified/legacy)
- Remove syncOtherSide checkbox and conflict detection
- Clean up unused imports and state variables
- Delete associated test files
Backend Changes (~400 lines cleaned):
- Remove unused Tauri commands: import_mcp_from_*, sync_enabled_mcp_to_*
- Delete unused Gemini MCP functions: get_mcp_status, upsert/delete_mcp_server
- Add #[allow(deprecated)] to compatibility layer commands
- Add #[allow(dead_code)] to legacy helper functions for future migration
- Simplify boolean expression in mcp.rs per Clippy suggestion
API Deprecation:
- Mark legacy APIs with @deprecated JSDoc (getConfig, upsertServerInConfig, etc.)
- Preserve backward compatibility for v3.x, planned removal in v4.0
Verification:
- ✅ Zero TypeScript errors (pnpm typecheck)
- ✅ Zero Clippy warnings (cargo clippy)
- ✅ All code formatted (prettier + cargo fmt)
- ✅ Builds successfully
Total cleanup: ~1,350 lines of code removed/marked
Breaking changes: None (all legacy APIs still functional)
2025-11-14 22:43:25 +08:00
|
|
|
|
<DialogFooter className="flex justify-end gap-3 pt-4">
|
2025-10-16 16:20:45 +08:00
|
|
|
|
{/* 操作按钮 */}
|
refactor(mcp): complete v3.7.0 cleanup - remove legacy code and warnings
This commit finalizes the v3.7.0 unified MCP architecture migration by
removing all deprecated code paths and eliminating compiler warnings.
Frontend Changes (~950 lines removed):
- Remove deprecated components: McpPanel, McpListItem, McpToggle
- Remove deprecated hook: useMcpActions
- Remove unused API methods: importFrom*, syncEnabledTo*, syncAllServers
- Simplify McpFormModal by removing dual-mode logic (unified/legacy)
- Remove syncOtherSide checkbox and conflict detection
- Clean up unused imports and state variables
- Delete associated test files
Backend Changes (~400 lines cleaned):
- Remove unused Tauri commands: import_mcp_from_*, sync_enabled_mcp_to_*
- Delete unused Gemini MCP functions: get_mcp_status, upsert/delete_mcp_server
- Add #[allow(deprecated)] to compatibility layer commands
- Add #[allow(dead_code)] to legacy helper functions for future migration
- Simplify boolean expression in mcp.rs per Clippy suggestion
API Deprecation:
- Mark legacy APIs with @deprecated JSDoc (getConfig, upsertServerInConfig, etc.)
- Preserve backward compatibility for v3.x, planned removal in v4.0
Verification:
- ✅ Zero TypeScript errors (pnpm typecheck)
- ✅ Zero Clippy warnings (cargo clippy)
- ✅ All code formatted (prettier + cargo fmt)
- ✅ Builds successfully
Total cleanup: ~1,350 lines of code removed/marked
Breaking changes: None (all legacy APIs still functional)
2025-11-14 22:43:25 +08:00
|
|
|
|
<Button type="button" variant="ghost" onClick={onClose}>
|
|
|
|
|
|
{t("common.cancel")}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={handleSubmit}
|
|
|
|
|
|
disabled={saving || (!isEditing && !!idError)}
|
|
|
|
|
|
variant="mcp"
|
|
|
|
|
|
>
|
|
|
|
|
|
{isEditing ? <Save size={16} /> : <Plus size={16} />}
|
|
|
|
|
|
{saving
|
|
|
|
|
|
? t("common.saving")
|
|
|
|
|
|
: isEditing
|
|
|
|
|
|
? t("common.save")
|
|
|
|
|
|
: t("common.add")}
|
|
|
|
|
|
</Button>
|
2025-10-16 16:20:45 +08:00
|
|
|
|
</DialogFooter>
|
|
|
|
|
|
</DialogContent>
|
|
|
|
|
|
</Dialog>
|
2025-10-09 11:30:28 +08:00
|
|
|
|
|
|
|
|
|
|
{/* Wizard Modal */}
|
|
|
|
|
|
<McpWizardModal
|
|
|
|
|
|
isOpen={isWizardOpen}
|
|
|
|
|
|
onClose={() => setIsWizardOpen(false)}
|
|
|
|
|
|
onApply={handleWizardApply}
|
2025-10-13 23:37:33 +08:00
|
|
|
|
initialTitle={formId}
|
|
|
|
|
|
initialServer={wizardInitialSpec}
|
2025-10-09 11:30:28 +08:00
|
|
|
|
/>
|
2025-10-16 16:20:45 +08:00
|
|
|
|
</>
|
2025-10-09 11:04:36 +08:00
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default McpFormModal;
|