From 69f3c78bbf63c94d754e26c1bb9b2a076b6866a0 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Wed, 24 Dec 2025 14:42:46 +0800 Subject: [PATCH] feat(ui): add OpenRouter compatibility mode toggle Add UI toggle for OpenRouter providers to enable/disable compatibility mode which uses OpenAI Chat Completions format with SSE conversion. --- .../providers/forms/ClaudeFormFields.tsx | 31 +++++++++++ .../providers/forms/ProviderForm.tsx | 52 +++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/src/components/providers/forms/ClaudeFormFields.tsx b/src/components/providers/forms/ClaudeFormFields.tsx index 61a0f88e2..351f11b41 100644 --- a/src/components/providers/forms/ClaudeFormFields.tsx +++ b/src/components/providers/forms/ClaudeFormFields.tsx @@ -1,5 +1,6 @@ import { useTranslation } from "react-i18next"; import { FormLabel } from "@/components/ui/form"; +import { Switch } from "@/components/ui/switch"; import { Input } from "@/components/ui/input"; import EndpointSpeedTest from "./EndpointSpeedTest"; import { ApiKeySection, EndpointField } from "./shared"; @@ -55,6 +56,11 @@ interface ClaudeFormFieldsProps { // Speed Test Endpoints speedTestEndpoints: EndpointCandidate[]; + + // OpenRouter Compat + showOpenRouterCompatToggle: boolean; + openRouterCompatEnabled: boolean; + onOpenRouterCompatChange: (enabled: boolean) => void; } export function ClaudeFormFields({ @@ -85,6 +91,9 @@ export function ClaudeFormFields({ defaultOpusModel, onModelChange, speedTestEndpoints, + showOpenRouterCompatToggle, + openRouterCompatEnabled, + onOpenRouterCompatChange, }: ClaudeFormFieldsProps) { const { t } = useTranslation(); @@ -165,6 +174,28 @@ export function ClaudeFormFields({ /> )} + {showOpenRouterCompatToggle && ( +
+
+ + {t("providerForm.openrouterCompatMode", { + defaultValue: "OpenRouter 兼容模式", + })} + +

+ {t("providerForm.openrouterCompatModeHint", { + defaultValue: + "使用 OpenAI Chat Completions 接口并转换为 Anthropic SSE。", + })} +

+
+ +
+ )} + {/* 模型选择器 */} {shouldShowModelSelector && (
diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index 107917090..fadec46a7 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -162,6 +162,8 @@ export function ProviderForm({ mode: "onSubmit", }); + const settingsConfigValue = form.watch("settingsConfig"); + // 使用 API Key hook const { apiKey, @@ -200,6 +202,53 @@ export function ProviderForm({ onConfigChange: (config) => form.setValue("settingsConfig", config), }); + const isOpenRouterProvider = useMemo(() => { + if (appId !== "claude") return false; + const normalized = baseUrl.trim().toLowerCase(); + if (normalized.includes("openrouter.ai")) { + return true; + } + try { + const config = JSON.parse(settingsConfigValue || "{}"); + const envUrl = config?.env?.ANTHROPIC_BASE_URL; + return typeof envUrl === "string" && envUrl.includes("openrouter.ai"); + } catch { + return false; + } + }, [appId, baseUrl, settingsConfigValue]); + + const openRouterCompatEnabled = useMemo(() => { + if (!isOpenRouterProvider) return false; + try { + const config = JSON.parse(settingsConfigValue || "{}"); + const raw = config?.openrouter_compat_mode; + if (typeof raw === "boolean") return raw; + if (typeof raw === "number") return raw !== 0; + if (typeof raw === "string") { + const normalized = raw.trim().toLowerCase(); + return normalized === "true" || normalized === "1"; + } + } catch { + // ignore + } + return true; + }, [isOpenRouterProvider, settingsConfigValue]); + + const handleOpenRouterCompatChange = useCallback( + (enabled: boolean) => { + try { + const currentConfig = JSON.parse( + form.getValues("settingsConfig") || "{}", + ); + currentConfig.openrouter_compat_mode = enabled; + form.setValue("settingsConfig", JSON.stringify(currentConfig, null, 2)); + } catch { + // ignore + } + }, + [form], + ); + // 使用 Codex 配置 hook (仅 Codex 模式) const { codexAuth, @@ -796,6 +845,9 @@ export function ProviderForm({ defaultOpusModel={defaultOpusModel} onModelChange={handleModelChange} speedTestEndpoints={speedTestEndpoints} + showOpenRouterCompatToggle={isOpenRouterProvider} + openRouterCompatEnabled={openRouterCompatEnabled} + onOpenRouterCompatChange={handleOpenRouterCompatChange} /> )}