perf(providers,auth-files): cache status bar data and add auto-refresh

This commit is contained in:
Supra4E8C
2025-12-27 15:26:38 +08:00
parent 408e6e5872
commit cb6b810d6d
2 changed files with 83 additions and 17 deletions

View File

@@ -1,5 +1,6 @@
import { Fragment, useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';
import { Fragment, useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { useInterval } from '@/hooks/useInterval';
import { Card } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
@@ -204,6 +205,7 @@ export function AiProvidersPage() {
const [openaiProviders, setOpenaiProviders] = useState<OpenAIProviderConfig[]>([]);
const [keyStats, setKeyStats] = useState<KeyStats>({ bySource: {}, byAuthIndex: {} });
const [usageDetails, setUsageDetails] = useState<UsageDetail[]>([]);
const loadingKeyStatsRef = useRef(false);
const [modal, setModal] = useState<ProviderModal | null>(null);
@@ -275,8 +277,11 @@ export function AiProvidersPage() {
[openaiForm.modelEntries]
);
// 加载 key 统计和 usage 明细
// 加载 key 统计和 usage 明细API 层已有60秒超时
const loadKeyStats = useCallback(async () => {
// 防止重复请求
if (loadingKeyStatsRef.current) return;
loadingKeyStatsRef.current = true;
try {
const usageResponse = await usageApi.getUsage();
const usageData = usageResponse?.usage ?? usageResponse;
@@ -287,6 +292,8 @@ export function AiProvidersPage() {
setUsageDetails(details);
} catch {
// 静默失败
} finally {
loadingKeyStatsRef.current = false;
}
}, []);
@@ -318,6 +325,9 @@ export function AiProvidersPage() {
loadKeyStats();
}, [loadKeyStats]);
// 定时刷新状态数据每240秒
useInterval(loadKeyStats, 240_000);
useEffect(() => {
if (config?.geminiApiKeys) setGeminiKeys(config.geminiApiKeys);
if (config?.codexApiKeys) setCodexConfigs(config.codexApiKeys);
@@ -1097,9 +1107,43 @@ export function AiProvidersPage() {
);
};
// 预计算所有 apiKey 的状态栏数据(避免每次渲染重复计算)
const statusBarCache = useMemo(() => {
const cache = new Map<string, ReturnType<typeof calculateStatusBarData>>();
// 收集所有需要计算的 apiKey
const allApiKeys = new Set<string>();
geminiKeys.forEach((k) => k.apiKey && allApiKeys.add(k.apiKey));
codexConfigs.forEach((k) => k.apiKey && allApiKeys.add(k.apiKey));
claudeConfigs.forEach((k) => k.apiKey && allApiKeys.add(k.apiKey));
openaiProviders.forEach((p) => {
(p.apiKeyEntries || []).forEach((e) => e.apiKey && allApiKeys.add(e.apiKey));
});
// 预计算每个 apiKey 的状态数据
allApiKeys.forEach((apiKey) => {
cache.set(apiKey, calculateStatusBarData(usageDetails, apiKey));
});
return cache;
}, [usageDetails, geminiKeys, codexConfigs, claudeConfigs, openaiProviders]);
// 预计算 OpenAI 提供商的汇总状态栏数据
const openaiStatusBarCache = useMemo(() => {
const cache = new Map<string, ReturnType<typeof calculateStatusBarData>>();
openaiProviders.forEach((provider) => {
const allKeys = (provider.apiKeyEntries || []).map((e) => e.apiKey).filter(Boolean);
const filteredDetails = usageDetails.filter((detail) => allKeys.includes(detail.source));
cache.set(provider.name, calculateStatusBarData(filteredDetails));
});
return cache;
}, [usageDetails, openaiProviders]);
// 渲染状态监测栏
const renderStatusBar = (apiKey: string) => {
const statusData = calculateStatusBarData(usageDetails, apiKey);
const statusData = statusBarCache.get(apiKey) || calculateStatusBarData([], apiKey);
const hasData = statusData.totalSuccess + statusData.totalFailure > 0;
const rateClass = !hasData
? ''
@@ -1132,11 +1176,8 @@ export function AiProvidersPage() {
};
// 渲染 OpenAI 提供商的状态栏(汇总多个 apiKey
const renderOpenAIStatusBar = (apiKeyEntries: ApiKeyEntry[] | undefined) => {
// 合并所有 apiKey 的 usage details
const allKeys = (apiKeyEntries || []).map((e) => e.apiKey).filter(Boolean);
const filteredDetails = usageDetails.filter((detail) => allKeys.includes(detail.source));
const statusData = calculateStatusBarData(filteredDetails);
const renderOpenAIStatusBar = (providerName: string) => {
const statusData = openaiStatusBarCache.get(providerName) || calculateStatusBarData([]);
const hasData = statusData.totalSuccess + statusData.totalFailure > 0;
const rateClass = !hasData
? ''
@@ -1806,7 +1847,7 @@ export function AiProvidersPage() {
</span>
</div>
{/* 状态监测栏(汇总) */}
{renderOpenAIStatusBar(item.apiKeyEntries)}
{renderOpenAIStatusBar(item.name)}
</Fragment>
);
},

View File

@@ -1,5 +1,6 @@
import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useInterval } from '@/hooks/useInterval';
import { Card } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
@@ -166,6 +167,7 @@ export function AuthFilesPage() {
const [savingExcluded, setSavingExcluded] = useState(false);
const fileInputRef = useRef<HTMLInputElement | null>(null);
const loadingKeyStatsRef = useRef(false);
const excludedUnsupportedRef = useRef(false);
const disableControls = connectionStatus !== 'connected';
@@ -197,8 +199,11 @@ export function AuthFilesPage() {
}
}, [t]);
// 加载 key 统计和 usage 明细
// 加载 key 统计和 usage 明细API 层已有60秒超时
const loadKeyStats = useCallback(async () => {
// 防止重复请求
if (loadingKeyStatsRef.current) return;
loadingKeyStatsRef.current = true;
try {
const usageResponse = await usageApi.getUsage();
const usageData = usageResponse?.usage ?? usageResponse;
@@ -209,6 +214,8 @@ export function AuthFilesPage() {
setUsageDetails(details);
} catch {
// 静默失败
} finally {
loadingKeyStatsRef.current = false;
}
}, []);
@@ -244,6 +251,9 @@ export function AuthFilesPage() {
loadExcluded();
}, [loadFiles, loadKeyStats, loadExcluded]);
// 定时刷新状态数据每240秒
useInterval(loadKeyStats, 240_000);
// 提取所有存在的类型
const existingTypes = useMemo(() => {
const types = new Set<string>(['all']);
@@ -577,19 +587,34 @@ export function AuthFilesPage() {
</div>
);
// 预计算所有认证文件的状态栏数据(避免每次渲染重复计算)
const statusBarCache = useMemo(() => {
const cache = new Map<string, ReturnType<typeof calculateStatusBarData>>();
files.forEach((file) => {
const rawAuthIndex = file['auth_index'] ?? file.authIndex;
const authIndexKey = normalizeAuthIndexValue(rawAuthIndex);
if (authIndexKey) {
// 过滤出属于该认证文件的 usage 明细
const filteredDetails = usageDetails.filter((detail) => {
const detailAuthIndex = normalizeAuthIndexValue(detail.auth_index);
return detailAuthIndex !== null && detailAuthIndex === authIndexKey;
});
cache.set(authIndexKey, calculateStatusBarData(filteredDetails));
}
});
return cache;
}, [usageDetails, files]);
// 渲染状态监测栏
const renderStatusBar = (item: AuthFileItem) => {
// 认证文件使用 authIndex 来匹配 usage 数据
const rawAuthIndex = item['auth_index'] ?? item.authIndex;
const authIndexKey = normalizeAuthIndexValue(rawAuthIndex);
// 过滤出属于该认证文件的 usage 明细
const filteredDetails = usageDetails.filter((detail) => {
const detailAuthIndex = normalizeAuthIndexValue(detail.auth_index);
return detailAuthIndex !== null && detailAuthIndex === authIndexKey;
});
const statusData = calculateStatusBarData(filteredDetails);
const statusData = (authIndexKey && statusBarCache.get(authIndexKey)) || calculateStatusBarData([]);
const hasData = statusData.totalSuccess + statusData.totalFailure > 0;
const rateClass = !hasData
? ''