import { Fragment, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/Button'; import { Card } from '@/components/ui/Card'; import { IconCheck, IconX } from '@/components/ui/icons'; import iconOpenaiLight from '@/assets/icons/openai-light.svg'; import iconOpenaiDark from '@/assets/icons/openai-dark.svg'; import type { OpenAIProviderConfig } from '@/types'; import { maskApiKey } from '@/utils/format'; import { buildCandidateUsageSourceIds, calculateStatusBarData, type KeyStats, type UsageDetail, } from '@/utils/usage'; import styles from '@/pages/AiProvidersPage.module.scss'; import { ProviderList } from '../ProviderList'; import { ProviderStatusBar } from '../ProviderStatusBar'; import { getOpenAIProviderStats, getStatsBySource } from '../utils'; import type { OpenAIFormState } from '../types'; import { OpenAIModal } from './OpenAIModal'; interface OpenAISectionProps { configs: OpenAIProviderConfig[]; keyStats: KeyStats; usageDetails: UsageDetail[]; loading: boolean; disableControls: boolean; isSaving: boolean; isSwitching: boolean; resolvedTheme: string; isModalOpen: boolean; modalIndex: number | null; onAdd: () => void; onEdit: (index: number) => void; onDelete: (index: number) => void; onCloseModal: () => void; onSave: (data: OpenAIFormState, index: number | null) => Promise; } export function OpenAISection({ configs, keyStats, usageDetails, loading, disableControls, isSaving, isSwitching, resolvedTheme, isModalOpen, modalIndex, onAdd, onEdit, onDelete, onCloseModal, onSave, }: OpenAISectionProps) { const { t } = useTranslation(); const actionsDisabled = disableControls || isSaving || isSwitching; const statusBarCache = useMemo(() => { const cache = new Map>(); configs.forEach((provider) => { const sourceIds = new Set(); buildCandidateUsageSourceIds({ prefix: provider.prefix }).forEach((id) => sourceIds.add(id)); (provider.apiKeyEntries || []).forEach((entry) => { buildCandidateUsageSourceIds({ apiKey: entry.apiKey }).forEach((id) => sourceIds.add(id)); }); const filteredDetails = sourceIds.size ? usageDetails.filter((detail) => sourceIds.has(detail.source)) : []; cache.set(provider.name, calculateStatusBarData(filteredDetails)); }); return cache; }, [configs, usageDetails]); const initialData = modalIndex !== null ? configs[modalIndex] : undefined; return ( <> {t('ai_providers.openai_title')} } extra={ } > items={configs} loading={loading} keyField={(item) => item.name} emptyTitle={t('ai_providers.openai_empty_title')} emptyDescription={t('ai_providers.openai_empty_desc')} onEdit={onEdit} onDelete={onDelete} actionsDisabled={actionsDisabled} renderContent={(item) => { const stats = getOpenAIProviderStats(item.apiKeyEntries, keyStats, item.prefix); const headerEntries = Object.entries(item.headers || {}); const apiKeyEntries = item.apiKeyEntries || []; const statusData = statusBarCache.get(item.name) || calculateStatusBarData([]); return (
{item.name}
{item.prefix && (
{t('common.prefix')}: {item.prefix}
)}
{t('common.base_url')}: {item.baseUrl}
{headerEntries.length > 0 && (
{headerEntries.map(([key, value]) => ( {key}: {value} ))}
)} {apiKeyEntries.length > 0 && (
{t('ai_providers.openai_keys_count')}: {apiKeyEntries.length}
{apiKeyEntries.map((entry, entryIndex) => { const entryStats = getStatsBySource(entry.apiKey, keyStats); return (
{entryIndex + 1} {maskApiKey(entry.apiKey)} {entry.proxyUrl && ( {entry.proxyUrl} )}
{entryStats.success} {entryStats.failure}
); })}
)}
{t('ai_providers.openai_models_count')}: {item.models?.length || 0}
{item.models?.length ? (
{item.models.map((model) => ( {model.name} {model.alias && model.alias !== model.name && ( {model.alias} )} ))}
) : null} {item.testModel && (
Test Model: {item.testModel}
)}
{t('stats.success')}: {stats.success} {t('stats.failure')}: {stats.failure}
); }} />
); }