From 5076b212af6a9318505e52536899af1060faf053 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Sun, 12 Apr 2026 01:24:40 -0400 Subject: [PATCH] fix(ui): clarify Gemini quota tooltip labels --- .../shared/quota-tooltip-content.tsx | 132 +++++++++++++++--- .../shared/quota-tooltip-content.test.tsx | 24 ++-- 2 files changed, 127 insertions(+), 29 deletions(-) diff --git a/ui/src/components/shared/quota-tooltip-content.tsx b/ui/src/components/shared/quota-tooltip-content.tsx index bd1e14dd..b653dde7 100644 --- a/ui/src/components/shared/quota-tooltip-content.tsx +++ b/ui/src/components/shared/quota-tooltip-content.tsx @@ -78,6 +78,63 @@ function getClaudeWindowDisplayLabel(rateLimitType: string, fallback: string): s } } +function formatGeminiTokenType(tokenType: string | null | undefined): string | null { + if (!tokenType) return null; + + switch (tokenType.trim().toLowerCase()) { + case 'requests': + return 'Requests'; + case 'input': + return 'Input tokens'; + case 'output': + return 'Output tokens'; + default: + return tokenType + .split(/[\s_-]+/g) + .map((part) => part.trim()) + .filter((part) => part.length > 0) + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(' '); + } +} + +function formatGeminiBucketLabel(label: string): string { + switch (label) { + case 'Gemini Flash Lite Series': + return 'Flash Lite'; + case 'Gemini Flash Series': + return 'Flash'; + case 'Gemini Pro Series': + return 'Pro'; + default: + return label; + } +} + +function formatGeminiBucketModels(modelIds: string[] | undefined): string | null { + const uniqueModelIds = Array.from(new Set((modelIds || []).filter(Boolean))); + return uniqueModelIds.length > 0 ? uniqueModelIds.join(', ') : null; +} + +function formatGeminiRemainingAmount( + remainingAmount: number | null | undefined, + tokenType: string | null | undefined +): string | null { + if (remainingAmount === null || remainingAmount === undefined) return null; + + const formattedAmount = remainingAmount.toLocaleString(); + switch (tokenType?.trim().toLowerCase()) { + case 'requests': + return `${formattedAmount} requests remaining`; + case 'input': + return `${formattedAmount} input tokens remaining`; + case 'output': + return `${formattedAmount} output tokens remaining`; + default: + return `${formattedAmount} remaining`; + } +} + function renderEntitlementRows(entitlement: ProviderEntitlementEvidence | undefined) { if (!entitlement) return null; @@ -301,6 +358,14 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro const hasBucketResetTime = quota.buckets.some((bucket) => !!bucket.resetTime); const hasEntitlementTier = !!quota.entitlement?.rawTierLabel || quota.entitlement?.normalizedTier !== 'unknown'; + const distinctTokenTypes = Array.from( + new Set( + quota.buckets + .map((bucket) => formatGeminiTokenType(bucket.tokenType)) + .filter((tokenType): tokenType is string => !!tokenType) + ) + ); + const sharedTokenType = distinctTokenTypes.length === 1 ? distinctTokenTypes[0] : null; return (
@@ -317,28 +382,53 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro {quota.creditBalance.toLocaleString()}
)} -

Buckets:

- {quota.buckets.map((b) => ( -
-
- - {b.label} - {b.tokenType ? ` (${b.tokenType})` : ''} - - {b.remainingPercent}% -
- {((b.remainingAmount !== null && b.remainingAmount !== undefined) || b.resetTime) && ( -
- - {b.remainingAmount !== null && b.remainingAmount !== undefined - ? `${b.remainingAmount.toLocaleString()} remaining` - : ''} - - {formatAbsoluteResetTime(b.resetTime) ?? ''} +
+

Model quotas:

+ {sharedTokenType && ( +

+ All buckets report {sharedTokenType} +

+ )} +
+ {quota.buckets.map((bucket) => { + const bucketTokenType = sharedTokenType ? null : formatGeminiTokenType(bucket.tokenType); + const bucketModels = formatGeminiBucketModels(bucket.modelIds); + const remainingAmountLabel = formatGeminiRemainingAmount( + bucket.remainingAmount, + bucket.tokenType + ); + + return ( +
+
+
+
+ + {formatGeminiBucketLabel(bucket.label)} + + {bucketTokenType && ( + + {bucketTokenType} + + )} +
+ {bucketModels && ( +
+ {bucketModels} +
+ )} +
+ {bucket.remainingPercent}%
- )} -
- ))} + {(remainingAmountLabel || bucket.resetTime) && ( +
+ {remainingAmountLabel ?? ''} + {formatAbsoluteResetTime(bucket.resetTime) ?? ''} +
+ )} +
+ ); + })} {!hasBucketResetTime && }
); diff --git a/ui/tests/unit/ui/components/shared/quota-tooltip-content.test.tsx b/ui/tests/unit/ui/components/shared/quota-tooltip-content.test.tsx index 790ec8a1..bf600694 100644 --- a/ui/tests/unit/ui/components/shared/quota-tooltip-content.test.tsx +++ b/ui/tests/unit/ui/components/shared/quota-tooltip-content.test.tsx @@ -12,22 +12,22 @@ function createGeminiQuotaResult( { id: 'gemini-flash-lite-series::combined', label: 'Gemini Flash Lite Series', - tokenType: null, + tokenType: 'requests', remainingFraction: 1, remainingPercent: 100, remainingAmount: 100, resetTime: '2026-01-30T09:00:00Z', - modelIds: ['gemini-2.5-flash-lite'], + modelIds: ['gemini-2.5-flash-lite', 'gemini-3.1-flash-lite-preview'], }, { id: 'gemini-flash-series::combined', label: 'Gemini Flash Series', - tokenType: null, + tokenType: 'requests', remainingFraction: 0.82, remainingPercent: 82, remainingAmount: 82, resetTime: '2026-01-30T14:00:00Z', - modelIds: ['gemini-3-flash-preview'], + modelIds: ['gemini-3-flash-preview', 'gemini-3.1-flash-preview', 'gemini-2.5-flash'], }, ], projectId: 'cloudaicompanion-test-123', @@ -51,7 +51,7 @@ function createGeminiQuotaResult( } describe('QuotaTooltipContent', () => { - it('renders Gemini tier, credits, remaining amount, and bucket reset timestamps', () => { + it('renders Gemini tier, model coverage, and clearer bucket wording', () => { const quota = createGeminiQuotaResult(); const expectedReset = new Date('2026-01-30T14:00:00Z').toLocaleString(undefined, { month: '2-digit', @@ -69,9 +69,17 @@ describe('QuotaTooltipContent', () => { expect(screen.getByText('g1-pro-tier')).toBeInTheDocument(); expect(screen.getByText('Credits')).toBeInTheDocument(); expect(screen.getByText('12')).toBeInTheDocument(); - expect(screen.getByText('Gemini Flash Lite Series')).toBeInTheDocument(); - expect(screen.getByText('100 remaining')).toBeInTheDocument(); - expect(screen.getByText('82 remaining')).toBeInTheDocument(); + expect(screen.getByText('Model quotas:')).toBeInTheDocument(); + expect(screen.getByText('All buckets report Requests')).toBeInTheDocument(); + expect(screen.getByText('Flash Lite')).toBeInTheDocument(); + expect( + screen.getByText('gemini-2.5-flash-lite, gemini-3.1-flash-lite-preview') + ).toBeInTheDocument(); + expect(screen.getByText('100 requests remaining')).toBeInTheDocument(); + expect( + screen.getByText('gemini-3-flash-preview, gemini-3.1-flash-preview, gemini-2.5-flash') + ).toBeInTheDocument(); + expect(screen.getByText('82 requests remaining')).toBeInTheDocument(); expect(screen.getByText(expectedReset)).toBeInTheDocument(); });