diff --git a/src/pages/AiProvidersPage.tsx b/src/pages/AiProvidersPage.tsx index 9fc6997..5c8d29a 100644 --- a/src/pages/AiProvidersPage.tsx +++ b/src/pages/AiProvidersPage.tsx @@ -1,4 +1,4 @@ -import { Fragment, useEffect, useMemo, useState, type ReactNode } from 'react'; +import { Fragment, useCallback, useEffect, useMemo, useState, type ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; @@ -7,7 +7,7 @@ import { Modal } from '@/components/ui/Modal'; import { EmptyState } from '@/components/ui/EmptyState'; import { HeaderInputList } from '@/components/ui/HeaderInputList'; import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores'; -import { providersApi } from '@/services/api'; +import { providersApi, usageApi } from '@/services/api'; import type { GeminiKeyConfig, ProviderKeyConfig, @@ -15,8 +15,10 @@ import type { ApiKeyEntry, ModelAlias } from '@/types'; +import type { KeyStats, KeyStatBucket } from '@/utils/usage'; import { headersToEntries, buildHeaderObject, type HeaderEntry } from '@/utils/headers'; import { maskApiKey } from '@/utils/format'; +import styles from './AiProvidersPage.module.scss'; type ProviderModal = | { type: 'gemini'; index: number | null } @@ -64,6 +66,11 @@ const parseExcludedModels = (text: string): string[] => const excludedModelsToText = (models?: string[]) => (Array.isArray(models) ? models.join('\n') : ''); +// 根据 auth_index 获取统计数据 +const getStatsByAuthIndex = (authIndex: string, keyStats: KeyStats): KeyStatBucket => { + return keyStats.byAuthIndex?.[authIndex] ?? { success: 0, failure: 0 }; +}; + const buildApiKeyEntry = (input?: Partial): ApiKeyEntry => ({ apiKey: input?.apiKey ?? '', proxyUrl: input?.proxyUrl ?? '', @@ -87,6 +94,7 @@ export function AiProvidersPage() { const [codexConfigs, setCodexConfigs] = useState([]); const [claudeConfigs, setClaudeConfigs] = useState([]); const [openaiProviders, setOpenaiProviders] = useState([]); + const [keyStats, setKeyStats] = useState({ bySource: {}, byAuthIndex: {} }); const [modal, setModal] = useState(null); @@ -116,6 +124,16 @@ export function AiProvidersPage() { const disableControls = useMemo(() => connectionStatus !== 'connected', [connectionStatus]); + // 加载 key 统计 + const loadKeyStats = useCallback(async () => { + try { + const stats = await usageApi.getKeyStats(); + setKeyStats(stats); + } catch { + // 静默失败 + } + }, []); + const loadConfigs = async () => { setLoading(true); setError(''); @@ -134,7 +152,8 @@ export function AiProvidersPage() { useEffect(() => { loadConfigs(); - }, []); + loadKeyStats(); + }, [loadKeyStats]); useEffect(() => { if (config?.geminiApiKeys) setGeminiKeys(config.geminiApiKeys); diff --git a/src/pages/AuthFilesPage.module.scss b/src/pages/AuthFilesPage.module.scss index be89a4f..e954a7a 100644 --- a/src/pages/AuthFilesPage.module.scss +++ b/src/pages/AuthFilesPage.module.scss @@ -198,31 +198,40 @@ .cardStats { display: flex; - gap: $spacing-md; + gap: $spacing-sm; padding: $spacing-sm 0; } .statSuccess { - display: flex; + display: inline-flex; align-items: center; - gap: 4px; - font-size: 13px; - color: var(--success-color, #22c55e); - font-weight: 500; + gap: 6px; + font-size: 12px; + font-weight: 600; + color: #fff; + background-color: #22c55e; + padding: 4px 12px; + border-radius: 14px; + white-space: nowrap; } .statFailure { - display: flex; + display: inline-flex; align-items: center; - gap: 4px; - font-size: 13px; - color: var(--danger-color, #ef4444); - font-weight: 500; + gap: 6px; + font-size: 12px; + font-weight: 600; + color: #fff; + background-color: #ef4444; + padding: 4px 12px; + border-radius: 14px; + white-space: nowrap; } .statIcon { font-style: normal; - font-size: 12px; + font-size: 11px; + line-height: 1; } .cardActions { diff --git a/src/pages/AuthFilesPage.tsx b/src/pages/AuthFilesPage.tsx index 01cf9c6..7835ac6 100644 --- a/src/pages/AuthFilesPage.tsx +++ b/src/pages/AuthFilesPage.tsx @@ -488,12 +488,10 @@ export function AuthFilesPage() {
- - {t('stats.success')}: {fileStats.success} + {t('stats.success')}:{fileStats.success}次 - - {t('stats.failure')}: {fileStats.failure} + {t('stats.failure')}:{fileStats.failure}次