feat: add useSpeedTestEndpoints hook to collect all endpoint candidates for speed test modal
- Create useSpeedTestEndpoints hook that collects endpoints from: 1. Current baseUrl/codexBaseUrl 2. Initial data URL (edit mode) 3. Preset's endpointCandidates array - Pass speedTestEndpoints to ClaudeFormFields and CodexFormFields - Update EndpointSpeedTest to use collected endpoints instead of just current URL - Fix PackyCode preset endpoints not appearing in speed test modal
This commit is contained in:
@@ -8,6 +8,10 @@ import { Zap } from "lucide-react";
|
|||||||
import type { ProviderCategory } from "@/types";
|
import type { ProviderCategory } from "@/types";
|
||||||
import type { TemplateValueConfig } from "@/config/providerPresets";
|
import type { TemplateValueConfig } from "@/config/providerPresets";
|
||||||
|
|
||||||
|
interface EndpointCandidate {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface ClaudeFormFieldsProps {
|
interface ClaudeFormFieldsProps {
|
||||||
// API Key
|
// API Key
|
||||||
shouldShowApiKey: boolean;
|
shouldShowApiKey: boolean;
|
||||||
@@ -48,6 +52,9 @@ interface ClaudeFormFieldsProps {
|
|||||||
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
||||||
value: string
|
value: string
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
|
// Speed Test Endpoints
|
||||||
|
speedTestEndpoints: EndpointCandidate[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ClaudeFormFields({
|
export function ClaudeFormFields({
|
||||||
@@ -75,6 +82,7 @@ export function ClaudeFormFields({
|
|||||||
kimiAnthropicModel,
|
kimiAnthropicModel,
|
||||||
kimiAnthropicSmallFastModel,
|
kimiAnthropicSmallFastModel,
|
||||||
onKimiModelChange,
|
onKimiModelChange,
|
||||||
|
speedTestEndpoints,
|
||||||
}: ClaudeFormFieldsProps) {
|
}: ClaudeFormFieldsProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -195,7 +203,7 @@ export function ClaudeFormFields({
|
|||||||
appType="claude"
|
appType="claude"
|
||||||
value={baseUrl}
|
value={baseUrl}
|
||||||
onChange={onBaseUrlChange}
|
onChange={onBaseUrlChange}
|
||||||
initialEndpoints={[{ url: baseUrl }]}
|
initialEndpoints={speedTestEndpoints}
|
||||||
visible={isEndpointModalOpen}
|
visible={isEndpointModalOpen}
|
||||||
onClose={() => onEndpointModalToggle(false)}
|
onClose={() => onEndpointModalToggle(false)}
|
||||||
onCustomEndpointsChange={onCustomEndpointsChange}
|
onCustomEndpointsChange={onCustomEndpointsChange}
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import EndpointSpeedTest from "@/components/ProviderForm/EndpointSpeedTest";
|
|||||||
import { Zap } from "lucide-react";
|
import { Zap } from "lucide-react";
|
||||||
import type { ProviderCategory } from "@/types";
|
import type { ProviderCategory } from "@/types";
|
||||||
|
|
||||||
|
interface EndpointCandidate {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface CodexFormFieldsProps {
|
interface CodexFormFieldsProps {
|
||||||
// API Key
|
// API Key
|
||||||
codexApiKey: string;
|
codexApiKey: string;
|
||||||
@@ -21,6 +25,9 @@ interface CodexFormFieldsProps {
|
|||||||
isEndpointModalOpen: boolean;
|
isEndpointModalOpen: boolean;
|
||||||
onEndpointModalToggle: (open: boolean) => void;
|
onEndpointModalToggle: (open: boolean) => void;
|
||||||
onCustomEndpointsChange: (endpoints: string[]) => void;
|
onCustomEndpointsChange: (endpoints: string[]) => void;
|
||||||
|
|
||||||
|
// Speed Test Endpoints
|
||||||
|
speedTestEndpoints: EndpointCandidate[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CodexFormFields({
|
export function CodexFormFields({
|
||||||
@@ -35,6 +42,7 @@ export function CodexFormFields({
|
|||||||
isEndpointModalOpen,
|
isEndpointModalOpen,
|
||||||
onEndpointModalToggle,
|
onEndpointModalToggle,
|
||||||
onCustomEndpointsChange,
|
onCustomEndpointsChange,
|
||||||
|
speedTestEndpoints,
|
||||||
}: CodexFormFieldsProps) {
|
}: CodexFormFieldsProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -120,7 +128,7 @@ export function CodexFormFields({
|
|||||||
appType="codex"
|
appType="codex"
|
||||||
value={codexBaseUrl}
|
value={codexBaseUrl}
|
||||||
onChange={onBaseUrlChange}
|
onChange={onBaseUrlChange}
|
||||||
initialEndpoints={[{ url: codexBaseUrl }]}
|
initialEndpoints={speedTestEndpoints}
|
||||||
visible={isEndpointModalOpen}
|
visible={isEndpointModalOpen}
|
||||||
onClose={() => onEndpointModalToggle(false)}
|
onClose={() => onEndpointModalToggle(false)}
|
||||||
onCustomEndpointsChange={onCustomEndpointsChange}
|
onCustomEndpointsChange={onCustomEndpointsChange}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
useTemplateValues,
|
useTemplateValues,
|
||||||
useCommonConfigSnippet,
|
useCommonConfigSnippet,
|
||||||
useCodexCommonConfig,
|
useCodexCommonConfig,
|
||||||
|
useSpeedTestEndpoints,
|
||||||
} from "./hooks";
|
} from "./hooks";
|
||||||
|
|
||||||
const CLAUDE_DEFAULT_CONFIG = JSON.stringify({ env: {}, config: {} }, null, 2);
|
const CLAUDE_DEFAULT_CONFIG = JSON.stringify({ env: {}, config: {} }, null, 2);
|
||||||
@@ -370,6 +371,16 @@ export function ProviderForm({
|
|||||||
codexBaseUrl,
|
codexBaseUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 使用端点测速候选 hook
|
||||||
|
const speedTestEndpoints = useSpeedTestEndpoints({
|
||||||
|
appType,
|
||||||
|
selectedPresetId,
|
||||||
|
presetEntries,
|
||||||
|
baseUrl,
|
||||||
|
codexBaseUrl,
|
||||||
|
initialData,
|
||||||
|
});
|
||||||
|
|
||||||
const handlePresetChange = (value: string) => {
|
const handlePresetChange = (value: string) => {
|
||||||
setSelectedPresetId(value);
|
setSelectedPresetId(value);
|
||||||
if (value === "custom") {
|
if (value === "custom") {
|
||||||
@@ -470,6 +481,7 @@ export function ProviderForm({
|
|||||||
kimiAnthropicModel={kimiAnthropicModel}
|
kimiAnthropicModel={kimiAnthropicModel}
|
||||||
kimiAnthropicSmallFastModel={kimiAnthropicSmallFastModel}
|
kimiAnthropicSmallFastModel={kimiAnthropicSmallFastModel}
|
||||||
onKimiModelChange={handleKimiModelChange}
|
onKimiModelChange={handleKimiModelChange}
|
||||||
|
speedTestEndpoints={speedTestEndpoints}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -487,6 +499,7 @@ export function ProviderForm({
|
|||||||
isEndpointModalOpen={isCodexEndpointModalOpen}
|
isEndpointModalOpen={isCodexEndpointModalOpen}
|
||||||
onEndpointModalToggle={setIsCodexEndpointModalOpen}
|
onEndpointModalToggle={setIsCodexEndpointModalOpen}
|
||||||
onCustomEndpointsChange={setDraftCustomEndpoints}
|
onCustomEndpointsChange={setDraftCustomEndpoints}
|
||||||
|
speedTestEndpoints={speedTestEndpoints}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ export { useKimiModelSelector } from "./useKimiModelSelector";
|
|||||||
export { useTemplateValues } from "./useTemplateValues";
|
export { useTemplateValues } from "./useTemplateValues";
|
||||||
export { useCommonConfigSnippet } from "./useCommonConfigSnippet";
|
export { useCommonConfigSnippet } from "./useCommonConfigSnippet";
|
||||||
export { useCodexCommonConfig } from "./useCodexCommonConfig";
|
export { useCodexCommonConfig } from "./useCodexCommonConfig";
|
||||||
|
export { useSpeedTestEndpoints } from "./useSpeedTestEndpoints";
|
||||||
|
|||||||
143
src/components/providers/forms/hooks/useSpeedTestEndpoints.ts
Normal file
143
src/components/providers/forms/hooks/useSpeedTestEndpoints.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import type { AppType } from "@/lib/api";
|
||||||
|
import type { ProviderPreset } from "@/config/providerPresets";
|
||||||
|
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
|
||||||
|
|
||||||
|
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>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EndpointCandidate {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集端点测速弹窗的初始端点列表
|
||||||
|
*
|
||||||
|
* 收集来源:
|
||||||
|
* 1. 当前选中的 Base URL
|
||||||
|
* 2. 编辑模式下的初始数据 URL
|
||||||
|
* 3. 预设中的 endpointCandidates
|
||||||
|
*/
|
||||||
|
export function useSpeedTestEndpoints({
|
||||||
|
appType,
|
||||||
|
selectedPresetId,
|
||||||
|
presetEntries,
|
||||||
|
baseUrl,
|
||||||
|
codexBaseUrl,
|
||||||
|
initialData,
|
||||||
|
}: UseSpeedTestEndpointsProps) {
|
||||||
|
const claudeEndpoints = useMemo<EndpointCandidate[]>(() => {
|
||||||
|
if (appType !== "claude") return [];
|
||||||
|
|
||||||
|
const map = new Map<string, EndpointCandidate>();
|
||||||
|
const add = (url?: string) => {
|
||||||
|
if (!url) return;
|
||||||
|
const sanitized = url.trim().replace(/\/+$/, "");
|
||||||
|
if (!sanitized || map.has(sanitized)) return;
|
||||||
|
map.set(sanitized, { url: sanitized });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. 当前 Base URL
|
||||||
|
if (baseUrl) {
|
||||||
|
add(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 编辑模式:初始数据中的 URL
|
||||||
|
if (initialData && typeof initialData.settingsConfig === "object") {
|
||||||
|
const envUrl = (initialData.settingsConfig as any)?.env
|
||||||
|
?.ANTHROPIC_BASE_URL;
|
||||||
|
if (typeof envUrl === "string") {
|
||||||
|
add(envUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 预设中的 endpointCandidates
|
||||||
|
if (selectedPresetId && selectedPresetId !== "custom") {
|
||||||
|
const entry = presetEntries.find((item) => item.id === selectedPresetId);
|
||||||
|
if (entry) {
|
||||||
|
const preset = entry.preset as ProviderPreset;
|
||||||
|
// 添加预设自己的 baseUrl
|
||||||
|
const presetEnv = (preset.settingsConfig as any)?.env
|
||||||
|
?.ANTHROPIC_BASE_URL;
|
||||||
|
if (typeof presetEnv === "string") {
|
||||||
|
add(presetEnv);
|
||||||
|
}
|
||||||
|
// 添加预设的候选端点
|
||||||
|
if (Array.isArray((preset as any).endpointCandidates)) {
|
||||||
|
for (const u of (preset as any).endpointCandidates as string[]) {
|
||||||
|
add(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(map.values());
|
||||||
|
}, [appType, baseUrl, initialData, selectedPresetId, presetEntries]);
|
||||||
|
|
||||||
|
const codexEndpoints = useMemo<EndpointCandidate[]>(() => {
|
||||||
|
if (appType !== "codex") return [];
|
||||||
|
|
||||||
|
const map = new Map<string, EndpointCandidate>();
|
||||||
|
const add = (url?: string) => {
|
||||||
|
if (!url) return;
|
||||||
|
const sanitized = url.trim().replace(/\/+$/, "");
|
||||||
|
if (!sanitized || map.has(sanitized)) return;
|
||||||
|
map.set(sanitized, { url: sanitized });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. 当前 Codex Base URL
|
||||||
|
if (codexBaseUrl) {
|
||||||
|
add(codexBaseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 编辑模式:初始数据中的 URL
|
||||||
|
const initialCodexConfig =
|
||||||
|
initialData && typeof initialData.settingsConfig?.config === "string"
|
||||||
|
? (initialData.settingsConfig as any).config
|
||||||
|
: "";
|
||||||
|
// 从 TOML 中提取 base_url
|
||||||
|
const match = /base_url\s*=\s*["']([^"']+)["']/i.exec(initialCodexConfig);
|
||||||
|
if (match?.[1]) {
|
||||||
|
add(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 presetMatch = /base_url\s*=\s*["']([^"']+)["']/i.exec(
|
||||||
|
presetConfig
|
||||||
|
);
|
||||||
|
if (presetMatch?.[1]) {
|
||||||
|
add(presetMatch[1]);
|
||||||
|
}
|
||||||
|
// 添加预设的候选端点
|
||||||
|
if (Array.isArray((preset as any).endpointCandidates)) {
|
||||||
|
for (const u of (preset as any).endpointCandidates as string[]) {
|
||||||
|
add(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(map.values());
|
||||||
|
}, [appType, codexBaseUrl, initialData, selectedPresetId, presetEntries]);
|
||||||
|
|
||||||
|
return appType === "codex" ? codexEndpoints : claudeEndpoints;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user