fix(ui): clarify Gemini quota tooltip labels

This commit is contained in:
Tam Nhu Tran committed 2026-04-12 01:24:40 -04:00
1 parent ca8401e5fc
commit 5076b212af
2 files changed
+127 -29

No files matched your search

@@ -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) { function renderEntitlementRows(entitlement: ProviderEntitlementEvidence | undefined) {
if (!entitlement) return null; if (!entitlement) return null;
@@ -301,6 +358,14 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
const hasBucketResetTime = quota.buckets.some((bucket) => !!bucket.resetTime); const hasBucketResetTime = quota.buckets.some((bucket) => !!bucket.resetTime);
const hasEntitlementTier = const hasEntitlementTier =
!!quota.entitlement?.rawTierLabel || quota.entitlement?.normalizedTier !== 'unknown'; !!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 ( return (
<div className="text-xs space-y-1.5"> <div className="text-xs space-y-1.5">
@@ -317,28 +382,53 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
<span className="font-mono">{quota.creditBalance.toLocaleString()}</span> <span className="font-mono">{quota.creditBalance.toLocaleString()}</span>
</div> </div>
)} )}
<p className="font-medium">Buckets:</p> <div className="space-y-1">
{quota.buckets.map((b) => ( <p className="font-medium">Model quotas:</p>
<div key={b.id} className="space-y-0.5"> {sharedTokenType && (
<div className="flex justify-between gap-4"> <p className="text-[11px] text-muted-foreground">
<span className={cn(b.remainingPercent < 20 && lowQuotaTextClass)}> All buckets report {sharedTokenType}
{b.label} </p>
{b.tokenType ? ` (${b.tokenType})` : ''} )}
</span> </div>
<span className="font-mono">{b.remainingPercent}%</span> {quota.buckets.map((bucket) => {
</div> const bucketTokenType = sharedTokenType ? null : formatGeminiTokenType(bucket.tokenType);
{((b.remainingAmount !== null && b.remainingAmount !== undefined) || b.resetTime) && ( const bucketModels = formatGeminiBucketModels(bucket.modelIds);
<div className="flex justify-between gap-4 text-[11px] text-muted-foreground"> const remainingAmountLabel = formatGeminiRemainingAmount(
<span> bucket.remainingAmount,
{b.remainingAmount !== null && b.remainingAmount !== undefined bucket.tokenType
? `${b.remainingAmount.toLocaleString()} remaining` );
: ''}
</span> return (
<span>{formatAbsoluteResetTime(b.resetTime) ?? ''}</span> <div key={bucket.id} className="space-y-0.5">
<div className="flex justify-between gap-4">
<div className="min-w-0 space-y-0.5">
<div className="flex items-center gap-2">
<span className={cn(bucket.remainingPercent < 20 && lowQuotaTextClass)}>
{formatGeminiBucketLabel(bucket.label)}
</span>
{bucketTokenType && (
<span className="rounded border border-border/60 px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-muted-foreground">
{bucketTokenType}
</span>
)}
</div>
{bucketModels && (
<div className="break-words text-[11px] text-muted-foreground">
{bucketModels}
</div>
)}
</div>
<span className="shrink-0 font-mono">{bucket.remainingPercent}%</span>
</div> </div>
)} {(remainingAmountLabel || bucket.resetTime) && (
</div> <div className="flex justify-between gap-4 text-[11px] text-muted-foreground">
))} <span>{remainingAmountLabel ?? ''}</span>
<span>{formatAbsoluteResetTime(bucket.resetTime) ?? ''}</span>
</div>
)}
</div>
);
})}
{!hasBucketResetTime && <ResetTimeIndicator resetTime={resetTime} />} {!hasBucketResetTime && <ResetTimeIndicator resetTime={resetTime} />}
</div> </div>
); );
@@ -12,22 +12,22 @@ function createGeminiQuotaResult(
{ {
id: 'gemini-flash-lite-series::combined', id: 'gemini-flash-lite-series::combined',
label: 'Gemini Flash Lite Series', label: 'Gemini Flash Lite Series',
tokenType: null, tokenType: 'requests',
remainingFraction: 1, remainingFraction: 1,
remainingPercent: 100, remainingPercent: 100,
remainingAmount: 100, remainingAmount: 100,
resetTime: '2026-01-30T09:00:00Z', 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', id: 'gemini-flash-series::combined',
label: 'Gemini Flash Series', label: 'Gemini Flash Series',
tokenType: null, tokenType: 'requests',
remainingFraction: 0.82, remainingFraction: 0.82,
remainingPercent: 82, remainingPercent: 82,
remainingAmount: 82, remainingAmount: 82,
resetTime: '2026-01-30T14:00:00Z', 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', projectId: 'cloudaicompanion-test-123',
@@ -51,7 +51,7 @@ function createGeminiQuotaResult(
} }
describe('QuotaTooltipContent', () => { 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 quota = createGeminiQuotaResult();
const expectedReset = new Date('2026-01-30T14:00:00Z').toLocaleString(undefined, { const expectedReset = new Date('2026-01-30T14:00:00Z').toLocaleString(undefined, {
month: '2-digit', month: '2-digit',
@@ -69,9 +69,17 @@ describe('QuotaTooltipContent', () => {
expect(screen.getByText('g1-pro-tier')).toBeInTheDocument(); expect(screen.getByText('g1-pro-tier')).toBeInTheDocument();
expect(screen.getByText('Credits')).toBeInTheDocument(); expect(screen.getByText('Credits')).toBeInTheDocument();
expect(screen.getByText('12')).toBeInTheDocument(); expect(screen.getByText('12')).toBeInTheDocument();
expect(screen.getByText('Gemini Flash Lite Series')).toBeInTheDocument(); expect(screen.getByText('Model quotas:')).toBeInTheDocument();
expect(screen.getByText('100 remaining')).toBeInTheDocument(); expect(screen.getByText('All buckets report Requests')).toBeInTheDocument();
expect(screen.getByText('82 remaining')).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(); expect(screen.getByText(expectedReset)).toBeInTheDocument();
}); });