feat: enhance AiProvidersPage with key statistics loading, improve layout styles, and update AuthFilesPage for better visual representation of success and failure stats

This commit is contained in:
Supra4E8C
2025-12-11 12:24:29 +08:00
parent 3c1a600994
commit d425332eb0
3 changed files with 45 additions and 19 deletions

View File

@@ -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>): ApiKeyEntry => ({
apiKey: input?.apiKey ?? '',
proxyUrl: input?.proxyUrl ?? '',
@@ -87,6 +94,7 @@ export function AiProvidersPage() {
const [codexConfigs, setCodexConfigs] = useState<ProviderKeyConfig[]>([]);
const [claudeConfigs, setClaudeConfigs] = useState<ProviderKeyConfig[]>([]);
const [openaiProviders, setOpenaiProviders] = useState<OpenAIProviderConfig[]>([]);
const [keyStats, setKeyStats] = useState<KeyStats>({ bySource: {}, byAuthIndex: {} });
const [modal, setModal] = useState<ProviderModal | null>(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);

View File

@@ -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 {

View File

@@ -488,12 +488,10 @@ export function AuthFilesPage() {
<div className={styles.cardStats}>
<span className={styles.statSuccess}>
<i className={styles.statIcon}></i>
{t('stats.success')}: {fileStats.success}
{t('stats.success')}{fileStats.success}
</span>
<span className={styles.statFailure}>
<i className={styles.statIcon}></i>
{t('stats.failure')}: {fileStats.failure}
{t('stats.failure')}{fileStats.failure}
</span>
</div>