mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-19 19:20:49 +08:00
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:
@@ -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 { useTranslation } from 'react-i18next';
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
@@ -7,7 +7,7 @@ import { Modal } from '@/components/ui/Modal';
|
|||||||
import { EmptyState } from '@/components/ui/EmptyState';
|
import { EmptyState } from '@/components/ui/EmptyState';
|
||||||
import { HeaderInputList } from '@/components/ui/HeaderInputList';
|
import { HeaderInputList } from '@/components/ui/HeaderInputList';
|
||||||
import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
|
import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
|
||||||
import { providersApi } from '@/services/api';
|
import { providersApi, usageApi } from '@/services/api';
|
||||||
import type {
|
import type {
|
||||||
GeminiKeyConfig,
|
GeminiKeyConfig,
|
||||||
ProviderKeyConfig,
|
ProviderKeyConfig,
|
||||||
@@ -15,8 +15,10 @@ import type {
|
|||||||
ApiKeyEntry,
|
ApiKeyEntry,
|
||||||
ModelAlias
|
ModelAlias
|
||||||
} from '@/types';
|
} from '@/types';
|
||||||
|
import type { KeyStats, KeyStatBucket } from '@/utils/usage';
|
||||||
import { headersToEntries, buildHeaderObject, type HeaderEntry } from '@/utils/headers';
|
import { headersToEntries, buildHeaderObject, type HeaderEntry } from '@/utils/headers';
|
||||||
import { maskApiKey } from '@/utils/format';
|
import { maskApiKey } from '@/utils/format';
|
||||||
|
import styles from './AiProvidersPage.module.scss';
|
||||||
|
|
||||||
type ProviderModal =
|
type ProviderModal =
|
||||||
| { type: 'gemini'; index: number | null }
|
| { type: 'gemini'; index: number | null }
|
||||||
@@ -64,6 +66,11 @@ const parseExcludedModels = (text: string): string[] =>
|
|||||||
|
|
||||||
const excludedModelsToText = (models?: string[]) => (Array.isArray(models) ? models.join('\n') : '');
|
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 => ({
|
const buildApiKeyEntry = (input?: Partial<ApiKeyEntry>): ApiKeyEntry => ({
|
||||||
apiKey: input?.apiKey ?? '',
|
apiKey: input?.apiKey ?? '',
|
||||||
proxyUrl: input?.proxyUrl ?? '',
|
proxyUrl: input?.proxyUrl ?? '',
|
||||||
@@ -87,6 +94,7 @@ export function AiProvidersPage() {
|
|||||||
const [codexConfigs, setCodexConfigs] = useState<ProviderKeyConfig[]>([]);
|
const [codexConfigs, setCodexConfigs] = useState<ProviderKeyConfig[]>([]);
|
||||||
const [claudeConfigs, setClaudeConfigs] = useState<ProviderKeyConfig[]>([]);
|
const [claudeConfigs, setClaudeConfigs] = useState<ProviderKeyConfig[]>([]);
|
||||||
const [openaiProviders, setOpenaiProviders] = useState<OpenAIProviderConfig[]>([]);
|
const [openaiProviders, setOpenaiProviders] = useState<OpenAIProviderConfig[]>([]);
|
||||||
|
const [keyStats, setKeyStats] = useState<KeyStats>({ bySource: {}, byAuthIndex: {} });
|
||||||
|
|
||||||
const [modal, setModal] = useState<ProviderModal | null>(null);
|
const [modal, setModal] = useState<ProviderModal | null>(null);
|
||||||
|
|
||||||
@@ -116,6 +124,16 @@ export function AiProvidersPage() {
|
|||||||
|
|
||||||
const disableControls = useMemo(() => connectionStatus !== 'connected', [connectionStatus]);
|
const disableControls = useMemo(() => connectionStatus !== 'connected', [connectionStatus]);
|
||||||
|
|
||||||
|
// 加载 key 统计
|
||||||
|
const loadKeyStats = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const stats = await usageApi.getKeyStats();
|
||||||
|
setKeyStats(stats);
|
||||||
|
} catch {
|
||||||
|
// 静默失败
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const loadConfigs = async () => {
|
const loadConfigs = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
@@ -134,7 +152,8 @@ export function AiProvidersPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadConfigs();
|
loadConfigs();
|
||||||
}, []);
|
loadKeyStats();
|
||||||
|
}, [loadKeyStats]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (config?.geminiApiKeys) setGeminiKeys(config.geminiApiKeys);
|
if (config?.geminiApiKeys) setGeminiKeys(config.geminiApiKeys);
|
||||||
|
|||||||
@@ -198,31 +198,40 @@
|
|||||||
|
|
||||||
.cardStats {
|
.cardStats {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: $spacing-md;
|
gap: $spacing-sm;
|
||||||
padding: $spacing-sm 0;
|
padding: $spacing-sm 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statSuccess {
|
.statSuccess {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
color: var(--success-color, #22c55e);
|
font-weight: 600;
|
||||||
font-weight: 500;
|
color: #fff;
|
||||||
|
background-color: #22c55e;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 14px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statFailure {
|
.statFailure {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
color: var(--danger-color, #ef4444);
|
font-weight: 600;
|
||||||
font-weight: 500;
|
color: #fff;
|
||||||
|
background-color: #ef4444;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 14px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statIcon {
|
.statIcon {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardActions {
|
.cardActions {
|
||||||
|
|||||||
@@ -488,12 +488,10 @@ export function AuthFilesPage() {
|
|||||||
|
|
||||||
<div className={styles.cardStats}>
|
<div className={styles.cardStats}>
|
||||||
<span className={styles.statSuccess}>
|
<span className={styles.statSuccess}>
|
||||||
<i className={styles.statIcon}>✓</i>
|
{t('stats.success')}:{fileStats.success}次
|
||||||
{t('stats.success')}: {fileStats.success}
|
|
||||||
</span>
|
</span>
|
||||||
<span className={styles.statFailure}>
|
<span className={styles.statFailure}>
|
||||||
<i className={styles.statIcon}>✗</i>
|
{t('stats.failure')}:{fileStats.failure}次
|
||||||
{t('stats.failure')}: {fileStats.failure}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user