From 4ebcf51873cd344b6c1c7c087e59dea30d41c5f0 Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Wed, 24 Jun 2026 15:23:31 +0800 Subject: [PATCH] feat: add APIKEY.FUN provider support with detailed management and UI integration - Introduced new provider 'apikeyFun' with support for API key entries, protocols, and configurations. - Implemented UI components for displaying and managing APIKEY.FUN resources, including a dedicated form for input. - Enhanced the ProviderResourcePanel and ProviderResourceTable to accommodate the new provider, including sponsor links and protocol summaries. - Updated localization files to include translations for the new provider and its features. - Refactored provider management logic to handle APIKEY.FUN entries alongside existing providers. --- src/assets/icons/apikey-fun.png | Bin 0 -> 17719 bytes .../providers/ProvidersWorkbenchPage.tsx | 3 + src/features/providers/adapters.ts | 103 +++++++ src/features/providers/brandLogos.ts | 2 + .../ProviderCategoryList.module.scss | 8 + .../components/ProviderCategoryList.tsx | 118 ++++---- .../ProviderResourcePanel.module.scss | 22 ++ .../components/ProviderResourcePanel.tsx | 12 + .../components/ProviderResourceTable.tsx | 25 +- src/features/providers/descriptors.ts | 20 ++ .../providers/sheets/ProviderSheet.tsx | 21 +- .../providers/sheets/ResourceDetailView.tsx | 75 ++++- .../sheets/forms/SponsorProviderForm.tsx | 271 ++++++++++++++++++ .../sheets/forms/sharedForm.module.scss | 35 +++ src/features/providers/sponsor.ts | 44 +++ src/features/providers/types.ts | 20 +- .../providers/useProviderWorkbench.ts | 247 +++++++++++++++- src/i18n/locales/en.json | 19 +- src/i18n/locales/ru.json | 19 +- src/i18n/locales/zh-CN.json | 19 +- src/i18n/locales/zh-TW.json | 19 +- 21 files changed, 1033 insertions(+), 69 deletions(-) create mode 100644 src/assets/icons/apikey-fun.png create mode 100644 src/features/providers/sheets/forms/SponsorProviderForm.tsx create mode 100644 src/features/providers/sponsor.ts diff --git a/src/assets/icons/apikey-fun.png b/src/assets/icons/apikey-fun.png new file mode 100644 index 0000000000000000000000000000000000000000..0364ec694de231c53cab13a012d041d20301f768 GIT binary patch literal 17719 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 { + if (resource.brand === 'apikeyFun') { + return 0; + } if (resource.brand === 'openaiCompatibility') { return getOpenAIProviderRecentWindowStats( resource.raw as OpenAIProviderConfig, diff --git a/src/features/providers/adapters.ts b/src/features/providers/adapters.ts index a8664f1a..edd38796 100644 --- a/src/features/providers/adapters.ts +++ b/src/features/providers/adapters.ts @@ -4,10 +4,17 @@ import { stripDisableAllModelsRule, } from '@/components/providers/utils'; import { maskApiKey } from '@/utils/format'; +import { + APIKEY_FUN_ANTHROPIC_BASE_URL, + APIKEY_FUN_DISPLAY_NAME, + APIKEY_FUN_OPENAI_BASE_URL, + APIKEY_FUN_PROTOCOLS, +} from './sponsor'; import type { ProviderBrand, ProviderResource, ProviderResourceSelector, + SponsorProviderRaw, } from './types'; const countHeaders = (headers?: Record): number => @@ -130,3 +137,99 @@ export function openaiToResource( raw: config, }; } + +export function apiKeyFunToResource(raw: SponsorProviderRaw): ProviderResource | null { + if (raw.openai.length === 0 && raw.claude.length === 0 && raw.codex.length === 0) { + return null; + } + const openaiKeyCount = raw.openai.reduce( + (count, item) => count + (item.config.apiKeyEntries?.length ?? 0), + 0 + ); + const codexKeyCount = raw.codex.length; + const firstOpenAIEntry = raw.openai + .flatMap((item) => item.config.apiKeyEntries ?? []) + .find((entry) => entry.apiKey?.trim()); + const firstCodex = raw.codex.find((item) => item.config.apiKey?.trim()); + const firstClaude = raw.claude.find((item) => item.config.apiKey?.trim()); + const apiKey = + firstOpenAIEntry?.apiKey ?? firstCodex?.config.apiKey ?? firstClaude?.config.apiKey ?? ''; + const openaiDisabled = + raw.openai.length > 0 && raw.openai.every((item) => item.config.disabled === true); + const codexDisabled = + raw.codex.length > 0 && + raw.codex.every((item) => hasDisableAllModelsRule(item.config.excludedModels)); + const claudeDisabled = + raw.claude.length > 0 && + raw.claude.every((item) => hasDisableAllModelsRule(item.config.excludedModels)); + const enabledCount = + (raw.openai.length > 0 && !openaiDisabled ? 1 : 0) + + (raw.codex.length > 0 && !codexDisabled ? 1 : 0) + + (raw.claude.length > 0 && !claudeDisabled ? 1 : 0); + const allResourcesConfigured = + raw.openai.length > 0 || raw.codex.length > 0 || raw.claude.length > 0; + const disabled = allResourcesConfigured && enabledCount === 0; + const models = [ + ...raw.openai.flatMap((item) => collectModelNames(item.config.models)), + ...raw.codex.flatMap((item) => collectModelNames(item.config.models)), + ...raw.claude.flatMap((item) => collectModelNames(item.config.models)), + ]; + const uniqueModels = Array.from(new Set(models)); + const headerCount = + raw.openai.reduce((count, item) => count + countHeaders(item.config.headers), 0) + + raw.codex.reduce((count, item) => count + countHeaders(item.config.headers), 0) + + raw.claude.reduce((count, item) => count + countHeaders(item.config.headers), 0); + const priority = Math.max( + 0, + ...raw.openai.map((item) => normalizePriority(item.config.priority)), + ...raw.codex.map((item) => normalizePriority(item.config.priority)), + ...raw.claude.map((item) => normalizePriority(item.config.priority)) + ); + + return { + id: buildId('apikeyFun', 0, 'sponsor'), + brand: 'apikeyFun', + originalIndex: 0, + name: APIKEY_FUN_DISPLAY_NAME, + identifier: APIKEY_FUN_DISPLAY_NAME, + apiKeyPreview: apiKey ? maskApiKey(apiKey) : null, + apiKey: apiKey || null, + authIndex: null, + baseUrl: `${APIKEY_FUN_OPENAI_BASE_URL} / ${APIKEY_FUN_ANTHROPIC_BASE_URL}`, + proxyUrl: + firstOpenAIEntry?.proxyUrl ?? + raw.codex.find((item) => item.config.proxyUrl)?.config.proxyUrl ?? + raw.claude.find((item) => item.config.proxyUrl)?.config.proxyUrl ?? + null, + prefix: + raw.openai[0]?.config.prefix ?? + raw.codex[0]?.config.prefix ?? + raw.claude[0]?.config.prefix ?? + null, + modelCount: uniqueModels.length, + models: uniqueModels, + priority, + headerCount, + excludedModelCount: + raw.codex.reduce( + (count, item) => count + stripDisableAllModelsRule(item.config.excludedModels).length, + 0 + ) + + raw.claude.reduce( + (count, item) => count + stripDisableAllModelsRule(item.config.excludedModels).length, + 0 + ), + apiKeyEntryCount: openaiKeyCount + codexKeyCount + raw.claude.length, + disabled, + flags: { + protocols: [...APIKEY_FUN_PROTOCOLS], + }, + selector: { + brand: 'apikeyFun', + openaiIndices: raw.openai.map((item) => item.index), + claudeIndices: raw.claude.map((item) => item.index), + codexIndices: raw.codex.map((item) => item.index), + } as ProviderResourceSelector, + raw, + }; +} diff --git a/src/features/providers/brandLogos.ts b/src/features/providers/brandLogos.ts index 7b7ec931..1f592e82 100644 --- a/src/features/providers/brandLogos.ts +++ b/src/features/providers/brandLogos.ts @@ -3,6 +3,7 @@ import codexLogo from '@/assets/icons/codex.svg'; import geminiLogo from '@/assets/icons/gemini.svg'; import openaiLogo from '@/assets/icons/openai-light.svg'; import vertexLogo from '@/assets/icons/vertex.svg'; +import apikeyFunLogo from '@/assets/icons/apikey-fun.png'; import type { ProviderBrand } from './types'; export interface ProviderBrandLogo { @@ -16,4 +17,5 @@ export const PROVIDER_LOGOS: Record = { codex: { src: codexLogo }, vertex: { src: vertexLogo }, openaiCompatibility: { src: openaiLogo, invertOnDark: true }, + apikeyFun: { src: apikeyFunLogo }, }; diff --git a/src/features/providers/components/ProviderCategoryList.module.scss b/src/features/providers/components/ProviderCategoryList.module.scss index c2bf432b..f8a54072 100644 --- a/src/features/providers/components/ProviderCategoryList.module.scss +++ b/src/features/providers/components/ProviderCategoryList.module.scss @@ -1,6 +1,14 @@ @use '../../../styles/mixins' as *; @use '../../../styles/variables' as *; +.stack { + display: flex; + flex-direction: column; + gap: 12px; + min-width: 0; + align-self: start; +} + .aside { background: var(--bg-primary); border: 1px solid var(--border-color); diff --git a/src/features/providers/components/ProviderCategoryList.tsx b/src/features/providers/components/ProviderCategoryList.tsx index f86bf458..8361d1c6 100644 --- a/src/features/providers/components/ProviderCategoryList.tsx +++ b/src/features/providers/components/ProviderCategoryList.tsx @@ -15,61 +15,75 @@ export function ProviderCategoryList({ onSelect, }: ProviderCategoryListProps) { const { t } = useTranslation(); + const providerGroups = groups.filter((group) => group.id !== 'apikeyFun'); + const sponsorGroups = groups.filter((group) => group.id === 'apikeyFun'); - return ( - + + + {total} + + + ); + })} + + ); + + return ( +
+ + {sponsorGroups.length > 0 ? ( + + ) : null} +
); } diff --git a/src/features/providers/components/ProviderResourcePanel.module.scss b/src/features/providers/components/ProviderResourcePanel.module.scss index a78bd868..a2c16448 100644 --- a/src/features/providers/components/ProviderResourcePanel.module.scss +++ b/src/features/providers/components/ProviderResourcePanel.module.scss @@ -73,6 +73,28 @@ letter-spacing: -0.005em; } +.sponsorLink { + display: inline-block; + margin-top: 6px; + color: var(--primary-color); + font-family: $font-mono; + font-size: 12px; + line-height: 1.4; + max-width: min(520px, 100%); + overflow-wrap: anywhere; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + + &:focus-visible { + outline: 2px solid var(--primary-color); + outline-offset: 2px; + border-radius: var(--radius-sm); + } +} + .searchWrap { position: relative; min-width: 0; diff --git a/src/features/providers/components/ProviderResourcePanel.tsx b/src/features/providers/components/ProviderResourcePanel.tsx index 8cfdcaaf..2fa1c123 100644 --- a/src/features/providers/components/ProviderResourcePanel.tsx +++ b/src/features/providers/components/ProviderResourcePanel.tsx @@ -8,6 +8,8 @@ import { ProviderResourceToolbar } from './ProviderResourceToolbar'; import type { ProviderSortBy, SortDir } from '../types'; import styles from './ProviderResourcePanel.module.scss'; +const APIKEY_FUN_AFFILIATE_URL = 'https://apikey.fun/register?aff=AKCPA'; + export interface ProviderPanelControls { sortBy: ProviderSortBy; sortDir: SortDir; @@ -72,6 +74,16 @@ export function ProviderResourcePanel({ {t(`providersPage.providerNames.${group.id}`)} + {group.id === 'apikeyFun' ? ( + + {APIKEY_FUN_AFFILIATE_URL} + + ) : null}