diff --git a/src-tauri/src/claude_desktop_config.rs b/src-tauri/src/claude_desktop_config.rs index 96095710e..8bb53701f 100644 --- a/src-tauri/src/claude_desktop_config.rs +++ b/src-tauri/src/claude_desktop_config.rs @@ -28,7 +28,6 @@ const ONE_M_CONTEXT_SUFFIX: &str = " [1M]"; pub struct ClaudeDesktopDefaultRoute { pub route_id: &'static str, pub env_key: &'static str, - pub display_name: &'static str, #[serde(rename = "supports1m")] pub supports_1m: bool, } @@ -37,19 +36,16 @@ pub const DEFAULT_PROXY_ROUTES: &[ClaudeDesktopDefaultRoute] = &[ ClaudeDesktopDefaultRoute { route_id: "claude-sonnet-4-6", env_key: "ANTHROPIC_DEFAULT_SONNET_MODEL", - display_name: "Sonnet", supports_1m: true, }, ClaudeDesktopDefaultRoute { route_id: "claude-opus-4-7", env_key: "ANTHROPIC_DEFAULT_OPUS_MODEL", - display_name: "Opus", supports_1m: true, }, ClaudeDesktopDefaultRoute { route_id: "claude-haiku-4-5", env_key: "ANTHROPIC_DEFAULT_HAIKU_MODEL", - display_name: "Haiku", supports_1m: true, }, ]; @@ -96,7 +92,6 @@ pub struct ClaudeDesktopStatus { pub struct ResolvedModelRoute { pub route_id: String, pub upstream_model: String, - pub display_name: Option, pub supports_1m: bool, } @@ -518,7 +513,6 @@ pub fn proxy_model_routes(provider: &Provider) -> Result result.push(ResolvedModelRoute { route_id: desktop_model_id(&route_id, supports_1m), upstream_model: upstream_model_id(upstream_model, supports_1m), - display_name: route.display_name.clone(), supports_1m, }); } @@ -546,7 +540,6 @@ pub fn model_list_response(provider: &Provider) -> Result { let mut item = json!({ "type": "model", "id": model_id, - "display_name": route.display_name.as_deref().unwrap_or(&route.route_id), "created_at": DEFAULT_CREATED_AT, }); if route.supports_1m { @@ -1065,7 +1058,6 @@ mod tests { "claude-sonnet-4-6".to_string(), ClaudeDesktopModelRoute { model: "kimi-k2".to_string(), - display_name: Some("Kimi".to_string()), supports_1m: Some(true), }, )]), @@ -1083,7 +1075,6 @@ mod tests { "claude-deepseek-chat".to_string(), ClaudeDesktopModelRoute { model: "claude-deepseek-chat".to_string(), - display_name: Some("DeepSeek".to_string()), supports_1m: Some(true), }, )]), diff --git a/src-tauri/src/commands/provider.rs b/src-tauri/src/commands/provider.rs index 5fceb49c8..fb6dc5ba3 100644 --- a/src-tauri/src/commands/provider.rs +++ b/src-tauri/src/commands/provider.rs @@ -255,7 +255,6 @@ fn suggested_claude_desktop_routes( env: &serde_json::Map, route_id: &str, env_key: &str, - display_name: &str, ) { if let Some(model) = env .get(env_key) @@ -267,7 +266,6 @@ fn suggested_claude_desktop_routes( route_id.to_string(), crate::provider::ClaudeDesktopModelRoute { model: model.to_string(), - display_name: Some(display_name.to_string()), supports_1m: Some(true), }, ); @@ -275,24 +273,12 @@ fn suggested_claude_desktop_routes( } for spec in crate::claude_desktop_config::DEFAULT_PROXY_ROUTES { - add_route( - &mut routes, - env, - spec.route_id, - spec.env_key, - spec.display_name, - ); + add_route(&mut routes, env, spec.route_id, spec.env_key); } let primary_route = crate::claude_desktop_config::DEFAULT_PROXY_ROUTES[0]; if !routes.contains_key(primary_route.route_id) { - add_route( - &mut routes, - env, - primary_route.route_id, - "ANTHROPIC_MODEL", - primary_route.display_name, - ); + add_route(&mut routes, env, primary_route.route_id, "ANTHROPIC_MODEL"); } (!routes.is_empty()).then_some(routes) diff --git a/src-tauri/src/provider.rs b/src-tauri/src/provider.rs index 64f57a525..49c6cea3b 100644 --- a/src-tauri/src/provider.rs +++ b/src-tauri/src/provider.rs @@ -230,9 +230,6 @@ pub enum ClaudeDesktopMode { pub struct ClaudeDesktopModelRoute { /// 真实上游模型名,只保存在 CC Switch 内部,不写入 Claude Desktop profile。 pub model: String, - /// Desktop /v1/models 中显示的名称。 - #[serde(skip_serializing_if = "Option::is_none")] - pub display_name: Option, /// Claude Desktop 3P 识别的 1M 上下文能力标记。 #[serde(rename = "supports1m", skip_serializing_if = "Option::is_none")] pub supports_1m: Option, diff --git a/src/components/providers/forms/ClaudeDesktopProviderForm.tsx b/src/components/providers/forms/ClaudeDesktopProviderForm.tsx index 45eef3793..140a4b196 100644 --- a/src/components/providers/forms/ClaudeDesktopProviderForm.tsx +++ b/src/components/providers/forms/ClaudeDesktopProviderForm.tsx @@ -100,10 +100,12 @@ export interface ClaudeDesktopProviderFormProps { type RouteRow = { route: string; model: string; - displayName: string; supports1m: boolean; }; +const CLAUDE_ROUTE_PREFIX = "claude-"; +const ANTHROPIC_CLAUDE_ROUTE_PREFIX = "anthropic/claude-"; + function envString( settingsConfig: Record | undefined, key: string, @@ -121,13 +123,50 @@ function clonePlainRecord(value: unknown): Record { return { ...(value as Record) }; } +function desktopRouteInputValue(route: string) { + const trimmed = route.trim(); + const lower = trimmed.toLowerCase(); + if (lower.startsWith(CLAUDE_ROUTE_PREFIX)) { + return trimmed.slice(CLAUDE_ROUTE_PREFIX.length); + } + if (lower.startsWith(ANTHROPIC_CLAUDE_ROUTE_PREFIX)) { + return trimmed.slice(ANTHROPIC_CLAUDE_ROUTE_PREFIX.length); + } + return trimmed; +} + +function normalizeDesktopRouteSuffix(value: string) { + const trimmed = value.trim(); + const lower = trimmed.toLowerCase(); + if (lower.startsWith(CLAUDE_ROUTE_PREFIX)) { + return trimmed.slice(CLAUDE_ROUTE_PREFIX.length); + } + if (lower.startsWith(ANTHROPIC_CLAUDE_ROUTE_PREFIX)) { + return trimmed.slice(ANTHROPIC_CLAUDE_ROUTE_PREFIX.length); + } + return trimmed; +} + +function desktopRouteIdFromInput(value: string) { + const suffix = normalizeDesktopRouteSuffix(value).replace(/[/\\\s]+/g, "-"); + if (!suffix) return ""; + return `${CLAUDE_ROUTE_PREFIX}${suffix}`; +} + +function desktopRouteIdFromModel(model: string) { + const suffix = model + .trim() + .replace(/^models\//i, "") + .replace(/[/\\\s]+/g, "-"); + return desktopRouteIdFromInput(suffix); +} + function initialRouteRows( routes: Record | undefined, ): RouteRow[] { return Object.entries(routes ?? {}).map(([route, value]) => ({ route, model: value.model ?? "", - displayName: value.displayName ?? "", supports1m: value.supports1m ?? false, })); } @@ -135,8 +174,10 @@ function initialRouteRows( function isClaudeSafeRoute(route: string) { const normalized = route.trim().toLowerCase(); return ( - normalized.startsWith("claude-") || - normalized.startsWith("anthropic/claude-") + (normalized.startsWith(CLAUDE_ROUTE_PREFIX) && + normalized.length > CLAUDE_ROUTE_PREFIX.length) || + (normalized.startsWith(ANTHROPIC_CLAUDE_ROUTE_PREFIX) && + normalized.length > ANTHROPIC_CLAUDE_ROUTE_PREFIX.length) ); } @@ -147,7 +188,6 @@ function defaultRouteRows( return defaults.map((route, index) => ({ route: route.routeId, model: index === 0 ? defaultModel : "", - displayName: route.displayName, supports1m: route.supports1m, })); } @@ -159,7 +199,6 @@ function nextRouteRow(current: RouteRow[], defaults: RouteRow[]): RouteRow { ) ?? { route: "", model: "", - displayName: "", supports1m: true, } ); @@ -315,7 +354,6 @@ export function ClaudeDesktopProviderForm({ preset.modelRoutes.map((r) => ({ route: r.routeId, model: r.upstreamModel, - displayName: r.displayName, supports1m: r.supports1m, })), ); @@ -441,24 +479,22 @@ export function ClaudeDesktopProviderForm({ } const routeEntries = routes - .map((route, index) => ({ + .map((route) => ({ ...route, - route: - route.route.trim() || - (mode === "proxy" - ? `claude-${route.model.trim().replace(/[/\\]/g, "-") || `model-${index + 1}`}` - : ""), + route: route.route.trim(), model: route.model.trim(), - displayName: route.displayName.trim(), })) .filter((route) => route.route || route.model); if (mode === "proxy") { - const missing = routeEntries.find((route) => !route.model); - if (missing) { + const invalid = routeEntries.find( + (route) => + !route.route || !route.model || !isClaudeSafeRoute(route.route), + ); + if (invalid) { toast.error( t("claudeDesktop.routeInvalid", { - defaultValue: "请填写上游模型名", + defaultValue: "请填写 Desktop 显示模型和实际请求模型", }), ); return; @@ -466,7 +502,7 @@ export function ClaudeDesktopProviderForm({ if (routeEntries.length === 0) { toast.error( t("claudeDesktop.routesRequired", { - defaultValue: "至少填写一个上游模型", + defaultValue: "至少填写一个模型映射", }), ); return; @@ -504,7 +540,6 @@ export function ClaudeDesktopProviderForm({ >((acc, route) => { acc[route.route] = { model: route.model || route.route, - displayName: route.displayName || undefined, supports1m: route.supports1m || undefined, }; return acc; @@ -705,20 +740,20 @@ export function ClaudeDesktopProviderForm({

{t("claudeDesktop.routeMapHint", { defaultValue: - "填写供应商实际提供的模型名,显示名为在 Claude Desktop 模型列表中展示的名称。", + "左侧决定 Claude Desktop 模型菜单里的可见模型 ID,实际写入为 claude-*;右侧填写供应商实际请求的模型名。", })}

- {t("claudeDesktop.upstreamModelLabel", { - defaultValue: "实际请求模型", + {t("claudeDesktop.routeModelLabel", { + defaultValue: "Desktop 显示模型", })} - {t("claudeDesktop.displayNameLabel", { - defaultValue: "显示名", + {t("claudeDesktop.upstreamModelLabel", { + defaultValue: "实际请求模型", })} @@ -733,6 +768,21 @@ export function ClaudeDesktopProviderForm({ key={`${route.route}-${index}`} className="grid grid-cols-1 gap-2 md:grid-cols-[1fr_1fr_92px_36px]" > +
+ + {CLAUDE_ROUTE_PREFIX} + + + updateRoute(index, { + route: desktopRouteIdFromInput(event.target.value), + }) + } + placeholder="gpt-5.5" + className="rounded-l-none" + /> +
0 && ( updateRoute(index, { model: id })} + onSelect={(id) => + updateRoute(index, { + model: id, + route: route.route || desktopRouteIdFromModel(id), + }) + } /> )}
- - updateRoute(index, { displayName: event.target.value }) - } - placeholder="Sonnet" - />