From 6541c14421a9cd5ef12f44e5e9c858b59a6709c8 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 16 Oct 2025 19:56:00 +0800 Subject: [PATCH] refactor: extract API Key link and custom endpoints logic into hooks - Create useApiKeyLink hook to manage API Key retrieval link display and URL - Create useCustomEndpoints hook to collect endpoints from multiple sources - Simplify ProviderForm by using these new hooks - Reduce code duplication and improve maintainability - Fix TypeScript error with form.watch("websiteUrl") by providing default empty string --- .../providers/forms/ProviderForm.tsx | 130 ++++++------------ src/components/providers/forms/hooks/index.ts | 2 + .../providers/forms/hooks/useApiKeyLink.ts | 63 +++++++++ .../forms/hooks/useCustomEndpoints.ts | 92 +++++++++++++ 4 files changed, 197 insertions(+), 90 deletions(-) create mode 100644 src/components/providers/forms/hooks/useApiKeyLink.ts create mode 100644 src/components/providers/forms/hooks/useCustomEndpoints.ts diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index e251b4c..ff0df78 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -33,6 +33,8 @@ import { useBaseUrlState, useModelState, useCodexConfigState, + useApiKeyLink, + useCustomEndpoints, } from "./hooks"; const CLAUDE_DEFAULT_CONFIG = JSON.stringify({ env: {}, config: {} }, null, 2); @@ -202,49 +204,9 @@ export function ProviderForm({ } } - // 若为"新建供应商",将端点候选一并随提交落盘到 meta.custom_endpoints: - // - 用户在弹窗中新增的自定义端点(draftCustomEndpoints,已去重) - // - 预设中的 endpointCandidates(若存在) - // - 当前选中的基础 URL(baseUrl/codexBaseUrl) - if (!initialData) { - const urlSet = new Set(); - const push = (raw?: string) => { - const url = (raw || "").trim().replace(/\/+$/, ""); - if (url) urlSet.add(url); - }; - - // 自定义端点(仅来自用户新增) - for (const u of draftCustomEndpoints) push(u); - - // 预设端点候选 - if (selectedPresetId && selectedPresetId !== "custom") { - const entry = presetEntries.find((item) => item.id === selectedPresetId); - if (entry) { - const preset = entry.preset as any; - if (Array.isArray(preset?.endpointCandidates)) { - for (const u of preset.endpointCandidates as string[]) push(u); - } - } - } - - // 当前 Base URL - if (appType === "codex") { - push(codexBaseUrl); - } else { - push(baseUrl); - } - - const urls = Array.from(urlSet.values()); - if (urls.length > 0) { - const now = Date.now(); - const customMap: Record = {}; - for (const url of urls) { - if (!customMap[url]) { - customMap[url] = { url, addedAt: now, lastUsed: undefined }; - } - } - payload.meta = { custom_endpoints: customMap }; - } + // 新建供应商时:添加自定义端点 + if (!initialData && customEndpointsMap) { + payload.meta = { custom_endpoints: customEndpointsMap }; } onSubmit(payload); @@ -302,51 +264,39 @@ export function ProviderForm({ const shouldShowSpeedTest = category === "third_party" || category === "custom"; - // 判断是否显示 Claude API Key 获取链接 - const shouldShowClaudeApiKeyLink = - appType === "claude" && - category !== "official" && - (category === "cn_official" || - category === "aggregator" || - category === "third_party"); + // 使用 API Key 链接 hook (Claude) + const { + shouldShowApiKeyLink: shouldShowClaudeApiKeyLink, + websiteUrl: claudeWebsiteUrl, + } = useApiKeyLink({ + appType: "claude", + category, + selectedPresetId, + presetEntries, + formWebsiteUrl: form.watch("websiteUrl") || "", + }); - // 获取当前 Claude 供应商的网址(用于 API Key 链接) - const getCurrentClaudeWebsiteUrl = (): string => { - if (selectedPresetId && selectedPresetId !== "custom") { - const entry = presetEntries.find((item) => item.id === selectedPresetId); - if (entry) { - const preset = entry.preset as ProviderPreset; - // 第三方供应商优先使用 apiKeyUrl - return preset.category === "third_party" - ? preset.apiKeyUrl || preset.websiteUrl || "" - : preset.websiteUrl || ""; - } - } - return form.watch("websiteUrl") || ""; - }; + // 使用 API Key 链接 hook (Codex) + const { + shouldShowApiKeyLink: shouldShowCodexApiKeyLink, + websiteUrl: codexWebsiteUrl, + } = useApiKeyLink({ + appType: "codex", + category, + selectedPresetId, + presetEntries, + formWebsiteUrl: form.watch("websiteUrl") || "", + }); - // 判断是否显示 Codex API Key 获取链接 - const shouldShowCodexApiKeyLink = - appType === "codex" && - category !== "official" && - (category === "cn_official" || - category === "aggregator" || - category === "third_party"); - - // 获取当前 Codex 供应商的网址(用于 API Key 链接) - const getCurrentCodexWebsiteUrl = (): string => { - if (selectedPresetId && selectedPresetId !== "custom") { - const entry = presetEntries.find((item) => item.id === selectedPresetId); - if (entry) { - const preset = entry.preset as CodexProviderPreset; - // 第三方供应商优先使用 apiKeyUrl - return preset.category === "third_party" - ? preset.apiKeyUrl || preset.websiteUrl || "" - : preset.websiteUrl || ""; - } - } - return form.watch("websiteUrl") || ""; - }; + // 使用自定义端点 hook + const customEndpointsMap = useCustomEndpoints({ + appType, + selectedPresetId, + presetEntries, + draftCustomEndpoints, + baseUrl, + codexBaseUrl, + }); const handlePresetChange = (value: string) => { setSelectedPresetId(value); @@ -510,10 +460,10 @@ export function ProviderForm({ disabled={category === "official"} /> {/* API Key 获取链接 */} - {shouldShowClaudeApiKeyLink && getCurrentClaudeWebsiteUrl() && ( + {shouldShowClaudeApiKeyLink && claudeWebsiteUrl && (
{/* Codex API Key 获取链接 */} - {shouldShowCodexApiKeyLink && getCurrentCodexWebsiteUrl() && ( + {shouldShowCodexApiKeyLink && codexWebsiteUrl && (
{ + return ( + category !== "official" && + (category === "cn_official" || + category === "aggregator" || + category === "third_party") + ); + }, [category]); + + // 获取当前供应商的网址(用于 API Key 链接) + const getWebsiteUrl = useMemo(() => { + if (selectedPresetId && selectedPresetId !== "custom") { + const entry = presetEntries.find((item) => item.id === selectedPresetId); + if (entry) { + const preset = entry.preset; + // 第三方供应商优先使用 apiKeyUrl + return preset.category === "third_party" + ? preset.apiKeyUrl || preset.websiteUrl || "" + : preset.websiteUrl || ""; + } + } + return formWebsiteUrl || ""; + }, [selectedPresetId, presetEntries, formWebsiteUrl]); + + return { + shouldShowApiKeyLink: appType === "claude" + ? shouldShowApiKeyLink + : appType === "codex" + ? shouldShowApiKeyLink + : false, + websiteUrl: getWebsiteUrl, + }; +} diff --git a/src/components/providers/forms/hooks/useCustomEndpoints.ts b/src/components/providers/forms/hooks/useCustomEndpoints.ts new file mode 100644 index 0000000..cc7b02d --- /dev/null +++ b/src/components/providers/forms/hooks/useCustomEndpoints.ts @@ -0,0 +1,92 @@ +import { useMemo } from "react"; +import type { AppType } from "@/lib/api"; +import type { CustomEndpoint } from "@/types"; +import type { ProviderPreset } from "@/config/providerPresets"; +import type { CodexProviderPreset } from "@/config/codexProviderPresets"; + +type PresetEntry = { + id: string; + preset: ProviderPreset | CodexProviderPreset; +}; + +interface UseCustomEndpointsProps { + appType: AppType; + selectedPresetId: string | null; + presetEntries: PresetEntry[]; + draftCustomEndpoints: string[]; + baseUrl: string; + codexBaseUrl: string; +} + +/** + * 收集和管理自定义端点 + * + * 收集来源: + * 1. 用户在测速弹窗中新增的自定义端点 + * 2. 预设中的 endpointCandidates + * 3. 当前选中的 Base URL + */ +export function useCustomEndpoints({ + appType, + selectedPresetId, + presetEntries, + draftCustomEndpoints, + baseUrl, + codexBaseUrl, +}: UseCustomEndpointsProps) { + const customEndpointsMap = useMemo(() => { + const urlSet = new Set(); + + // 辅助函数:标准化并添加 URL + const push = (raw?: string) => { + const url = (raw || "").trim().replace(/\/+$/, ""); + if (url) urlSet.add(url); + }; + + // 1. 自定义端点(来自用户新增) + for (const u of draftCustomEndpoints) push(u); + + // 2. 预设端点候选 + if (selectedPresetId && selectedPresetId !== "custom") { + const entry = presetEntries.find((item) => item.id === selectedPresetId); + if (entry) { + const preset = entry.preset as any; + if (Array.isArray(preset?.endpointCandidates)) { + for (const u of preset.endpointCandidates as string[]) push(u); + } + } + } + + // 3. 当前 Base URL + if (appType === "codex") { + push(codexBaseUrl); + } else { + push(baseUrl); + } + + // 构建 CustomEndpoint map + const urls = Array.from(urlSet.values()); + if (urls.length === 0) { + return null; + } + + const now = Date.now(); + const customMap: Record = {}; + for (const url of urls) { + if (!customMap[url]) { + customMap[url] = { url, addedAt: now, lastUsed: undefined }; + } + } + + return customMap; + }, [ + appType, + selectedPresetId, + presetEntries, + draftCustomEndpoints, + baseUrl, + codexBaseUrl, + ]); + + return customEndpointsMap; +}