refactor(proxy): unify URL building logic in backend

Move URL preview and proxy requirement checks from frontend to a centralized
Rust module. This ensures consistency between the endpoint field preview and
actual proxy behavior.
This commit is contained in:
YoVinchen
2026-02-05 14:30:15 +08:00
parent 7f6ce72a88
commit b12d12790b
12 changed files with 640 additions and 181 deletions
+81 -1
View File
@@ -10,19 +10,99 @@ import { Switch } from "@/components/ui/switch";
import { useProxyStatus } from "@/hooks/useProxyStatus";
import { cn } from "@/lib/utils";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import type { AppId } from "@/lib/api";
import { proxyApi } from "@/lib/api/proxy";
import type { Provider } from "@/types";
interface ProxyToggleProps {
className?: string;
activeApp: AppId;
currentProvider?: Provider | null;
}
export function ProxyToggle({ className, activeApp }: ProxyToggleProps) {
/**
* 从 provider 配置中提取 base URL
*/
function extractBaseUrl(provider: Provider, appId: AppId): string | null {
try {
const config = provider.settingsConfig;
if (!config) return null;
if (appId === "claude") {
const envUrl = config?.env?.ANTHROPIC_BASE_URL;
return typeof envUrl === "string" ? envUrl.trim() : null;
}
if (appId === "codex") {
const tomlConfig = config?.config;
if (typeof tomlConfig === "string") {
const match = tomlConfig.match(/base_url\s*=\s*(['"])([^'"]+)\1/);
return match?.[2]?.trim() || null;
}
const baseUrl = config?.base_url;
return typeof baseUrl === "string" ? baseUrl.trim() : null;
}
if (appId === "gemini") {
const envUrl = config?.env?.GOOGLE_GEMINI_BASE_URL;
if (typeof envUrl === "string") return envUrl.trim();
const baseUrl = config?.GEMINI_API_BASE || config?.base_url;
return typeof baseUrl === "string" ? baseUrl.trim() : null;
}
return null;
} catch {
return null;
}
}
export function ProxyToggle({
className,
activeApp,
currentProvider,
}: ProxyToggleProps) {
const { t } = useTranslation();
const { isRunning, takeoverStatus, setTakeoverForApp, isPending, status } =
useProxyStatus();
const handleToggle = async (checked: boolean) => {
// 关闭代理时,检查当前供应商是否是全链接配置
if (
!checked &&
currentProvider &&
currentProvider.category !== "official"
) {
const baseUrl = extractBaseUrl(currentProvider, activeApp);
const apiFormat = currentProvider.meta?.apiFormat;
if (baseUrl) {
try {
const proxyRequirement = await proxyApi.checkProxyRequirement(
activeApp,
baseUrl,
apiFormat,
);
if (proxyRequirement) {
// 显示警告但仍允许关闭
toast.warning(
t("notifications.fullUrlWarningOnDisable", {
defaultValue:
"当前供应商配置了完整 API 路径或特殊格式,关闭代理后可能无法正常工作。建议更换为基础地址配置或保持代理开启。",
}),
{
duration: 6000,
closeButton: true,
},
);
}
} catch (error) {
console.error("Failed to check proxy requirement:", error);
}
}
}
try {
await setTakeoverForApp({ appType: activeApp, enabled: checked });
} catch (error) {