diff --git a/src/cliproxy/quota-fetcher.ts b/src/cliproxy/quota-fetcher.ts index f1b993d8..9301d985 100644 --- a/src/cliproxy/quota-fetcher.ts +++ b/src/cliproxy/quota-fetcher.ts @@ -494,15 +494,22 @@ async function fetchAvailableModels(accessToken: string, _projectId: string): Pr const remaining = quotaInfo.remainingFraction ?? quotaInfo.remaining_fraction ?? quotaInfo.remaining; - // Skip invalid values (NaN, Infinity, non-numbers) - if (typeof remaining !== 'number' || !isFinite(remaining)) continue; - - // Convert to percentage (0-100) and clamp to valid range - const percentage = Math.max(0, Math.min(100, Math.round(remaining * 100))); - // Extract reset time const resetTime = quotaInfo.resetTime || quotaInfo.reset_time || null; + // If remaining is not a valid number but resetTime exists, treat as exhausted (0%) + // This happens when Claude models hit quota limit - API returns resetTime but no fraction + let percentage: number; + if (typeof remaining === 'number' && isFinite(remaining)) { + percentage = Math.max(0, Math.min(100, Math.round(remaining * 100))); + } else if (resetTime) { + // Model is exhausted but has reset time - show as 0% + percentage = 0; + } else { + // No valid data, skip this model + continue; + } + models.push({ name: modelId, displayName: modelData.displayName, diff --git a/ui/src/lib/utils.ts b/ui/src/lib/utils.ts index 397edd5d..a6966d0a 100644 --- a/ui/src/lib/utils.ts +++ b/ui/src/lib/utils.ts @@ -174,8 +174,8 @@ export function getMinClaudeQuota< } /** - * Get reset time for Claude/GPT models (matches getMinClaudeQuota logic). - * Falls back to earliest of all models if Claude/GPT not present (still need reset info). + * Get reset time for Claude/GPT models (primary models). + * Returns null only if no primary models present in response. */ export function getClaudeResetTime< T extends { name: string; displayName?: string; resetTime: string | null }, @@ -183,10 +183,9 @@ export function getClaudeResetTime< if (models.length === 0) return null; const primaryModels = filterPrimaryModels(models); - // Fall back to all models for reset time (we need some reset info even if exhausted) - const targetModels = primaryModels.length > 0 ? primaryModels : models; + if (primaryModels.length === 0) return null; - return targetModels.reduce( + return primaryModels.reduce( (earliest, m) => { if (!m.resetTime) return earliest; if (!earliest) return m.resetTime; @@ -267,11 +266,14 @@ export function getModelsWithTiers< // Add all models with tier info for (const m of models) { const displayName = m.displayName || m.name; + const tier = getModelTier(displayName); result.push({ name: m.name, displayName, percentage: m.percentage, - tier: getModelTier(displayName), + tier, + // Mark primary models at 0% as exhausted for red styling + exhausted: tier === 'primary' && m.percentage === 0, }); }