2025-10-16 22:41:36 +08:00
|
|
|
|
import { useMemo } from "react";
|
|
|
|
|
|
import type { AppType } from "@/lib/api";
|
|
|
|
|
|
import type { ProviderPreset } from "@/config/providerPresets";
|
|
|
|
|
|
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
|
2025-10-24 09:24:03 +08:00
|
|
|
|
import type { ProviderMeta, EndpointCandidate } from "@/types";
|
2025-10-16 22:41:36 +08:00
|
|
|
|
|
|
|
|
|
|
type PresetEntry = {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
preset: ProviderPreset | CodexProviderPreset;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
interface UseSpeedTestEndpointsProps {
|
|
|
|
|
|
appType: AppType;
|
|
|
|
|
|
selectedPresetId: string | null;
|
|
|
|
|
|
presetEntries: PresetEntry[];
|
|
|
|
|
|
baseUrl: string;
|
|
|
|
|
|
codexBaseUrl: string;
|
|
|
|
|
|
initialData?: {
|
|
|
|
|
|
settingsConfig?: Record<string, unknown>;
|
2025-10-24 09:24:03 +08:00
|
|
|
|
meta?: ProviderMeta;
|
2025-10-16 22:41:36 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 收集端点测速弹窗的初始端点列表
|
|
|
|
|
|
*
|
|
|
|
|
|
* 收集来源:
|
2025-10-24 09:24:03 +08:00
|
|
|
|
* 1. 编辑模式:从 meta.custom_endpoints 读取已保存的端点(优先)
|
|
|
|
|
|
* 2. 当前选中的 Base URL
|
|
|
|
|
|
* 3. 编辑模式下的初始数据 URL
|
|
|
|
|
|
* 4. 预设中的 endpointCandidates
|
2025-10-16 22:41:36 +08:00
|
|
|
|
*/
|
|
|
|
|
|
export function useSpeedTestEndpoints({
|
|
|
|
|
|
appType,
|
|
|
|
|
|
selectedPresetId,
|
|
|
|
|
|
presetEntries,
|
|
|
|
|
|
baseUrl,
|
|
|
|
|
|
codexBaseUrl,
|
|
|
|
|
|
initialData,
|
|
|
|
|
|
}: UseSpeedTestEndpointsProps) {
|
|
|
|
|
|
const claudeEndpoints = useMemo<EndpointCandidate[]>(() => {
|
|
|
|
|
|
if (appType !== "claude") return [];
|
|
|
|
|
|
|
|
|
|
|
|
const map = new Map<string, EndpointCandidate>();
|
2025-10-24 09:24:03 +08:00
|
|
|
|
// 所有端点都标记为 isCustom: true,给用户完全的管理自由
|
2025-10-16 22:41:36 +08:00
|
|
|
|
const add = (url?: string) => {
|
|
|
|
|
|
if (!url) return;
|
|
|
|
|
|
const sanitized = url.trim().replace(/\/+$/, "");
|
|
|
|
|
|
if (!sanitized || map.has(sanitized)) return;
|
2025-10-24 09:24:03 +08:00
|
|
|
|
map.set(sanitized, { url: sanitized, isCustom: true });
|
2025-10-16 22:41:36 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-24 09:24:03 +08:00
|
|
|
|
// 1. 编辑模式:从 meta.custom_endpoints 读取已保存的端点(优先)
|
|
|
|
|
|
if (initialData?.meta?.custom_endpoints) {
|
|
|
|
|
|
const customEndpoints = initialData.meta.custom_endpoints;
|
|
|
|
|
|
for (const url of Object.keys(customEndpoints)) {
|
|
|
|
|
|
add(url);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 当前 Base URL
|
2025-10-16 22:41:36 +08:00
|
|
|
|
if (baseUrl) {
|
|
|
|
|
|
add(baseUrl);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-24 09:24:03 +08:00
|
|
|
|
// 3. 编辑模式:初始数据中的 URL
|
2025-10-16 22:41:36 +08:00
|
|
|
|
if (initialData && typeof initialData.settingsConfig === "object") {
|
2025-10-24 09:24:03 +08:00
|
|
|
|
const configEnv = initialData.settingsConfig as {
|
|
|
|
|
|
env?: { ANTHROPIC_BASE_URL?: string };
|
|
|
|
|
|
};
|
|
|
|
|
|
const envUrl = configEnv.env?.ANTHROPIC_BASE_URL;
|
2025-10-16 22:41:36 +08:00
|
|
|
|
if (typeof envUrl === "string") {
|
|
|
|
|
|
add(envUrl);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-24 09:24:03 +08:00
|
|
|
|
// 4. 预设中的 endpointCandidates(也允许用户删除)
|
2025-10-16 22:41:36 +08:00
|
|
|
|
if (selectedPresetId && selectedPresetId !== "custom") {
|
|
|
|
|
|
const entry = presetEntries.find((item) => item.id === selectedPresetId);
|
|
|
|
|
|
if (entry) {
|
|
|
|
|
|
const preset = entry.preset as ProviderPreset;
|
|
|
|
|
|
// 添加预设自己的 baseUrl
|
2025-10-24 09:24:03 +08:00
|
|
|
|
const presetEnv = preset.settingsConfig as {
|
|
|
|
|
|
env?: { ANTHROPIC_BASE_URL?: string };
|
|
|
|
|
|
};
|
|
|
|
|
|
if (presetEnv.env?.ANTHROPIC_BASE_URL) {
|
|
|
|
|
|
add(presetEnv.env.ANTHROPIC_BASE_URL);
|
2025-10-16 22:41:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 添加预设的候选端点
|
2025-10-24 09:24:03 +08:00
|
|
|
|
if (preset.endpointCandidates) {
|
|
|
|
|
|
preset.endpointCandidates.forEach((url) => add(url));
|
2025-10-16 22:41:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return Array.from(map.values());
|
|
|
|
|
|
}, [appType, baseUrl, initialData, selectedPresetId, presetEntries]);
|
|
|
|
|
|
|
|
|
|
|
|
const codexEndpoints = useMemo<EndpointCandidate[]>(() => {
|
|
|
|
|
|
if (appType !== "codex") return [];
|
|
|
|
|
|
|
|
|
|
|
|
const map = new Map<string, EndpointCandidate>();
|
2025-10-24 09:24:03 +08:00
|
|
|
|
// 所有端点都标记为 isCustom: true,给用户完全的管理自由
|
2025-10-16 22:41:36 +08:00
|
|
|
|
const add = (url?: string) => {
|
|
|
|
|
|
if (!url) return;
|
|
|
|
|
|
const sanitized = url.trim().replace(/\/+$/, "");
|
|
|
|
|
|
if (!sanitized || map.has(sanitized)) return;
|
2025-10-24 09:24:03 +08:00
|
|
|
|
map.set(sanitized, { url: sanitized, isCustom: true });
|
2025-10-16 22:41:36 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-24 09:24:03 +08:00
|
|
|
|
// 1. 编辑模式:从 meta.custom_endpoints 读取已保存的端点(优先)
|
|
|
|
|
|
if (initialData?.meta?.custom_endpoints) {
|
|
|
|
|
|
const customEndpoints = initialData.meta.custom_endpoints;
|
|
|
|
|
|
for (const url of Object.keys(customEndpoints)) {
|
|
|
|
|
|
add(url);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 当前 Codex Base URL
|
2025-10-16 22:41:36 +08:00
|
|
|
|
if (codexBaseUrl) {
|
|
|
|
|
|
add(codexBaseUrl);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-24 09:24:03 +08:00
|
|
|
|
// 3. 编辑模式:初始数据中的 URL
|
2025-10-24 13:02:35 +08:00
|
|
|
|
const initialCodexConfig = initialData?.settingsConfig as
|
|
|
|
|
|
| {
|
|
|
|
|
|
config?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
| undefined;
|
2025-10-24 09:24:03 +08:00
|
|
|
|
const configStr = initialCodexConfig?.config ?? "";
|
2025-10-16 22:41:36 +08:00
|
|
|
|
// 从 TOML 中提取 base_url
|
2025-10-24 09:24:03 +08:00
|
|
|
|
const match = /base_url\s*=\s*["']([^"']+)["']/i.exec(configStr);
|
2025-10-16 22:41:36 +08:00
|
|
|
|
if (match?.[1]) {
|
|
|
|
|
|
add(match[1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-24 09:24:03 +08:00
|
|
|
|
// 4. 预设中的 endpointCandidates(也允许用户删除)
|
2025-10-16 22:41:36 +08:00
|
|
|
|
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 presetMatch = /base_url\s*=\s*["']([^"']+)["']/i.exec(
|
2025-10-18 16:52:02 +08:00
|
|
|
|
presetConfig,
|
2025-10-16 22:41:36 +08:00
|
|
|
|
);
|
|
|
|
|
|
if (presetMatch?.[1]) {
|
|
|
|
|
|
add(presetMatch[1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 添加预设的候选端点
|
2025-10-24 09:24:03 +08:00
|
|
|
|
if (preset.endpointCandidates) {
|
|
|
|
|
|
preset.endpointCandidates.forEach((url) => add(url));
|
2025-10-16 22:41:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return Array.from(map.values());
|
|
|
|
|
|
}, [appType, codexBaseUrl, initialData, selectedPresetId, presetEntries]);
|
|
|
|
|
|
|
|
|
|
|
|
return appType === "codex" ? codexEndpoints : claudeEndpoints;
|
|
|
|
|
|
}
|