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
This commit is contained in:
@@ -317,7 +317,9 @@ impl MultiAppConfig {
|
|||||||
|
|
||||||
// 迁移通用配置片段:claude_common_config_snippet → common_config_snippets.claude
|
// 迁移通用配置片段:claude_common_config_snippet → common_config_snippets.claude
|
||||||
if let Some(old_claude_snippet) = config.claude_common_config_snippet.take() {
|
if let Some(old_claude_snippet) = config.claude_common_config_snippet.take() {
|
||||||
log::info!("迁移通用配置:claude_common_config_snippet → common_config_snippets.claude");
|
log::info!(
|
||||||
|
"迁移通用配置:claude_common_config_snippet → common_config_snippets.claude"
|
||||||
|
);
|
||||||
config.common_config_snippets.claude = Some(old_claude_snippet);
|
config.common_config_snippets.claude = Some(old_claude_snippet);
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
@@ -414,9 +416,7 @@ impl MultiAppConfig {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(
|
log::info!("检测到已存在配置文件且 Prompt 列表为空,将尝试从现有提示词文件自动导入");
|
||||||
"检测到已存在配置文件且 Prompt 列表为空,将尝试从现有提示词文件自动导入"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut imported = false;
|
let mut imported = false;
|
||||||
for app in [AppType::Claude, AppType::Codex, AppType::Gemini] {
|
for app in [AppType::Claude, AppType::Codex, AppType::Gemini] {
|
||||||
|
|||||||
@@ -139,13 +139,11 @@ pub fn upsert_mcp_server(id: &str, spec: Value) -> Result<bool, AppError> {
|
|||||||
if is_http || is_sse {
|
if is_http || is_sse {
|
||||||
let url = spec.get("url").and_then(|x| x.as_str()).unwrap_or("");
|
let url = spec.get("url").and_then(|x| x.as_str()).unwrap_or("");
|
||||||
if url.is_empty() {
|
if url.is_empty() {
|
||||||
return Err(AppError::McpValidation(
|
return Err(AppError::McpValidation(if is_http {
|
||||||
if is_http {
|
"http 类型的 MCP 服务器缺少 url 字段".into()
|
||||||
"http 类型的 MCP 服务器缺少 url 字段".into()
|
} else {
|
||||||
} else {
|
"sse 类型的 MCP 服务器缺少 url 字段".into()
|
||||||
"sse 类型的 MCP 服务器缺少 url 字段".into()
|
}));
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,13 +184,12 @@ pub async fn get_common_config_snippet(
|
|||||||
use crate::app_config::AppType;
|
use crate::app_config::AppType;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
let app = AppType::from_str(&app_type)
|
let app = AppType::from_str(&app_type).map_err(|e| format!("无效的应用类型: {e}"))?;
|
||||||
.map_err(|e| format!("无效的应用类型: {}", e))?;
|
|
||||||
|
|
||||||
let guard = state
|
let guard = state
|
||||||
.config
|
.config
|
||||||
.read()
|
.read()
|
||||||
.map_err(|e| format!("读取配置锁失败: {}", e))?;
|
.map_err(|e| format!("读取配置锁失败: {e}"))?;
|
||||||
|
|
||||||
Ok(guard.common_config_snippets.get(&app).cloned())
|
Ok(guard.common_config_snippets.get(&app).cloned())
|
||||||
}
|
}
|
||||||
@@ -205,13 +204,12 @@ pub async fn set_common_config_snippet(
|
|||||||
use crate::app_config::AppType;
|
use crate::app_config::AppType;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
let app = AppType::from_str(&app_type)
|
let app = AppType::from_str(&app_type).map_err(|e| format!("无效的应用类型: {e}"))?;
|
||||||
.map_err(|e| format!("无效的应用类型: {}", e))?;
|
|
||||||
|
|
||||||
let mut guard = state
|
let mut guard = state
|
||||||
.config
|
.config
|
||||||
.write()
|
.write()
|
||||||
.map_err(|e| format!("写入配置锁失败: {}", e))?;
|
.map_err(|e| format!("写入配置锁失败: {e}"))?;
|
||||||
|
|
||||||
// 验证格式(根据应用类型)
|
// 验证格式(根据应用类型)
|
||||||
if !snippet.trim().is_empty() {
|
if !snippet.trim().is_empty() {
|
||||||
@@ -219,7 +217,7 @@ pub async fn set_common_config_snippet(
|
|||||||
AppType::Claude | AppType::Gemini => {
|
AppType::Claude | AppType::Gemini => {
|
||||||
// 验证 JSON 格式
|
// 验证 JSON 格式
|
||||||
serde_json::from_str::<serde_json::Value>(&snippet)
|
serde_json::from_str::<serde_json::Value>(&snippet)
|
||||||
.map_err(|e| format!("无效的 JSON 格式: {}", e))?;
|
.map_err(|e| format!("无效的 JSON 格式: {e}"))?;
|
||||||
}
|
}
|
||||||
AppType::Codex => {
|
AppType::Codex => {
|
||||||
// TOML 格式暂不验证(或可使用 toml crate)
|
// TOML 格式暂不验证(或可使用 toml crate)
|
||||||
|
|||||||
@@ -48,8 +48,6 @@ pub fn read_mcp_json() -> Result<Option<String>, AppError> {
|
|||||||
Ok(Some(content))
|
Ok(Some(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// 读取 Gemini settings.json 中的 mcpServers 映射
|
/// 读取 Gemini settings.json 中的 mcpServers 映射
|
||||||
pub fn read_mcp_servers_map() -> Result<std::collections::HashMap<String, Value>, AppError> {
|
pub fn read_mcp_servers_map() -> Result<std::collections::HashMap<String, Value>, AppError> {
|
||||||
let path = user_config_path();
|
let path = user_config_path();
|
||||||
|
|||||||
@@ -396,11 +396,7 @@ pub fn import_from_claude(config: &mut MultiAppConfig) -> Result<usize, AppError
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
log::warn!(
|
log::warn!("导入完成,但有 {} 项失败: {:?}", errors.len(), errors);
|
||||||
"导入完成,但有 {} 项失败: {:?}",
|
|
||||||
errors.len(),
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(changed)
|
Ok(changed)
|
||||||
@@ -783,11 +779,7 @@ pub fn import_from_gemini(config: &mut MultiAppConfig) -> Result<usize, AppError
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
log::warn!(
|
log::warn!("导入完成,但有 {} 项失败: {:?}", errors.len(), errors);
|
||||||
"导入完成,但有 {} 项失败: {:?}",
|
|
||||||
errors.len(),
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(changed)
|
Ok(changed)
|
||||||
|
|||||||
@@ -488,10 +488,17 @@ url = "https://example.com"
|
|||||||
assert!(changed >= 2, "should import both servers");
|
assert!(changed >= 2, "should import both servers");
|
||||||
|
|
||||||
// v3.7.0: 检查统一结构
|
// v3.7.0: 检查统一结构
|
||||||
let servers = config.mcp.servers.as_ref().expect("unified servers should exist");
|
let servers = config
|
||||||
|
.mcp
|
||||||
|
.servers
|
||||||
|
.as_ref()
|
||||||
|
.expect("unified servers should exist");
|
||||||
|
|
||||||
let echo = servers.get("echo_server").expect("echo server");
|
let echo = servers.get("echo_server").expect("echo server");
|
||||||
assert_eq!(echo.apps.codex, true, "Codex app should be enabled for echo_server");
|
assert_eq!(
|
||||||
|
echo.apps.codex, true,
|
||||||
|
"Codex app should be enabled for echo_server"
|
||||||
|
);
|
||||||
let server_spec = echo.server.as_object().expect("server spec");
|
let server_spec = echo.server.as_object().expect("server spec");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
server_spec
|
server_spec
|
||||||
@@ -502,7 +509,10 @@ url = "https://example.com"
|
|||||||
);
|
);
|
||||||
|
|
||||||
let http = servers.get("http_server").expect("http server");
|
let http = servers.get("http_server").expect("http server");
|
||||||
assert_eq!(http.apps.codex, true, "Codex app should be enabled for http_server");
|
assert_eq!(
|
||||||
|
http.apps.codex, true,
|
||||||
|
"Codex app should be enabled for http_server"
|
||||||
|
);
|
||||||
let http_spec = http.server.as_object().expect("http spec");
|
let http_spec = http.server.as_object().expect("http spec");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
http_spec.get("url").and_then(|v| v.as_str()).unwrap_or(""),
|
http_spec.get("url").and_then(|v| v.as_str()).unwrap_or(""),
|
||||||
@@ -541,7 +551,7 @@ command = "echo"
|
|||||||
}),
|
}),
|
||||||
apps: cc_switch_lib::McpApps {
|
apps: cc_switch_lib::McpApps {
|
||||||
claude: false,
|
claude: false,
|
||||||
codex: false, // 初始未启用
|
codex: false, // 初始未启用
|
||||||
gemini: false,
|
gemini: false,
|
||||||
},
|
},
|
||||||
description: None,
|
description: None,
|
||||||
@@ -564,7 +574,10 @@ command = "echo"
|
|||||||
.expect("existing entry");
|
.expect("existing entry");
|
||||||
|
|
||||||
// 验证 Codex 应用已启用
|
// 验证 Codex 应用已启用
|
||||||
assert_eq!(entry.apps.codex, true, "Codex app should be enabled after import");
|
assert_eq!(
|
||||||
|
entry.apps.codex, true,
|
||||||
|
"Codex app should be enabled after import"
|
||||||
|
);
|
||||||
|
|
||||||
// 验证现有配置被保留(server 不应被覆盖)
|
// 验证现有配置被保留(server 不应被覆盖)
|
||||||
let spec = entry.server.as_object().expect("server spec");
|
let spec = entry.server.as_object().expect("server spec");
|
||||||
@@ -662,7 +675,7 @@ fn import_from_claude_merges_into_config() {
|
|||||||
"command": "prev"
|
"command": "prev"
|
||||||
}),
|
}),
|
||||||
apps: cc_switch_lib::McpApps {
|
apps: cc_switch_lib::McpApps {
|
||||||
claude: false, // 初始未启用
|
claude: false, // 初始未启用
|
||||||
codex: false,
|
codex: false,
|
||||||
gemini: false,
|
gemini: false,
|
||||||
},
|
},
|
||||||
@@ -686,7 +699,10 @@ fn import_from_claude_merges_into_config() {
|
|||||||
.expect("entry exists");
|
.expect("entry exists");
|
||||||
|
|
||||||
// 验证 Claude 应用已启用
|
// 验证 Claude 应用已启用
|
||||||
assert_eq!(entry.apps.claude, true, "Claude app should be enabled after import");
|
assert_eq!(
|
||||||
|
entry.apps.claude, true,
|
||||||
|
"Claude app should be enabled after import"
|
||||||
|
);
|
||||||
|
|
||||||
// 验证现有配置被保留(server 不应被覆盖)
|
// 验证现有配置被保留(server 不应被覆盖)
|
||||||
let server = entry.server.as_object().expect("server obj");
|
let server = entry.server.as_object().expect("server obj");
|
||||||
|
|||||||
@@ -127,8 +127,14 @@ fn import_mcp_from_claude_creates_config_and_enables_servers() {
|
|||||||
|
|
||||||
let guard = state.config.read().expect("lock config");
|
let guard = state.config.read().expect("lock config");
|
||||||
// v3.7.0: 检查统一结构
|
// v3.7.0: 检查统一结构
|
||||||
let servers = guard.mcp.servers.as_ref().expect("unified servers should exist");
|
let servers = guard
|
||||||
let entry = servers.get("echo").expect("server imported into unified structure");
|
.mcp
|
||||||
|
.servers
|
||||||
|
.as_ref()
|
||||||
|
.expect("unified servers should exist");
|
||||||
|
let entry = servers
|
||||||
|
.get("echo")
|
||||||
|
.expect("server imported into unified structure");
|
||||||
assert!(
|
assert!(
|
||||||
entry.apps.claude,
|
entry.apps.claude,
|
||||||
"imported server should have Claude app enabled"
|
"imported server should have Claude app enabled"
|
||||||
@@ -182,10 +188,12 @@ fn set_mcp_enabled_for_codex_writes_live_config() {
|
|||||||
// 创建 Codex 配置目录和文件
|
// 创建 Codex 配置目录和文件
|
||||||
let codex_dir = home.join(".codex");
|
let codex_dir = home.join(".codex");
|
||||||
fs::create_dir_all(&codex_dir).expect("create codex dir");
|
fs::create_dir_all(&codex_dir).expect("create codex dir");
|
||||||
fs::write(codex_dir.join("auth.json"), r#"{"OPENAI_API_KEY":"test-key"}"#)
|
fs::write(
|
||||||
.expect("create auth.json");
|
codex_dir.join("auth.json"),
|
||||||
fs::write(codex_dir.join("config.toml"), "")
|
r#"{"OPENAI_API_KEY":"test-key"}"#,
|
||||||
.expect("create empty config.toml");
|
)
|
||||||
|
.expect("create auth.json");
|
||||||
|
fs::write(codex_dir.join("config.toml"), "").expect("create empty config.toml");
|
||||||
|
|
||||||
let mut config = MultiAppConfig::default();
|
let mut config = MultiAppConfig::default();
|
||||||
config.ensure_app(&AppType::Codex);
|
config.ensure_app(&AppType::Codex);
|
||||||
@@ -203,7 +211,7 @@ fn set_mcp_enabled_for_codex_writes_live_config() {
|
|||||||
}),
|
}),
|
||||||
apps: McpApps {
|
apps: McpApps {
|
||||||
claude: false,
|
claude: false,
|
||||||
codex: false, // 初始未启用
|
codex: false, // 初始未启用
|
||||||
gemini: false,
|
gemini: false,
|
||||||
},
|
},
|
||||||
description: None,
|
description: None,
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Save, Plus, AlertCircle, ChevronDown, ChevronUp, Wand2 } from "lucide-react";
|
import {
|
||||||
|
Save,
|
||||||
|
Plus,
|
||||||
|
AlertCircle,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp,
|
||||||
|
Wand2,
|
||||||
|
} from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ const McpWizardModal: React.FC<McpWizardModalProps> = ({
|
|||||||
initialServer,
|
initialServer,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [wizardType, setWizardType] = useState<"stdio" | "http" | "sse">("stdio");
|
const [wizardType, setWizardType] = useState<"stdio" | "http" | "sse">(
|
||||||
|
"stdio",
|
||||||
|
);
|
||||||
const [wizardTitle, setWizardTitle] = useState("");
|
const [wizardTitle, setWizardTitle] = useState("");
|
||||||
// stdio 字段
|
// stdio 字段
|
||||||
const [wizardCommand, setWizardCommand] = useState("");
|
const [wizardCommand, setWizardCommand] = useState("");
|
||||||
|
|||||||
@@ -76,10 +76,7 @@ export function useMcpValidation() {
|
|||||||
if (typ === "stdio" && !(obj as any)?.command?.trim()) {
|
if (typ === "stdio" && !(obj as any)?.command?.trim()) {
|
||||||
return t("mcp.error.commandRequired");
|
return t("mcp.error.commandRequired");
|
||||||
}
|
}
|
||||||
if (
|
if ((typ === "http" || typ === "sse") && !(obj as any)?.url?.trim()) {
|
||||||
(typ === "http" || typ === "sse") &&
|
|
||||||
!(obj as any)?.url?.trim()
|
|
||||||
) {
|
|
||||||
return t("mcp.wizard.urlRequired");
|
return t("mcp.wizard.urlRequired");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export function AddProviderDialog({
|
|||||||
// 构造基础提交数据
|
// 构造基础提交数据
|
||||||
const providerData: Omit<Provider, "id"> = {
|
const providerData: Omit<Provider, "id"> = {
|
||||||
name: values.name.trim(),
|
name: values.name.trim(),
|
||||||
|
notes: values.notes?.trim() || undefined,
|
||||||
websiteUrl: values.websiteUrl?.trim() || undefined,
|
websiteUrl: values.websiteUrl?.trim() || undefined,
|
||||||
settingsConfig: parsedConfig,
|
settingsConfig: parsedConfig,
|
||||||
...(values.presetCategory ? { category: values.presetCategory } : {}),
|
...(values.presetCategory ? { category: values.presetCategory } : {}),
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export function EditProviderDialog({
|
|||||||
const updatedProvider: Provider = {
|
const updatedProvider: Provider = {
|
||||||
...provider,
|
...provider,
|
||||||
name: values.name.trim(),
|
name: values.name.trim(),
|
||||||
|
notes: values.notes?.trim() || undefined,
|
||||||
websiteUrl: values.websiteUrl?.trim() || undefined,
|
websiteUrl: values.websiteUrl?.trim() || undefined,
|
||||||
settingsConfig: parsedConfig,
|
settingsConfig: parsedConfig,
|
||||||
...(values.presetCategory ? { category: values.presetCategory } : {}),
|
...(values.presetCategory ? { category: values.presetCategory } : {}),
|
||||||
@@ -129,6 +130,7 @@ export function EditProviderDialog({
|
|||||||
onCancel={() => onOpenChange(false)}
|
onCancel={() => onOpenChange(false)}
|
||||||
initialData={{
|
initialData={{
|
||||||
name: provider.name,
|
name: provider.name,
|
||||||
|
notes: provider.notes,
|
||||||
websiteUrl: provider.websiteUrl,
|
websiteUrl: provider.websiteUrl,
|
||||||
// 若读取到实时配置则优先使用
|
// 若读取到实时配置则优先使用
|
||||||
settingsConfig: initialSettingsConfig,
|
settingsConfig: initialSettingsConfig,
|
||||||
|
|||||||
@@ -623,7 +623,6 @@ export function ProviderForm({
|
|||||||
presetCategoryLabels={presetCategoryLabels}
|
presetCategoryLabels={presetCategoryLabels}
|
||||||
onPresetChange={handlePresetChange}
|
onPresetChange={handlePresetChange}
|
||||||
category={category}
|
category={category}
|
||||||
appId={appId}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import type { ProviderPreset } from "@/config/claudeProviderPresets";
|
|||||||
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
|
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
|
||||||
import type { GeminiProviderPreset } from "@/config/geminiProviderPresets";
|
import type { GeminiProviderPreset } from "@/config/geminiProviderPresets";
|
||||||
import type { ProviderCategory } from "@/types";
|
import type { ProviderCategory } from "@/types";
|
||||||
import type { AppId } from "@/lib/api";
|
|
||||||
|
|
||||||
type PresetEntry = {
|
type PresetEntry = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -20,7 +19,6 @@ interface ProviderPresetSelectorProps {
|
|||||||
presetCategoryLabels: Record<string, string>;
|
presetCategoryLabels: Record<string, string>;
|
||||||
onPresetChange: (value: string) => void;
|
onPresetChange: (value: string) => void;
|
||||||
category?: ProviderCategory; // 当前选中的分类
|
category?: ProviderCategory; // 当前选中的分类
|
||||||
appId?: AppId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProviderPresetSelector({
|
export function ProviderPresetSelector({
|
||||||
@@ -30,7 +28,6 @@ export function ProviderPresetSelector({
|
|||||||
presetCategoryLabels,
|
presetCategoryLabels,
|
||||||
onPresetChange,
|
onPresetChange,
|
||||||
category,
|
category,
|
||||||
appId,
|
|
||||||
}: ProviderPresetSelectorProps) {
|
}: ProviderPresetSelectorProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ describe("McpFormModal", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("缺少配置命令时阻止提交并提示错误", async () => {
|
it("缺少配置命令时阻止提交并提示错误", async () => {
|
||||||
const { onSave } = renderForm();
|
renderForm();
|
||||||
|
|
||||||
fireEvent.change(screen.getByPlaceholderText("mcp.form.titlePlaceholder"), {
|
fireEvent.change(screen.getByPlaceholderText("mcp.form.titlePlaceholder"), {
|
||||||
target: { value: "no-command" },
|
target: { value: "no-command" },
|
||||||
@@ -288,7 +288,7 @@ command = "run"
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("TOML 模式下缺少命令时展示错误提示并阻止提交", async () => {
|
it("TOML 模式下缺少命令时展示错误提示并阻止提交", async () => {
|
||||||
const { onSave } = renderForm({ defaultFormat: "toml" });
|
renderForm({ defaultFormat: "toml" });
|
||||||
|
|
||||||
// 填写 ID 字段
|
// 填写 ID 字段
|
||||||
fireEvent.change(screen.getByPlaceholderText("mcp.form.titlePlaceholder"), {
|
fireEvent.change(screen.getByPlaceholderText("mcp.form.titlePlaceholder"), {
|
||||||
|
|||||||
Reference in New Issue
Block a user