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();
});