refactor(claude-desktop): simplify model mapping UX

- Remove "Import from Claude" button from main provider list (keep in empty state)
- Remove "Desktop model" column from proxy mode mapping table; route names are now auto-generated
- Rename "upstream model" label to "requested model" and equalize column widths
- Rewrite model mapping hint and toggle description for end-user clarity
- Update validation messages and remove dead i18n keys (routeModelLabel)
This commit is contained in:
Jason
2026-05-08 16:26:40 +08:00
parent 2deee1097b
commit 21b9eb0430
5 changed files with 35 additions and 65 deletions
+1 -16
View File
@@ -13,7 +13,7 @@ import {
type CSSProperties,
} from "react";
import { AnimatePresence, motion } from "framer-motion";
import { AlertTriangle, Download, Search, X } from "lucide-react";
import { AlertTriangle, Search, X } from "lucide-react";
import { useTranslation } from "react-i18next";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
@@ -470,21 +470,6 @@ export function ProviderList({
return (
<div className="mt-4 space-y-4">
{appId === "claude-desktop" && (
<div className="flex flex-wrap justify-end gap-2">
<Button
variant="outline"
size="sm"
onClick={() => importMutation.mutate()}
disabled={importMutation.isPending}
>
<Download className="mr-2 h-4 w-4" />
{t("provider.importFromClaude", {
defaultValue: "从 Claude 导入兼容供应商",
})}
</Button>
</div>
)}
{claudeDesktopStatusMessages.length > 0 && (
<div className="rounded-lg border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-900 dark:text-amber-200">
<div className="flex items-center gap-2 font-medium">
@@ -314,24 +314,24 @@ export function ClaudeDesktopProviderForm({
}
const routeEntries = routes
.map((route) => ({
.map((route, index) => ({
...route,
route: route.route.trim(),
route:
route.route.trim() ||
(mode === "proxy"
? `claude-${route.model.trim().replace(/[/\\]/g, "-") || `model-${index + 1}`}`
: ""),
model: route.model.trim(),
displayName: route.displayName.trim(),
}))
.filter((route) => route.route || route.model);
if (mode === "proxy") {
const invalid = routeEntries.find(
(route) =>
!route.route || !route.model || !isClaudeSafeRoute(route.route),
);
if (invalid) {
const missing = routeEntries.find((route) => !route.model);
if (missing) {
toast.error(
t("claudeDesktop.routeInvalid", {
defaultValue:
"模型映射必须填写 claude-* / anthropic/claude-* 路由名和上游模型名",
defaultValue: "请填写上游模型名",
}),
);
return;
@@ -339,7 +339,7 @@ export function ClaudeDesktopProviderForm({
if (routeEntries.length === 0) {
toast.error(
t("claudeDesktop.routesRequired", {
defaultValue: "需要模型映射时至少填写一个模型路由",
defaultValue: "至少填写一个上游模型",
}),
);
return;
@@ -479,7 +479,7 @@ export function ClaudeDesktopProviderForm({
{needsModelMapping
? t("claudeDesktop.modelMappingOnHint", {
defaultValue:
"Claude Desktop 只看到 claude-* 路由名,CC Switch 本地路由会映射到真实上游模型;需要保持本地路由运行。",
"Claude Desktop 目前对模型 ID 进行了限制,如果您的供应商提供的模型不是 Claude 系列模型,则需要打开本开关,并在使用过程中保持本地路由开启。",
})
: t("claudeDesktop.modelMappingOffHint", {
defaultValue:
@@ -552,26 +552,21 @@ export function ClaudeDesktopProviderForm({
...current,
nextRouteRow(current, defaultProxyRouteRows),
]),
t("claudeDesktop.addRoute", { defaultValue: "添加路由" }),
t("claudeDesktop.addRoute", { defaultValue: "添加模型" }),
)}
</div>
<p className="text-xs leading-relaxed text-muted-foreground">
{t("claudeDesktop.routeMapHint", {
defaultValue:
"左侧是 Claude Desktop 看到的 claude-* 模型名,右侧是真实发送给供应商的上游模型名。",
"填写供应商实际提供的模型名,显示名为在 Claude Desktop 模型列表中展示的名称。",
})}
</p>
</div>
<div className="hidden grid-cols-[1fr_1fr_140px_92px_36px] gap-2 px-1 text-xs font-medium text-muted-foreground md:grid">
<span>
{t("claudeDesktop.routeModelLabel", {
defaultValue: "Desktop 模型",
})}
</span>
<div className="hidden grid-cols-[1fr_1fr_92px_36px] gap-2 px-1 text-xs font-medium text-muted-foreground md:grid">
<span>
{t("claudeDesktop.upstreamModelLabel", {
defaultValue: "上游模型",
defaultValue: "实际请求模型",
})}
</span>
<span>
@@ -589,15 +584,8 @@ export function ClaudeDesktopProviderForm({
{routes.map((route, index) => (
<div
key={`${route.route}-${index}`}
className="grid grid-cols-1 gap-2 md:grid-cols-[1fr_1fr_140px_92px_36px]"
className="grid grid-cols-1 gap-2 md:grid-cols-[1fr_1fr_92px_36px]"
>
<Input
value={route.route}
onChange={(event) =>
updateRoute(index, { route: event.target.value })
}
placeholder="claude-sonnet-4-6"
/>
<div className="flex gap-1">
<Input
value={route.model}