mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-29 09:37:37 +08:00
Align Claude Desktop model mapping with Claude Code three-role tiers
Claude Desktop's 3P validation only accepts claude-{sonnet,opus,haiku}-*
role IDs, so providers must map every tier. Bring the Desktop mapping flow
in line with Claude Code and fix the fallout that broke sub-agent Haiku calls.
- Proxy form: replace the dynamic route list with fixed Sonnet/Opus/Haiku
tiers; blank tiers backfill from the first filled tier (Sonnet first) on
submit and inherit its supports1m flag
- Backend: add a role-keyword fallback in map_proxy_request_model so dated
official names (e.g. claude-haiku-4-5-20251001) resolve to the right tier,
guarded by is_claude_safe_model_id so [1m]-suffixed IDs stay rejected
- Tighten is_claude_safe_model_id / isClaudeSafeRoute to reject degenerate
role IDs like "claude-sonnet-"
- Fix the seed-effect race where normalizing empty routes to three blank
tiers blocked the default-route backfill
- Sync switch hints, placeholders, and the zh/en/ja/zh-TW locales to the
three-role-ID rule
- Update zh/en/ja user manual (2.1, 2.6), calling out legacy Claude IDs
(claude-3-5-sonnet-...) as a rejected example
Tests: 282 frontend + 34 backend claude_desktop; typecheck, clippy, fmt clean.
This commit is contained in:
@@ -118,44 +118,6 @@ const ANTHROPIC_CLAUDE_ROUTE_PREFIX = "anthropic/claude-";
|
||||
const LEGACY_ONE_M_MARKER = "[1m]";
|
||||
const ROLE_ROUTE_IDS = CLAUDE_DESKTOP_ROLE_ROUTE_IDS;
|
||||
const ROLE_ORDER: RouteRole[] = ["sonnet", "opus", "haiku"];
|
||||
const NON_ANTHROPIC_ROUTE_MARKERS = [
|
||||
"ark-code",
|
||||
"astron",
|
||||
"command-r",
|
||||
"deepseek",
|
||||
"doubao",
|
||||
"gemini",
|
||||
"gemma",
|
||||
"glm",
|
||||
"gpt",
|
||||
"grok",
|
||||
"hermes",
|
||||
"hy3",
|
||||
"kimi",
|
||||
"lfm",
|
||||
"llama",
|
||||
"longcat",
|
||||
"mimo",
|
||||
"minimax",
|
||||
"mistral",
|
||||
"mixtral",
|
||||
"moonshot",
|
||||
"nemotron",
|
||||
"openai",
|
||||
"qianfan",
|
||||
"qwen",
|
||||
"stepfun",
|
||||
"seed-",
|
||||
"hunyuan",
|
||||
"nova-",
|
||||
"ernie",
|
||||
"codex",
|
||||
"abab",
|
||||
"jamba",
|
||||
"arctic",
|
||||
"solar",
|
||||
"mercury",
|
||||
];
|
||||
|
||||
function envString(
|
||||
settingsConfig: Record<string, unknown> | undefined,
|
||||
@@ -231,21 +193,37 @@ function initialRouteRows(
|
||||
});
|
||||
}
|
||||
|
||||
// Proxy 模式对齐 Claude Code:固定 Sonnet / Opus / Haiku 三档。
|
||||
// 把任意来源的 route 行按角色归类到固定三槽(缺档留空),保证 UI 永远三行、
|
||||
// 用户不会漏配 Haiku 导致子 agent 找不到模型。
|
||||
function normalizeProxyRows(rows: RouteRow[]): RouteRow[] {
|
||||
return ROLE_ORDER.map((role) => {
|
||||
const match = rows.find(
|
||||
(row) => row.route.trim() && routeRoleFromId(row.route) === role,
|
||||
);
|
||||
return createRouteRow({
|
||||
route: ROLE_ROUTE_IDS[role],
|
||||
model: match?.model ?? "",
|
||||
labelOverride: match?.labelOverride ?? "",
|
||||
supports1m: match?.supports1m ?? false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isClaudeSafeRoute(route: string) {
|
||||
const normalized = route.trim().toLowerCase();
|
||||
if (normalized.includes(LEGACY_ONE_M_MARKER)) return false;
|
||||
const hasAllowedShape =
|
||||
[CLAUDE_ROUTE_PREFIX, ANTHROPIC_CLAUDE_ROUTE_PREFIX].some(
|
||||
(prefix) =>
|
||||
normalized.startsWith(prefix) && normalized.length > prefix.length,
|
||||
) ||
|
||||
["sonnet", "opus", "haiku"].includes(normalized) ||
|
||||
normalized.startsWith("sonnet-") ||
|
||||
normalized.startsWith("opus-") ||
|
||||
normalized.startsWith("haiku-");
|
||||
return (
|
||||
hasAllowedShape &&
|
||||
!NON_ANTHROPIC_ROUTE_MARKERS.some((marker) => normalized.includes(marker))
|
||||
const routeTail = normalized.startsWith(ANTHROPIC_CLAUDE_ROUTE_PREFIX)
|
||||
? normalized.slice(ANTHROPIC_CLAUDE_ROUTE_PREFIX.length)
|
||||
: normalized.startsWith(CLAUDE_ROUTE_PREFIX)
|
||||
? normalized.slice(CLAUDE_ROUTE_PREFIX.length)
|
||||
: "";
|
||||
|
||||
// 角色前缀后必须还有实际模型标识,拒绝 claude-sonnet- 这类退化值
|
||||
// (否则会写入 profile 并触发 Claude Desktop fail-all 拒收整组)。
|
||||
return ["sonnet-", "opus-", "haiku-"].some(
|
||||
(prefix) =>
|
||||
routeTail.startsWith(prefix) && routeTail.length > prefix.length,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -263,30 +241,6 @@ function defaultRouteRows(
|
||||
);
|
||||
}
|
||||
|
||||
function nextRouteRow(current: RouteRow[], defaults: RouteRow[]): RouteRow {
|
||||
const defaultRow =
|
||||
defaults.find(
|
||||
(route) => !current.some((existing) => existing.route === route.route),
|
||||
) ?? null;
|
||||
|
||||
if (defaultRow) {
|
||||
return createRouteRow({
|
||||
route: defaultRow.route,
|
||||
model: defaultRow.model,
|
||||
labelOverride: defaultRow.labelOverride,
|
||||
supports1m: defaultRow.supports1m,
|
||||
});
|
||||
}
|
||||
|
||||
const usedRoutes = new Set(current.map((route) => route.route));
|
||||
return createRouteRow({
|
||||
route: routeIdForRole("sonnet", usedRoutes),
|
||||
model: "",
|
||||
labelOverride: "",
|
||||
supports1m: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function ClaudeDesktopProviderForm({
|
||||
submitLabel,
|
||||
onSubmit,
|
||||
@@ -334,9 +288,15 @@ export function ClaudeDesktopProviderForm({
|
||||
providerType?: string;
|
||||
requiresOAuth?: boolean;
|
||||
} | null>(null);
|
||||
const [routes, setRoutes] = useState<RouteRow[]>(() =>
|
||||
initialRouteRows(initialData?.meta?.claudeDesktopModelRoutes),
|
||||
);
|
||||
const [routes, setRoutes] = useState<RouteRow[]>(() => {
|
||||
const rows = initialRouteRows(initialData?.meta?.claudeDesktopModelRoutes);
|
||||
// proxy 模式归一化成固定三档;但初始无任何 route 时保持空数组,交给 seed
|
||||
// effect 用默认路由回填(默认 1M 声明、ANTHROPIC_MODEL 预填),避免过早
|
||||
// normalize 成空三档把 routes.length 撑到 3、永久挡住 seed。
|
||||
return initialMode === "proxy" && rows.length > 0
|
||||
? normalizeProxyRows(rows)
|
||||
: rows;
|
||||
});
|
||||
const didSeedDefaultRoutes = useRef(
|
||||
Object.keys(initialData?.meta?.claudeDesktopModelRoutes ?? {}).length > 0,
|
||||
);
|
||||
@@ -435,13 +395,15 @@ export function ClaudeDesktopProviderForm({
|
||||
setMode(preset.mode);
|
||||
if (preset.mode === "proxy" && preset.modelRoutes) {
|
||||
setRoutes(
|
||||
preset.modelRoutes.map((r) =>
|
||||
createRouteRow({
|
||||
route: r.routeId,
|
||||
model: r.upstreamModel,
|
||||
labelOverride: r.labelOverride ?? "",
|
||||
supports1m: r.supports1m,
|
||||
}),
|
||||
normalizeProxyRows(
|
||||
preset.modelRoutes.map((r) =>
|
||||
createRouteRow({
|
||||
route: r.routeId,
|
||||
model: r.upstreamModel,
|
||||
labelOverride: r.labelOverride ?? "",
|
||||
supports1m: r.supports1m,
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -485,28 +447,26 @@ export function ClaudeDesktopProviderForm({
|
||||
);
|
||||
};
|
||||
|
||||
const updateRouteRole = (index: number, role: RouteRole) => {
|
||||
setRoutes((current) => {
|
||||
const usedRoutes = new Set(
|
||||
current
|
||||
.filter((_, i) => i !== index)
|
||||
.map((row) => row.route)
|
||||
.filter(Boolean),
|
||||
);
|
||||
const route = routeIdForRole(role, usedRoutes);
|
||||
return current.map((row, i) => (i === index ? { ...row, route } : row));
|
||||
});
|
||||
};
|
||||
|
||||
const handleModelMappingChange = (checked: boolean) => {
|
||||
setMode(checked ? "proxy" : "direct");
|
||||
if (checked) {
|
||||
// 切到 proxy:归一化成固定 Sonnet / Opus / Haiku 三档;
|
||||
// 若当前无路由则以后端默认路由作为来源(保留 Sonnet 默认模型)。
|
||||
setRoutes((current) => {
|
||||
if (current.length > 0 || defaultProxyRouteRows.length === 0) {
|
||||
// 默认路由(默认 1M 声明、ANTHROPIC_MODEL 预填)异步加载完成前,若当前
|
||||
// 无路由则保持空数组,交给 seed effect 在加载后回填;不要过早 normalize
|
||||
// 成空三档(会把 routes.length 撑到 3、永久挡住 seed)。
|
||||
if (current.length === 0 && defaultProxyRouteRows.length === 0) {
|
||||
return current;
|
||||
}
|
||||
didSeedDefaultRoutes.current = true;
|
||||
return defaultProxyRouteRows;
|
||||
const useDefaults =
|
||||
current.length === 0 && defaultProxyRouteRows.length > 0;
|
||||
if (useDefaults) {
|
||||
didSeedDefaultRoutes.current = true;
|
||||
}
|
||||
return normalizeProxyRows(
|
||||
useDefaults ? defaultProxyRouteRows : current,
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -522,7 +482,7 @@ export function ClaudeDesktopProviderForm({
|
||||
}
|
||||
|
||||
didSeedDefaultRoutes.current = true;
|
||||
setRoutes(defaultProxyRouteRows);
|
||||
setRoutes(normalizeProxyRows(defaultProxyRouteRows));
|
||||
}, [defaultProxyRouteRows, mode, routes.length]);
|
||||
|
||||
const handleFetchModels = async () => {
|
||||
@@ -614,19 +574,11 @@ export function ClaudeDesktopProviderForm({
|
||||
.filter((route) => route.route || route.model);
|
||||
|
||||
if (mode === "proxy") {
|
||||
const invalid = routeEntries.find(
|
||||
(route) =>
|
||||
!route.route || !route.model || !isClaudeSafeRoute(route.route),
|
||||
);
|
||||
if (invalid) {
|
||||
toast.error(
|
||||
t("claudeDesktop.routeInvalid", {
|
||||
defaultValue: "请填写 Desktop 显示模型和实际请求模型",
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (routeEntries.length === 0) {
|
||||
// 固定三档(Sonnet / Opus / Haiku),route_id 由 UI 生成、恒合法,
|
||||
// 因此只要求至少填一个实际请求模型;留空档继承第一个已填档(Sonnet 优先),
|
||||
// 对齐 Claude Code 的兜底,保证落库三档齐全、子 agent 不会找不到 Haiku。
|
||||
const primary = routeEntries.find((route) => route.model);
|
||||
if (!primary) {
|
||||
toast.error(
|
||||
t("claudeDesktop.routesRequired", {
|
||||
defaultValue: "至少填写一个模型映射",
|
||||
@@ -634,6 +586,19 @@ export function ClaudeDesktopProviderForm({
|
||||
);
|
||||
return;
|
||||
}
|
||||
for (const route of routeEntries) {
|
||||
if (!route.model) {
|
||||
route.model = primary.model;
|
||||
if (!route.labelOverride) {
|
||||
route.labelOverride = primary.labelOverride || primary.model;
|
||||
}
|
||||
// 回填的是同一个上游模型,1M 能力声明应与 primary 一致,
|
||||
// 避免同模型在不同档声明不同 1M(除非该档用户已显式勾选)。
|
||||
if (!route.supports1m) {
|
||||
route.supports1m = primary.supports1m;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const invalid = routeEntries.find(
|
||||
(route) => !route.route || !isClaudeSafeRoute(route.route),
|
||||
@@ -642,7 +607,7 @@ export function ClaudeDesktopProviderForm({
|
||||
toast.error(
|
||||
t("claudeDesktop.directModelInvalid", {
|
||||
defaultValue:
|
||||
"直连模型必须使用 claude-* / anthropic/claude-* 模型名",
|
||||
"直连模型必须使用 Claude Desktop 可识别的 Sonnet / Opus / Haiku 模型名",
|
||||
}),
|
||||
);
|
||||
return;
|
||||
@@ -668,7 +633,7 @@ export function ClaudeDesktopProviderForm({
|
||||
Record<string, ClaudeDesktopModelRoute>
|
||||
>((acc, route) => {
|
||||
acc[route.route] = {
|
||||
model: route.model || route.route,
|
||||
model: mode === "direct" ? route.route : route.model || route.route,
|
||||
labelOverride:
|
||||
route.labelOverride || (mode === "proxy" ? route.model : undefined),
|
||||
supports1m: route.supports1m || undefined,
|
||||
@@ -838,11 +803,11 @@ export function ClaudeDesktopProviderForm({
|
||||
{needsModelMapping
|
||||
? t("claudeDesktop.modelMappingOnHint", {
|
||||
defaultValue:
|
||||
"Claude Desktop 目前对模型 ID 进行了限制,如果您的供应商提供的模型不是 Claude 系列模型,则需要打开本开关,并在使用过程中保持本地路由开启。",
|
||||
"Claude Desktop 只接受 claude-sonnet-* / claude-opus-* / claude-haiku-* 三档角色 ID。开启后 CC Switch 会把这三档映射到供应商的实际模型,并在使用期间保持本地路由开启。",
|
||||
})
|
||||
: t("claudeDesktop.modelMappingOffHint", {
|
||||
defaultValue:
|
||||
"适合供应商已经暴露并接受 claude-* / anthropic/claude-* 模型名的 Anthropic Messages API;请求会由 Claude Desktop 直连供应商。",
|
||||
"仅当供应商直接接受 Claude Desktop 可识别的三档角色 ID(claude-sonnet-* / claude-opus-* / claude-haiku-*)时才适用直连;其他模型名(含 claude-3-5-sonnet-… 等旧式 ID)请打开此开关走映射。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
@@ -905,26 +870,35 @@ export function ClaudeDesktopProviderForm({
|
||||
defaultValue: "模型映射",
|
||||
})}
|
||||
</Label>
|
||||
{renderActionButtons(
|
||||
() =>
|
||||
setRoutes((current) => [
|
||||
...current,
|
||||
nextRouteRow(current, defaultProxyRouteRows),
|
||||
]),
|
||||
t("claudeDesktop.addRoute", {
|
||||
defaultValue: "添加模型",
|
||||
}),
|
||||
{!usesManagedOAuth && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleFetchModels}
|
||||
disabled={isFetchingModels}
|
||||
className="h-7 gap-1"
|
||||
>
|
||||
{isFetchingModels ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
) : (
|
||||
<Download className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{t("providerForm.fetchModels", {
|
||||
defaultValue: "获取模型",
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs leading-relaxed text-muted-foreground">
|
||||
{t("claudeDesktop.routeMapHint", {
|
||||
defaultValue:
|
||||
"选择模型角色后,CC Switch 会自动生成 Claude Desktop 兼容路由;菜单显示名可以写 DeepSeek、Kimi 等品牌模型,实际请求模型按右侧填写内容发送。",
|
||||
"为 Sonnet、Opus、Haiku 三档分别填写实际请求模型;菜单显示名可写 DeepSeek、Kimi 等品牌名。留空的档会自动沿用 Sonnet(或第一个已填档)的模型,确保子 agent 调用的 Haiku 始终可用。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="hidden grid-cols-[140px_1fr_1fr_116px_36px] gap-2 px-1 text-xs font-medium text-muted-foreground md:grid">
|
||||
<div className="hidden grid-cols-[140px_1fr_1fr_116px] gap-2 px-1 text-xs font-medium text-muted-foreground md:grid">
|
||||
<span>
|
||||
{t("claudeDesktop.routeModelLabel", {
|
||||
defaultValue: "模型角色",
|
||||
@@ -945,101 +919,75 @@ export function ClaudeDesktopProviderForm({
|
||||
defaultValue: "声明支持 1M",
|
||||
})}
|
||||
</span>
|
||||
<span />
|
||||
</div>
|
||||
{routes.map((route, index) => (
|
||||
<div
|
||||
key={route.rowId}
|
||||
className="grid grid-cols-1 gap-2 md:grid-cols-[140px_1fr_1fr_116px_36px]"
|
||||
>
|
||||
<Select
|
||||
value={routeRoleFromId(route.route)}
|
||||
onValueChange={(value) =>
|
||||
updateRouteRole(index, value as RouteRole)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="h-9">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="sonnet">
|
||||
{t("claudeDesktop.routeRoleSonnet", {
|
||||
defaultValue: "Sonnet",
|
||||
})}
|
||||
</SelectItem>
|
||||
<SelectItem value="opus">
|
||||
{t("claudeDesktop.routeRoleOpus", {
|
||||
defaultValue: "Opus",
|
||||
})}
|
||||
</SelectItem>
|
||||
<SelectItem value="haiku">
|
||||
{t("claudeDesktop.routeRoleHaiku", {
|
||||
defaultValue: "Haiku",
|
||||
})}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
value={route.labelOverride}
|
||||
onChange={(event) =>
|
||||
updateRoute(index, {
|
||||
labelOverride: event.target.value,
|
||||
{routes.map((route, index) => {
|
||||
const role = routeRoleFromId(route.route);
|
||||
const roleLabel =
|
||||
role === "opus"
|
||||
? t("claudeDesktop.routeRoleOpus", {
|
||||
defaultValue: "Opus",
|
||||
})
|
||||
}
|
||||
placeholder="DeepSeek V4 Pro"
|
||||
/>
|
||||
<div className="flex gap-1">
|
||||
: role === "haiku"
|
||||
? t("claudeDesktop.routeRoleHaiku", {
|
||||
defaultValue: "Haiku",
|
||||
})
|
||||
: t("claudeDesktop.routeRoleSonnet", {
|
||||
defaultValue: "Sonnet",
|
||||
});
|
||||
return (
|
||||
<div
|
||||
key={route.rowId}
|
||||
className="grid grid-cols-1 gap-2 md:grid-cols-[140px_1fr_1fr_116px]"
|
||||
>
|
||||
<div className="flex h-9 items-center rounded-md border border-input bg-muted px-3 text-sm font-medium text-muted-foreground">
|
||||
{roleLabel}
|
||||
</div>
|
||||
<Input
|
||||
value={route.model}
|
||||
value={route.labelOverride}
|
||||
onChange={(event) =>
|
||||
updateRoute(index, { model: event.target.value })
|
||||
updateRoute(index, {
|
||||
labelOverride: event.target.value,
|
||||
})
|
||||
}
|
||||
placeholder="kimi-k2 / deepseek-chat"
|
||||
className="flex-1"
|
||||
placeholder="DeepSeek V4 Pro"
|
||||
/>
|
||||
{fetchedModels.length > 0 && (
|
||||
<ModelDropdown
|
||||
models={fetchedModels}
|
||||
onSelect={(id) =>
|
||||
<div className="flex gap-1">
|
||||
<Input
|
||||
value={route.model}
|
||||
onChange={(event) =>
|
||||
updateRoute(index, { model: event.target.value })
|
||||
}
|
||||
placeholder="kimi-k2 / deepseek-chat"
|
||||
className="flex-1"
|
||||
/>
|
||||
{fetchedModels.length > 0 && (
|
||||
<ModelDropdown
|
||||
models={fetchedModels}
|
||||
onSelect={(id) =>
|
||||
updateRoute(index, {
|
||||
model: id,
|
||||
labelOverride: route.labelOverride || id,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<label className="flex h-9 items-center gap-2 text-sm text-muted-foreground">
|
||||
<Checkbox
|
||||
checked={route.supports1m}
|
||||
onCheckedChange={(checked) =>
|
||||
updateRoute(index, {
|
||||
model: id,
|
||||
labelOverride: route.labelOverride || id,
|
||||
route:
|
||||
route.route ||
|
||||
routeIdForRole(
|
||||
"sonnet",
|
||||
new Set(routes.map((row) => row.route)),
|
||||
),
|
||||
supports1m: checked === true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{t("claudeDesktop.supports1mShort", {
|
||||
defaultValue: "1M",
|
||||
})}
|
||||
</label>
|
||||
</div>
|
||||
<label className="flex h-9 items-center gap-2 text-sm text-muted-foreground">
|
||||
<Checkbox
|
||||
checked={route.supports1m}
|
||||
onCheckedChange={(checked) =>
|
||||
updateRoute(index, { supports1m: checked === true })
|
||||
}
|
||||
/>
|
||||
{t("claudeDesktop.supports1mShort", {
|
||||
defaultValue: "1M",
|
||||
})}
|
||||
</label>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() =>
|
||||
setRoutes((current) =>
|
||||
current.filter((_, i) => i !== index),
|
||||
)
|
||||
}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1081,7 +1029,7 @@ export function ClaudeDesktopProviderForm({
|
||||
<p className="flex-1 text-xs leading-relaxed text-muted-foreground">
|
||||
{t("claudeDesktop.directModelListHint", {
|
||||
defaultValue:
|
||||
"仅当供应商的 /v1/models 不可用或没有返回 Claude Desktop 可识别的 claude-* 模型名时填写;这些模型名会原样发送给供应商。",
|
||||
"仅当供应商的 /v1/models 不可用或没有返回 Claude Desktop 可识别的 Sonnet / Opus / Haiku 模型名时填写;勾选 1M 会向 Claude Desktop 声明支持 1M 上下文。",
|
||||
})}
|
||||
</p>
|
||||
{renderActionButtons(
|
||||
@@ -1116,7 +1064,7 @@ export function ClaudeDesktopProviderForm({
|
||||
route: event.target.value,
|
||||
})
|
||||
}
|
||||
placeholder="claude-deepseek-chat"
|
||||
placeholder="claude-sonnet-4-6"
|
||||
className="flex-1"
|
||||
/>
|
||||
{fetchedModels.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user