Files
cc-switch/src/components/providers/forms/hooks/useSpeedTestEndpoints.ts
Jason 3568c98f57 fix: make Codex TOML base_url editing section-aware
Rewrite setCodexBaseUrl/extractCodexBaseUrl to understand TOML section
boundaries, ensuring base_url is written into the correct
[model_providers.<name>] section instead of being appended to file end.

- Add section-aware TOML helpers in providerConfigUtils.ts
- Extract shared update_codex_toml_field/remove_codex_toml_base_url_if
  in codex_config.rs, deduplicate proxy.rs TOML editing logic
- Replace scattered inline base_url regexes with extractCodexBaseUrl()
- Add comprehensive tests for both Rust and TypeScript implementations
2026-03-12 22:20:59 +08:00

160 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useMemo } from "react";
import type { AppId } from "@/lib/api";
import type { ProviderPreset } from "@/config/claudeProviderPresets";
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
import type { ProviderMeta, EndpointCandidate } from "@/types";
import { extractCodexBaseUrl } from "@/utils/providerConfigUtils";
type PresetEntry = {
id: string;
preset: ProviderPreset | CodexProviderPreset;
};
interface UseSpeedTestEndpointsProps {
appId: AppId;
selectedPresetId: string | null;
presetEntries: PresetEntry[];
baseUrl: string;
codexBaseUrl: string;
initialData?: {
settingsConfig?: Record<string, unknown>;
meta?: ProviderMeta;
};
}
/**
* 收集端点测速弹窗的初始端点列表
*
* 收集来源:
* 1. 当前选中的 Base URL
* 2. 编辑模式下的初始数据 URL
* 3. 预设中的 endpointCandidates
*
* 注意:已保存的自定义端点通过 getCustomEndpoints API 在 EndpointSpeedTest 组件中加载,
* 不在此处读取,避免重复导入。
*/
export function useSpeedTestEndpoints({
appId,
selectedPresetId,
presetEntries,
baseUrl,
codexBaseUrl,
initialData,
}: UseSpeedTestEndpointsProps) {
const claudeEndpoints = useMemo<EndpointCandidate[]>(() => {
// Reuse this branch for Claude and Gemini (non-Codex)
if (appId !== "claude" && appId !== "gemini") return [];
const map = new Map<string, EndpointCandidate>();
// 候选端点标记为 isCustom: false表示来自预设或配置
// 已保存的自定义端点会在 EndpointSpeedTest 组件中通过 API 加载
const add = (url?: string, isCustom = false) => {
if (!url) return;
const sanitized = url.trim().replace(/\/+$/, "");
if (!sanitized || map.has(sanitized)) return;
map.set(sanitized, { url: sanitized, isCustom });
};
// 1. 当前 Base URL
if (baseUrl) {
add(baseUrl);
}
// 2. 编辑模式:初始数据中的 URL
if (initialData && typeof initialData.settingsConfig === "object") {
const configEnv = initialData.settingsConfig as {
env?: { ANTHROPIC_BASE_URL?: string; GOOGLE_GEMINI_BASE_URL?: string };
};
const envUrls = [
configEnv.env?.ANTHROPIC_BASE_URL,
configEnv.env?.GOOGLE_GEMINI_BASE_URL,
];
envUrls.forEach((u) => {
if (typeof u === "string") add(u);
});
}
// 3. 预设中的 endpointCandidates
if (selectedPresetId && selectedPresetId !== "custom") {
const entry = presetEntries.find((item) => item.id === selectedPresetId);
if (entry) {
const preset = entry.preset as ProviderPreset & {
settingsConfig?: { env?: { GOOGLE_GEMINI_BASE_URL?: string } };
endpointCandidates?: string[];
};
// 添加预设自己的 baseUrl兼容 Claude/Gemini
const presetEnv = preset.settingsConfig as {
env?: {
ANTHROPIC_BASE_URL?: string;
GOOGLE_GEMINI_BASE_URL?: string;
};
};
const presetUrls = [
presetEnv?.env?.ANTHROPIC_BASE_URL,
presetEnv?.env?.GOOGLE_GEMINI_BASE_URL,
];
presetUrls.forEach((u) => add(u));
// 添加预设的候选端点
if (preset.endpointCandidates) {
preset.endpointCandidates.forEach((url) => add(url));
}
}
}
return Array.from(map.values());
}, [appId, baseUrl, initialData, selectedPresetId, presetEntries]);
const codexEndpoints = useMemo<EndpointCandidate[]>(() => {
if (appId !== "codex") return [];
const map = new Map<string, EndpointCandidate>();
// 候选端点标记为 isCustom: false表示来自预设或配置
// 已保存的自定义端点会在 EndpointSpeedTest 组件中通过 API 加载
const add = (url?: string, isCustom = false) => {
if (!url) return;
const sanitized = url.trim().replace(/\/+$/, "");
if (!sanitized || map.has(sanitized)) return;
map.set(sanitized, { url: sanitized, isCustom });
};
// 1. 当前 Codex Base URL
if (codexBaseUrl) {
add(codexBaseUrl);
}
// 2. 编辑模式:初始数据中的 URL
const initialCodexConfig = initialData?.settingsConfig as
| {
config?: string;
}
| undefined;
const configStr = initialCodexConfig?.config ?? "";
const extractedBaseUrl = extractCodexBaseUrl(configStr);
if (extractedBaseUrl) {
add(extractedBaseUrl);
}
// 3. 预设中的 endpointCandidates
if (selectedPresetId && selectedPresetId !== "custom") {
const entry = presetEntries.find((item) => item.id === selectedPresetId);
if (entry) {
const preset = entry.preset as CodexProviderPreset;
// 添加预设自己的 baseUrl
const presetConfig = preset.config || "";
const presetBaseUrl = extractCodexBaseUrl(presetConfig);
if (presetBaseUrl) {
add(presetBaseUrl);
}
// 添加预设的候选端点
if (preset.endpointCandidates) {
preset.endpointCandidates.forEach((url) => add(url));
}
}
}
return Array.from(map.values());
}, [appId, codexBaseUrl, initialData, selectedPresetId, presetEntries]);
return appId === "codex" ? codexEndpoints : claudeEndpoints;
}