mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 20:14:34 +00:00
fix(ui): clarify Gemini quota tooltip labels
This commit is contained in:
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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user