diff --git a/ui/src/components/shared/quota-tooltip-content.tsx b/ui/src/components/shared/quota-tooltip-content.tsx
index 4554bb22..25edae1b 100644
--- a/ui/src/components/shared/quota-tooltip-content.tsx
+++ b/ui/src/components/shared/quota-tooltip-content.tsx
@@ -37,6 +37,23 @@ function formatPlanLabel(planType: string | null | undefined): string | null {
return normalized.length > 0 ? normalized.join(' ') : planType;
}
+function formatAbsoluteResetTime(resetTime: string | null): string | null {
+ if (!resetTime) return null;
+ try {
+ const parsed = new Date(resetTime);
+ if (Number.isNaN(parsed.getTime())) return null;
+ return parsed.toLocaleString(undefined, {
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ hour12: false,
+ });
+ } catch {
+ return null;
+ }
+}
+
function getClaudeWindowDisplayLabel(rateLimitType: string, fallback: string): string {
switch (rateLimitType) {
case 'five_hour':
@@ -235,19 +252,45 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
// Gemini provider tooltip
if (isGeminiQuotaResult(quota)) {
+ const hasBucketResetTime = quota.buckets.some((bucket) => !!bucket.resetTime);
+
return (
+ {quota.tierLabel && (
+
+ Tier
+ {quota.tierLabel}
+
+ )}
+ {quota.creditBalance !== null && quota.creditBalance !== undefined && (
+
+ Credits
+ {quota.creditBalance.toLocaleString()}
+
+ )}
Buckets:
{quota.buckets.map((b) => (
-
-
- {b.label}
- {b.tokenType ? ` (${b.tokenType})` : ''}
-
-
{b.remainingPercent}%
+
+
+
+ {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) ?? ''}
+
+ )}
))}
-
+ {!hasBucketResetTime &&
}
);
}
diff --git a/ui/src/lib/api-client.ts b/ui/src/lib/api-client.ts
index b092142a..24876be9 100644
--- a/ui/src/lib/api-client.ts
+++ b/ui/src/lib/api-client.ts
@@ -611,6 +611,8 @@ export interface GeminiCliBucket {
remainingFraction: number;
/** Remaining quota as percentage (0-100) */
remainingPercent: number;
+ /** Remaining quota count when the upstream API provides it */
+ remainingAmount?: number | null;
/** ISO timestamp when quota resets, null if unknown */
resetTime: string | null;
/** Model IDs in this bucket */
@@ -625,6 +627,12 @@ export interface GeminiCliQuotaResult {
buckets: GeminiCliBucket[];
/** GCP project ID for this account */
projectId: string | null;
+ /** Human-readable Gemini tier label when available */
+ tierLabel?: string | null;
+ /** Stable Gemini tier identifier when available */
+ tierId?: string | null;
+ /** Available Google One AI credits when reported by the API */
+ creditBalance?: number | null;
/** Timestamp of fetch */
lastUpdated: number;
/** Upstream HTTP status when available */
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
new file mode 100644
index 00000000..73ab0923
--- /dev/null
+++ b/ui/tests/unit/ui/components/shared/quota-tooltip-content.test.tsx
@@ -0,0 +1,93 @@
+import { render, screen } from '@tests/setup/test-utils';
+import { describe, expect, it, vi } from 'vitest';
+import { QuotaTooltipContent } from '@/components/shared/quota-tooltip-content';
+import type { GeminiCliQuotaResult } from '@/lib/api-client';
+
+function createGeminiQuotaResult(
+ overrides: Partial
= {}
+): GeminiCliQuotaResult {
+ return {
+ success: true,
+ buckets: [
+ {
+ id: 'gemini-flash-lite-series::combined',
+ label: 'Gemini Flash Lite Series',
+ tokenType: null,
+ remainingFraction: 1,
+ remainingPercent: 100,
+ remainingAmount: 100,
+ resetTime: '2026-01-30T09:00:00Z',
+ modelIds: ['gemini-2.5-flash-lite'],
+ },
+ {
+ id: 'gemini-flash-series::combined',
+ label: 'Gemini Flash Series',
+ tokenType: null,
+ remainingFraction: 0.82,
+ remainingPercent: 82,
+ remainingAmount: 82,
+ resetTime: '2026-01-30T14:00:00Z',
+ modelIds: ['gemini-3-flash-preview'],
+ },
+ ],
+ projectId: 'cloudaicompanion-test-123',
+ tierLabel: 'Pro',
+ tierId: 'g1-pro-tier',
+ creditBalance: 12,
+ lastUpdated: Date.now(),
+ ...overrides,
+ };
+}
+
+describe('QuotaTooltipContent', () => {
+ it('renders Gemini tier, credits, remaining amount, and bucket reset timestamps', () => {
+ const quota = createGeminiQuotaResult();
+ const expectedReset = new Date('2026-01-30T14:00:00Z').toLocaleString(undefined, {
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ hour12: false,
+ });
+
+ render();
+
+ expect(screen.getByText('Tier')).toBeInTheDocument();
+ expect(screen.getByText('Pro')).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(expectedReset)).toBeInTheDocument();
+ });
+
+ it('falls back to the shared reset indicator when Gemini buckets omit reset timestamps', () => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date('2026-01-29T00:00:00Z'));
+
+ const quota = createGeminiQuotaResult({
+ buckets: [
+ {
+ id: 'gemini-flash-series::combined',
+ label: 'Gemini Flash Series',
+ tokenType: null,
+ remainingFraction: 0.75,
+ remainingPercent: 75,
+ remainingAmount: 75,
+ resetTime: null,
+ modelIds: ['gemini-3-flash-preview'],
+ },
+ ],
+ creditBalance: null,
+ tierLabel: null,
+ tierId: null,
+ });
+
+ render();
+
+ expect(screen.getByText(/Resets/i)).toBeInTheDocument();
+
+ vi.useRealTimers();
+ });
+});