From c9e0c5d73ec2b9c04a4fc5be55e30a5992135932 Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Sun, 22 Feb 2026 13:45:27 +0800 Subject: [PATCH] refactor(usage): centralize normalizeAuthIndex --- src/components/quota/quotaConfigs.ts | 10 +++++----- src/components/usage/CredentialStatsCard.tsx | 18 ++++-------------- .../usage/RequestEventsDetailsCard.tsx | 18 ++++-------------- .../authFiles/components/AuthFileCard.tsx | 5 ++--- src/features/authFiles/constants.ts | 15 ++------------- .../hooks/useAuthFilesStatusBarCache.ts | 8 +++----- src/pages/LogsPage.tsx | 16 +++------------- src/utils/quota/parsers.ts | 13 ++----------- src/utils/usage.ts | 2 +- 9 files changed, 26 insertions(+), 79 deletions(-) diff --git a/src/components/quota/quotaConfigs.ts b/src/components/quota/quotaConfigs.ts index 026877d5..039fdd95 100644 --- a/src/components/quota/quotaConfigs.ts +++ b/src/components/quota/quotaConfigs.ts @@ -34,7 +34,6 @@ import { CODEX_REQUEST_HEADERS, GEMINI_CLI_QUOTA_URL, GEMINI_CLI_REQUEST_HEADERS, - normalizeAuthIndexValue, normalizeGeminiCliModelId, normalizeNumberValue, normalizePlanType, @@ -60,6 +59,7 @@ import { isGeminiCliFile, isRuntimeOnlyAuthFile, } from '@/utils/quota'; +import { normalizeAuthIndex } from '@/utils/usage'; import type { QuotaRenderHelpers } from './QuotaCard'; import styles from '@/pages/QuotaPage.module.scss'; @@ -135,7 +135,7 @@ const fetchAntigravityQuota = async ( t: TFunction ): Promise => { const rawAuthIndex = file['auth_index'] ?? file.authIndex; - const authIndex = normalizeAuthIndexValue(rawAuthIndex); + const authIndex = normalizeAuthIndex(rawAuthIndex); if (!authIndex) { throw new Error(t('antigravity_quota.missing_auth_index')); } @@ -377,7 +377,7 @@ const fetchCodexQuota = async ( t: TFunction ): Promise<{ planType: string | null; windows: CodexQuotaWindow[] }> => { const rawAuthIndex = file['auth_index'] ?? file.authIndex; - const authIndex = normalizeAuthIndexValue(rawAuthIndex); + const authIndex = normalizeAuthIndex(rawAuthIndex); if (!authIndex) { throw new Error(t('codex_quota.missing_auth_index')); } @@ -419,7 +419,7 @@ const fetchGeminiCliQuota = async ( t: TFunction ): Promise => { const rawAuthIndex = file['auth_index'] ?? file.authIndex; - const authIndex = normalizeAuthIndexValue(rawAuthIndex); + const authIndex = normalizeAuthIndex(rawAuthIndex); if (!authIndex) { throw new Error(t('gemini_cli_quota.missing_auth_index')); } @@ -667,7 +667,7 @@ const fetchClaudeQuota = async ( t: TFunction ): Promise<{ windows: ClaudeQuotaWindow[]; extraUsage?: ClaudeExtraUsage | null }> => { const rawAuthIndex = file['auth_index'] ?? file.authIndex; - const authIndex = normalizeAuthIndexValue(rawAuthIndex); + const authIndex = normalizeAuthIndex(rawAuthIndex); if (!authIndex) { throw new Error(t('claude_quota.missing_auth_index')); } diff --git a/src/components/usage/CredentialStatsCard.tsx b/src/components/usage/CredentialStatsCard.tsx index db36bee3..0579b30c 100644 --- a/src/components/usage/CredentialStatsCard.tsx +++ b/src/components/usage/CredentialStatsCard.tsx @@ -5,7 +5,8 @@ import { computeKeyStats, collectUsageDetails, buildCandidateUsageSourceIds, - formatCompactNumber + formatCompactNumber, + normalizeAuthIndex } from '@/utils/usage'; import { authFilesApi } from '@/services/api/authFiles'; import type { GeminiKeyConfig, ProviderKeyConfig, OpenAIProviderConfig } from '@/types'; @@ -43,17 +44,6 @@ interface CredentialBucket { failure: number; } -function normalizeAuthIndexValue(value: unknown): string | null { - if (typeof value === 'number' && Number.isFinite(value)) { - return value.toString(); - } - if (typeof value === 'string') { - const trimmed = value.trim(); - return trimmed || null; - } - return null; -} - export function CredentialStatsCard({ usage, loading, @@ -78,7 +68,7 @@ export function CredentialStatsCard({ const map = new Map(); files.forEach((file) => { const rawAuthIndex = file['auth_index'] ?? file.authIndex; - const key = normalizeAuthIndexValue(rawAuthIndex); + const key = normalizeAuthIndex(rawAuthIndex); if (key) { map.set(key, { name: file.name || key, @@ -195,7 +185,7 @@ export function CredentialStatsCard({ // Also collect fallback stats for details without source but with auth_index. const sourceToAuthFile = new Map(); details.forEach((d) => { - const authIdx = normalizeAuthIndexValue(d.auth_index); + const authIdx = normalizeAuthIndex(d.auth_index); if (!d.source) { if (!authIdx) return; const fallback = fallbackByAuthIndex.get(authIdx) ?? { success: 0, failure: 0 }; diff --git a/src/components/usage/RequestEventsDetailsCard.tsx b/src/components/usage/RequestEventsDetailsCard.tsx index 14e7bf14..14dd5298 100644 --- a/src/components/usage/RequestEventsDetailsCard.tsx +++ b/src/components/usage/RequestEventsDetailsCard.tsx @@ -10,7 +10,8 @@ import type { AuthFileItem } from '@/types/authFile'; import { buildCandidateUsageSourceIds, collectUsageDetails, - extractTotalTokens + extractTotalTokens, + normalizeAuthIndex } from '@/utils/usage'; import { downloadBlob } from '@/utils/download'; import styles from '@/pages/UsagePage.module.scss'; @@ -69,17 +70,6 @@ type SourceInfo = { type: string; }; -function normalizeAuthIndexValue(value: unknown): string | null { - if (typeof value === 'number' && Number.isFinite(value)) { - return value.toString(); - } - if (typeof value === 'string') { - const trimmed = value.trim(); - return trimmed || null; - } - return null; -} - export function RequestEventsDetailsCard({ usage, loading, @@ -106,7 +96,7 @@ export function RequestEventsDetailsCard({ if (!Array.isArray(files)) return; const map = new Map(); files.forEach((file) => { - const key = normalizeAuthIndexValue(file['auth_index'] ?? file.authIndex); + const key = normalizeAuthIndex(file['auth_index'] ?? file.authIndex); if (!key) return; map.set(key, { name: file.name || key, @@ -205,7 +195,7 @@ export function RequestEventsDetailsCard({ authIndexRaw === null || authIndexRaw === undefined || authIndexRaw === '' ? '-' : String(authIndexRaw); - const normalizedAuthIndex = normalizeAuthIndexValue(authIndexRaw); + const normalizedAuthIndex = normalizeAuthIndex(authIndexRaw); const sourceInfo = sourceInfoMap.get(sourceRaw); const authInfo = normalizedAuthIndex ? authFileMap.get(normalizedAuthIndex) : undefined; const source = sourceInfo?.displayName diff --git a/src/features/authFiles/components/AuthFileCard.tsx b/src/features/authFiles/components/AuthFileCard.tsx index 502c7926..16f498d9 100644 --- a/src/features/authFiles/components/AuthFileCard.tsx +++ b/src/features/authFiles/components/AuthFileCard.tsx @@ -6,7 +6,7 @@ import { IconBot, IconCheck, IconCode, IconDownload, IconInfo, IconTrash2 } from import { ProviderStatusBar } from '@/components/providers/ProviderStatusBar'; import type { AuthFileItem } from '@/types'; import { resolveAuthProvider } from '@/utils/quota'; -import { calculateStatusBarData, type KeyStats } from '@/utils/usage'; +import { calculateStatusBarData, normalizeAuthIndex, type KeyStats } from '@/utils/usage'; import { formatFileSize } from '@/utils/format'; import { AUTH_FILE_REFRESH_WARNING_MS, @@ -15,7 +15,6 @@ import { getTypeColor, getTypeLabel, isRuntimeOnlyAuthFile, - normalizeAuthIndexValue, resolveAuthFileStats, type QuotaProviderType, type ResolvedTheme @@ -108,7 +107,7 @@ export function AuthFileCard(props: AuthFileCardProps) { : ''; const rawAuthIndex = file['auth_index'] ?? file.authIndex; - const authIndexKey = normalizeAuthIndexValue(rawAuthIndex); + const authIndexKey = normalizeAuthIndex(rawAuthIndex); const statusData = (authIndexKey && statusBarCache.get(authIndexKey)) || calculateStatusBarData([]); const rawStatus = String(file.status ?? file['status'] ?? '') diff --git a/src/features/authFiles/constants.ts b/src/features/authFiles/constants.ts index 12dac667..0bb27c6d 100644 --- a/src/features/authFiles/constants.ts +++ b/src/features/authFiles/constants.ts @@ -1,6 +1,7 @@ import type { TFunction } from 'i18next'; import type { AuthFileItem } from '@/types'; import { + normalizeAuthIndex, normalizeUsageSourceId, type KeyStatBucket, type KeyStats @@ -143,18 +144,6 @@ export const parseDisableCoolingValue = (value: unknown): boolean | undefined => return undefined; }; -// 标准化 auth_index 值(与 usage.ts 中的 normalizeAuthIndex 保持一致) -export function normalizeAuthIndexValue(value: unknown): string | null { - if (typeof value === 'number' && Number.isFinite(value)) { - return value.toString(); - } - if (typeof value === 'string') { - const trimmed = value.trim(); - return trimmed ? trimmed : null; - } - return null; -} - export function isRuntimeOnlyAuthFile(file: AuthFileItem): boolean { const raw = file['runtime_only'] ?? file.runtimeOnly; if (typeof raw === 'boolean') return raw; @@ -168,7 +157,7 @@ export function resolveAuthFileStats(file: AuthFileItem, stats: KeyStats): KeySt // 兼容 auth_index 和 authIndex 两种字段名(API 返回的是 auth_index) const rawAuthIndex = file['auth_index'] ?? file.authIndex; - const authIndexKey = normalizeAuthIndexValue(rawAuthIndex); + const authIndexKey = normalizeAuthIndex(rawAuthIndex); // 尝试根据 authIndex 匹配 if (authIndexKey && stats.byAuthIndex?.[authIndexKey]) { diff --git a/src/features/authFiles/hooks/useAuthFilesStatusBarCache.ts b/src/features/authFiles/hooks/useAuthFilesStatusBarCache.ts index 836e0b0c..e14a1500 100644 --- a/src/features/authFiles/hooks/useAuthFilesStatusBarCache.ts +++ b/src/features/authFiles/hooks/useAuthFilesStatusBarCache.ts @@ -1,7 +1,6 @@ import { useMemo } from 'react'; import type { AuthFileItem } from '@/types'; -import { calculateStatusBarData, type UsageDetail } from '@/utils/usage'; -import { normalizeAuthIndexValue } from '@/features/authFiles/constants'; +import { calculateStatusBarData, normalizeAuthIndex, type UsageDetail } from '@/utils/usage'; export type AuthFileStatusBarData = ReturnType; @@ -11,11 +10,11 @@ export function useAuthFilesStatusBarCache(files: AuthFileItem[], usageDetails: files.forEach((file) => { const rawAuthIndex = file['auth_index'] ?? file.authIndex; - const authIndexKey = normalizeAuthIndexValue(rawAuthIndex); + const authIndexKey = normalizeAuthIndex(rawAuthIndex); if (authIndexKey) { const filteredDetails = usageDetails.filter((detail) => { - const detailAuthIndex = normalizeAuthIndexValue(detail.auth_index); + const detailAuthIndex = normalizeAuthIndex(detail.auth_index); return detailAuthIndex !== null && detailAuthIndex === authIndexKey; }); cache.set(authIndexKey, calculateStatusBarData(filteredDetails)); @@ -25,4 +24,3 @@ export function useAuthFilesStatusBarCache(files: AuthFileItem[], usageDetails: return cache; }, [files, usageDetails]); } - diff --git a/src/pages/LogsPage.tsx b/src/pages/LogsPage.tsx index bc083d8b..81ec0c32 100644 --- a/src/pages/LogsPage.tsx +++ b/src/pages/LogsPage.tsx @@ -30,6 +30,7 @@ import { formatUnixTimestamp } from '@/utils/format'; import { buildCandidateUsageSourceIds, collectUsageDetailsWithEndpoint, + normalizeAuthIndex, type UsageDetailWithEndpoint } from '@/utils/usage'; import styles from './LogsPage.module.scss'; @@ -171,17 +172,6 @@ const TRACE_MATCH_STRONG_WINDOW_MS = 3 * 1000; const TRACE_MATCH_WINDOW_MS = 10 * 1000; const TRACE_MATCH_MAX_WINDOW_MS = 30 * 1000; -const normalizeTraceAuthIndex = (value: unknown): string | null => { - if (typeof value === 'number' && Number.isFinite(value)) { - return value.toString(); - } - if (typeof value === 'string') { - const trimmed = value.trim(); - return trimmed || null; - } - return null; -}; - const normalizeTracePath = (value?: string) => String(value ?? '') .replace(/^"+|"+$/g, '') @@ -798,7 +788,7 @@ export function LogsPage() { if (Array.isArray(files)) { const map = new Map(); files.forEach((file) => { - const key = normalizeTraceAuthIndex(file['auth_index'] ?? file.authIndex); + const key = normalizeAuthIndex(file['auth_index'] ?? file.authIndex); if (!key) return; map.set(key, { name: file.name || key, @@ -949,7 +939,7 @@ export function LogsPage() { return matchedSource; } - const authIndexKey = normalizeTraceAuthIndex(authIndex); + const authIndexKey = normalizeAuthIndex(authIndex); if (authIndexKey) { const authInfo = traceAuthFileMap.get(authIndexKey); if (authInfo) { diff --git a/src/utils/quota/parsers.ts b/src/utils/quota/parsers.ts index c83c5689..a7aa6df9 100644 --- a/src/utils/quota/parsers.ts +++ b/src/utils/quota/parsers.ts @@ -3,19 +3,10 @@ */ import type { ClaudeUsagePayload, CodexUsagePayload, GeminiCliQuotaPayload } from '@/types'; +import { normalizeAuthIndex } from '@/utils/usage'; const GEMINI_CLI_MODEL_SUFFIX = '_vertex'; - -export function normalizeAuthIndexValue(value: unknown): string | null { - if (typeof value === 'number' && Number.isFinite(value)) { - return value.toString(); - } - if (typeof value === 'string') { - const trimmed = value.trim(); - return trimmed ? trimmed : null; - } - return null; -} +export { normalizeAuthIndex }; export function normalizeStringValue(value: unknown): string | null { if (typeof value === 'string') { diff --git a/src/utils/usage.ts b/src/utils/usage.ts index bdc25f1f..2d5f3781 100644 --- a/src/utils/usage.ts +++ b/src/utils/usage.ts @@ -222,7 +222,7 @@ export function filterUsageByTimeRange(usageData: T, range: UsageTimeRange, n } as T; } -const normalizeAuthIndex = (value: unknown) => { +export const normalizeAuthIndex = (value: unknown) => { if (typeof value === 'number' && Number.isFinite(value)) { return value.toString(); }