-
+
{t("claudeDesktop.directModelListHint", {
defaultValue:
"仅当供应商的 /v1/models 不可用或没有返回 Claude Desktop 可识别的 claude-* 模型名时填写;这些模型名会原样发送给供应商。",
})}
-
+ {renderActionButtons(
+ () =>
+ setRoutes((current) => [
+ ...current,
+ {
+ route: "",
+ model: "",
+ displayName: "",
+ supports1m: false,
+ },
+ ]),
+ t("claudeDesktop.addModel", { defaultValue: "添加模型" }),
+ )}
-
-
{routes.length > 0 ? (
{routes.map((route, index) => (
@@ -698,14 +710,24 @@ export function ClaudeDesktopProviderForm({
key={`${route.route}-${index}`}
className="grid grid-cols-1 gap-2 md:grid-cols-[1fr_92px_36px]"
>
-
- updateRoute(index, { route: event.target.value })
- }
- list="claude-desktop-direct-models"
- placeholder="claude-deepseek-chat"
- />
+
+
+ updateRoute(index, { route: event.target.value })
+ }
+ placeholder="claude-deepseek-chat"
+ className="flex-1"
+ />
+ {fetchedModels.length > 0 && (
+
+ updateRoute(index, { route: id })
+ }
+ />
+ )}
+
diff --git a/src/components/providers/forms/OpenCodeFormFields.tsx b/src/components/providers/forms/OpenCodeFormFields.tsx
index 717b166d0..70ba3307a 100644
--- a/src/components/providers/forms/OpenCodeFormFields.tsx
+++ b/src/components/providers/forms/OpenCodeFormFields.tsx
@@ -11,23 +11,8 @@ import {
SelectValue,
} from "@/components/ui/select";
import { toast } from "sonner";
-import {
- ChevronDown,
- Download,
- Plus,
- Trash2,
- ChevronRight,
- Loader2,
-} from "lucide-react";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { ApiKeySection } from "./shared";
+import { Download, Plus, Trash2, ChevronRight, Loader2 } from "lucide-react";
+import { ApiKeySection, ModelDropdown } from "./shared";
import {
fetchModelsForConfig,
showFetchModelsError,
@@ -157,49 +142,6 @@ function ModelOptionKeyInput({
);
}
-/** Dropdown button to select from fetched models */
-function ModelDropdown({
- models,
- onSelect,
-}: {
- models: FetchedModel[];
- onSelect: (id: string) => void;
-}) {
- const grouped: Record
= {};
- for (const model of models) {
- const vendor = model.ownedBy || "Other";
- if (!grouped[vendor]) grouped[vendor] = [];
- grouped[vendor].push(model);
- }
- const vendors = Object.keys(grouped).sort();
-
- return (
-
-
-
-
-
- {vendors.map((vendor, vi) => (
-
- {vi > 0 && }
- {vendor}
- {grouped[vendor].map((m) => (
- onSelect(m.id)}>
- {m.id}
-
- ))}
-
- ))}
-
-
- );
-}
-
interface OpenCodeFormFieldsProps {
// NPM Package
npm: string;
diff --git a/src/components/providers/forms/shared/ModelDropdown.tsx b/src/components/providers/forms/shared/ModelDropdown.tsx
new file mode 100644
index 000000000..ec5d64b84
--- /dev/null
+++ b/src/components/providers/forms/shared/ModelDropdown.tsx
@@ -0,0 +1,53 @@
+import { ChevronDown } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import type { FetchedModel } from "@/lib/api/model-fetch";
+
+export function ModelDropdown({
+ models,
+ onSelect,
+}: {
+ models: FetchedModel[];
+ onSelect: (id: string) => void;
+}) {
+ const grouped: Record = {};
+ for (const model of models) {
+ const vendor = model.ownedBy || "Other";
+ if (!grouped[vendor]) grouped[vendor] = [];
+ grouped[vendor].push(model);
+ }
+ const vendors = Object.keys(grouped).sort();
+
+ return (
+
+
+
+
+
+ {vendors.map((vendor, vi) => (
+
+ {vi > 0 && }
+ {vendor}
+ {grouped[vendor].map((m) => (
+ onSelect(m.id)}>
+ {m.id}
+
+ ))}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/providers/forms/shared/index.ts b/src/components/providers/forms/shared/index.ts
index e5be59876..e1359c466 100644
--- a/src/components/providers/forms/shared/index.ts
+++ b/src/components/providers/forms/shared/index.ts
@@ -1,3 +1,4 @@
export { ApiKeySection } from "./ApiKeySection";
export { EndpointField } from "./EndpointField";
+export { ModelDropdown } from "./ModelDropdown";
export { ModelInputWithFetch } from "./ModelInputWithFetch";
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 396aac0a2..89b49109b 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -177,8 +177,6 @@
"mode": "Model handling",
"modeDirect": "Direct",
"modeProxy": "Model mapping",
- "gatewayBaseUrl": "Gateway Base URL",
- "bearerToken": "Bearer Token",
"modelMappingToggle": "Needs model mapping",
"modelMappingOffHint": "Use this when the provider already exposes and accepts claude-* / anthropic/claude-* model IDs through Anthropic Messages. Claude Desktop connects to the provider directly.",
"modelMappingOnHint": "Claude Desktop only sees claude-* route names. CC Switch local routing maps them to the real upstream models, so local routing must stay running.",
diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json
index e8e93b69e..32b38eee8 100644
--- a/src/i18n/locales/ja.json
+++ b/src/i18n/locales/ja.json
@@ -177,8 +177,6 @@
"mode": "モデル処理方式",
"modeDirect": "直結",
"modeProxy": "モデルマッピング",
- "gatewayBaseUrl": "Gateway Base URL",
- "bearerToken": "Bearer Token",
"modelMappingToggle": "モデルマッピングが必要",
"modelMappingOffHint": "プロバイダーが Anthropic Messages で claude-* / anthropic/claude-* のモデル ID を公開し、そのまま受け付ける場合に使います。Claude Desktop はプロバイダーへ直接接続します。",
"modelMappingOnHint": "Claude Desktop には claude-* のルート名だけを見せ、CC Switch のローカルルーティングが実際の上流モデルへマッピングします。ローカルルーティングを起動したままにしてください。",
diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json
index 638ff59e8..70af32e4e 100644
--- a/src/i18n/locales/zh.json
+++ b/src/i18n/locales/zh.json
@@ -177,8 +177,6 @@
"mode": "模型处理方式",
"modeDirect": "直连",
"modeProxy": "模型映射",
- "gatewayBaseUrl": "Gateway Base URL",
- "bearerToken": "Bearer Token",
"modelMappingToggle": "需要模型映射",
"modelMappingOffHint": "适合供应商已经暴露并接受 claude-* / anthropic/claude-* 模型名的 Anthropic Messages API;请求会由 Claude Desktop 直连供应商。",
"modelMappingOnHint": "Claude Desktop 只看到 claude-* 路由名,CC Switch 本地路由会映射到真实上游模型;需要保持本地路由运行。",