import type { ProviderKeyStatusSnapshot, QuotaStatusSnapshot, QuotaWindowSnapshot, } from '@/api/endpoints/types/statusSnapshot' export interface ProviderKeyQuotaCarrier { account_quota?: string | null status_snapshot?: ProviderKeyStatusSnapshot | null } function normalizeText(value: unknown): string | null { if (typeof value !== 'string') return null const text = value.trim() return text || null } function clampPercent(value: number): number { if (!Number.isFinite(value)) return 0 if (value < 0) return 0 if (value > 100) return 100 return value } function formatPercent(value: number): string { return `${clampPercent(value).toFixed(1)}%` } function getQuotaSnapshot( input: ProviderKeyQuotaCarrier, ): QuotaStatusSnapshot | null { return input.status_snapshot?.quota ?? null } function getQuotaProviderType( quota: QuotaStatusSnapshot | null | undefined, fallbackProviderType?: string | null, ): string { const snapshotProviderType = normalizeText(quota?.provider_type)?.toLowerCase() if (snapshotProviderType) return snapshotProviderType return normalizeText(fallbackProviderType)?.toLowerCase() || '' } function getQuotaWindows( quota: QuotaStatusSnapshot | null | undefined, ): QuotaWindowSnapshot[] { return Array.isArray(quota?.windows) ? quota.windows : [] } function getQuotaWindowRemainingPercent( window: QuotaWindowSnapshot | null | undefined, ): number | null { if (!window) return null if (typeof window.remaining_ratio === 'number') { return clampPercent(window.remaining_ratio * 100) } if (typeof window.used_ratio === 'number') { return clampPercent((1 - window.used_ratio) * 100) } if (typeof window.limit_value === 'number' && window.limit_value > 0) { if (typeof window.remaining_value === 'number') { return clampPercent((window.remaining_value / window.limit_value) * 100) } if (typeof window.used_value === 'number') { return clampPercent((1 - (window.used_value / window.limit_value)) * 100) } } return null } function getQuotaWindow( quota: QuotaStatusSnapshot | null | undefined, code: string, ): QuotaWindowSnapshot | null { const normalizedCode = code.trim().toLowerCase() return getQuotaWindows(quota).find(window => normalizeText(window.code)?.toLowerCase() === normalizedCode) ?? null } function getQuotaWindowsByScope( quota: QuotaStatusSnapshot | null | undefined, scope: string, ): QuotaWindowSnapshot[] { const normalizedScope = scope.trim().toLowerCase() return getQuotaWindows(quota).filter(window => normalizeText(window.scope)?.toLowerCase() === normalizedScope) } function formatQuotaValue(value: number | null | undefined): string { const normalized = Number(value) if (!Number.isFinite(normalized)) return '0' const rounded = Math.round(normalized) if (Math.abs(normalized - rounded) < 1e-6) { return String(rounded) } return normalized.toFixed(1) } function getCodexQuotaText(quota: QuotaStatusSnapshot): string | null { const parts: string[] = [] for (const [label, code] of [ ['周', 'weekly'], ['5H', '5h'], ['Spark5H', 'spark_5h'], ['Spark周', 'spark_weekly'], ] as const) { const remainingPercent = getQuotaWindowRemainingPercent(getQuotaWindow(quota, code)) if (remainingPercent == null) continue parts.push(`${label}剩余 ${formatPercent(remainingPercent)}`) } if (parts.length > 0) return parts.join(' | ') if (quota.credits?.has_credits === true && typeof quota.credits.balance === 'number') { return `积分 ${quota.credits.balance.toFixed(2)}` } if (quota.credits?.has_credits === true) return '有积分' if (quota.credits?.has_credits === false) return '无可用积分' return normalizeText(quota.label) } function getKiroQuotaText(quota: QuotaStatusSnapshot): string | null { const code = normalizeText(quota.code)?.toLowerCase() if (code === 'banned') { return normalizeText(quota.label) || '账号已封禁' } const window = getQuotaWindow(quota, 'usage') ?? getQuotaWindowsByScope(quota, 'account')[0] ?? null const remainingPercent = getQuotaWindowRemainingPercent(window) if (typeof window?.remaining_value === 'number' && typeof window.limit_value === 'number' && window.limit_value > 0 && window.remaining_value <= 0) { return `剩余 ${formatQuotaValue(window.remaining_value)}/${formatQuotaValue(window.limit_value)}` } if (remainingPercent != null) { if (typeof window?.used_value === 'number' && typeof window.limit_value === 'number' && window.limit_value > 0) { return `剩余 ${formatPercent(remainingPercent)} (${formatQuotaValue(window.used_value)}/${formatQuotaValue(window.limit_value)})` } return `剩余 ${formatPercent(remainingPercent)}` } if (typeof window?.remaining_value === 'number' && typeof window.limit_value === 'number' && window.limit_value > 0) { return `剩余 ${formatQuotaValue(window.remaining_value)}/${formatQuotaValue(window.limit_value)}` } return normalizeText(quota.label) } function getAntigravityQuotaText(quota: QuotaStatusSnapshot): string | null { const code = normalizeText(quota.code)?.toLowerCase() if (code === 'forbidden') { return normalizeText(quota.label) || '访问受限' } const remainingList = getQuotaWindowsByScope(quota, 'model') .map(getQuotaWindowRemainingPercent) .filter((value): value is number => value != null) if (remainingList.length === 0) return normalizeText(quota.label) const minimumRemaining = Math.min(...remainingList) if (remainingList.length === 1) { return `剩余 ${formatPercent(minimumRemaining)}` } return `最低剩余 ${formatPercent(minimumRemaining)} (${remainingList.length} 模型)` } function getGeminiCliQuotaText(quota: QuotaStatusSnapshot): string | null { const modelWindows = getQuotaWindowsByScope(quota, 'model') const activeCoolingModels = modelWindows .filter((window) => { if (window.is_exhausted === true) return true if (typeof window.used_ratio === 'number') return window.used_ratio >= 1.0 - 1e-6 return false }) .filter((window) => { if (typeof window.reset_at !== 'number') return true return window.reset_at > Math.floor(Date.now() / 1000) }) .map((window) => normalizeText(window.label) || normalizeText(window.model) || '模型') if (activeCoolingModels.length === 1) { return `${activeCoolingModels[0]} 冷却中` } if (activeCoolingModels.length > 1) { return `${activeCoolingModels.length} 个模型冷却中` } const remainingList = modelWindows .map(getQuotaWindowRemainingPercent) .filter((value): value is number => value != null) if (remainingList.length === 0) return normalizeText(quota.label) const minimumRemaining = Math.min(...remainingList) if (remainingList.length === 1) { return `剩余 ${formatPercent(minimumRemaining)}` } return `最低剩余 ${formatPercent(minimumRemaining)} (${remainingList.length} 模型)` } function getChatGPTWebQuotaText(quota: QuotaStatusSnapshot): string | null { const window = getQuotaWindow(quota, 'image_gen') ?? getQuotaWindowsByScope(quota, 'account')[0] ?? null if (!window) return normalizeText(quota.label) const remainingPercent = getQuotaWindowRemainingPercent(window) if (typeof window.remaining_value === 'number' && typeof window.limit_value === 'number' && window.limit_value > 0 && window.remaining_value <= 0) { return `生图剩余 ${formatQuotaValue(window.remaining_value)}/${formatQuotaValue(window.limit_value)}` } if (remainingPercent != null) { if (typeof window.used_value === 'number' && typeof window.limit_value === 'number' && window.limit_value > 0) { return `生图剩余 ${formatPercent(remainingPercent)} (${formatQuotaValue(window.used_value)}/${formatQuotaValue(window.limit_value)})` } return `生图剩余 ${formatPercent(remainingPercent)}` } if (typeof window.remaining_value === 'number' && typeof window.limit_value === 'number' && window.limit_value > 0) { return `生图剩余 ${formatQuotaValue(window.remaining_value)}/${formatQuotaValue(window.limit_value)}` } if (typeof window.remaining_value === 'number') { return `生图剩余 ${formatQuotaValue(window.remaining_value)}` } return normalizeText(quota.label) } export function getLegacyAccountQuotaText( input: ProviderKeyQuotaCarrier, ): string | null { return normalizeText(input.account_quota) } export function getQuotaSnapshotFallbackText( input: ProviderKeyQuotaCarrier, fallbackProviderType?: string | null, ): string | null { const quota = getQuotaSnapshot(input) if (!quota) return null const providerType = getQuotaProviderType(quota, fallbackProviderType) switch (providerType) { case 'codex': return getCodexQuotaText(quota) case 'kiro': return getKiroQuotaText(quota) case 'antigravity': return getAntigravityQuotaText(quota) case 'gemini_cli': return getGeminiCliQuotaText(quota) case 'chatgpt_web': return getChatGPTWebQuotaText(quota) default: return normalizeText(quota.label) } } export function getQuotaDisplayText( input: ProviderKeyQuotaCarrier, fallbackProviderType?: string | null, ): string | null { return getQuotaSnapshotFallbackText(input, fallbackProviderType) || getLegacyAccountQuotaText(input) }