feat(cli): improve quota display layout and fallback logic in /stats

This commit is contained in:
Spencer
2026-02-20 21:54:13 +00:00
parent 62841eea35
commit aa68a97482
6 changed files with 159 additions and 194 deletions
@@ -4,12 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { render } from '../../test-utils/render.js';
import { renderWithProviders } from '../../test-utils/render.js';
import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest';
import { ModelStatsDisplay } from './ModelStatsDisplay.js';
import * as SessionContext from '../contexts/SessionContext.js';
import * as SettingsContext from '../contexts/SettingsContext.js';
import type { LoadedSettings } from '../../config/settings.js';
import type { SessionMetrics } from '../contexts/SessionContext.js';
import { ToolCallDecision, LlmRole } from '@google/gemini-cli-core';
@@ -22,16 +20,7 @@ vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
};
});
vi.mock('../contexts/SettingsContext.js', async (importOriginal) => {
const actual = await importOriginal<typeof SettingsContext>();
return {
...actual,
useSettings: vi.fn(),
};
});
const useSessionStatsMock = vi.mocked(SessionContext.useSessionStats);
const useSettingsMock = vi.mocked(SettingsContext.useSettings);
const renderWithMockedStats = async (
metrics: SessionMetrics,
@@ -51,17 +40,9 @@ const renderWithMockedStats = async (
startNewPrompt: vi.fn(),
});
useSettingsMock.mockReturnValue({
merged: {
ui: {
showUserIdentity: true,
},
},
} as unknown as LoadedSettings);
const result = render(
const result = renderWithProviders(
<ModelStatsDisplay currentModel={currentModel} />,
width,
{ width },
);
await result.waitUntilReady();
return result;
@@ -474,14 +455,6 @@ describe('<ModelStatsDisplay />', () => {
});
it('should render user identity information when provided', async () => {
useSettingsMock.mockReturnValue({
merged: {
ui: {
showUserIdentity: true,
},
},
} as unknown as LoadedSettings);
useSessionStatsMock.mockReturnValue({
stats: {
sessionId: 'test-session',
@@ -528,7 +501,7 @@ describe('<ModelStatsDisplay />', () => {
startNewPrompt: vi.fn(),
});
const { lastFrame, waitUntilReady, unmount } = render(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ModelStatsDisplay
selectedAuthType="oauth"
userEmail="test@example.com"
@@ -23,9 +23,12 @@ import {
getDisplayString,
isAutoModel,
LlmRole,
AuthType,
type RetrieveUserQuotaResponse,
} from '@google/gemini-cli-core';
import type { QuotaStats } from '../types.js';
import { QuotaStatsInfo } from './QuotaStatsInfo.js';
import { useConfig } from '../contexts/ConfigContext.js';
interface StatRowData {
metric: string;
@@ -43,6 +46,7 @@ interface ModelStatsDisplayProps {
tier?: string;
currentModel?: string;
quotaStats?: QuotaStats;
quotas?: RetrieveUserQuotaResponse;
}
export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
@@ -51,8 +55,14 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
tier,
currentModel,
quotaStats,
quotas,
}) => {
const { stats } = useSessionStats();
const config = useConfig();
const useGemini3_1 = config.getGemini31LaunchedSync?.() ?? false;
const useCustomToolModel =
useGemini3_1 &&
config.getContentGeneratorConfig().authType === AuthType.USE_GEMINI;
const pooledRemaining = quotaStats?.remaining;
const pooledLimit = quotaStats?.limit;
@@ -343,6 +353,9 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
remaining={pooledRemaining}
limit={pooledLimit}
resetTime={pooledResetTime}
quotas={quotas}
useGemini3_1={useGemini3_1}
useCustomToolModel={useCustomToolModel}
/>
{(showUserIdentity || isAuto) && <Box height={1} />}
@@ -14,11 +14,19 @@ import {
QUOTA_THRESHOLD_MEDIUM,
} from '../utils/displayUtils.js';
import {
type RetrieveUserQuotaResponse,
isActiveModel,
} from '@google/gemini-cli-core';
interface QuotaStatsInfoProps {
remaining: number | undefined;
limit: number | undefined;
resetTime?: string;
showDetails?: boolean;
quotas?: RetrieveUserQuotaResponse;
useGemini3_1?: boolean;
useCustomToolModel?: boolean;
}
export const QuotaStatsInfo: React.FC<QuotaStatsInfoProps> = ({
@@ -26,23 +34,42 @@ export const QuotaStatsInfo: React.FC<QuotaStatsInfoProps> = ({
limit,
resetTime,
showDetails = true,
quotas,
useGemini3_1 = false,
useCustomToolModel = false,
}) => {
const hasData =
(remaining !== undefined && remaining !== null) ||
(limit !== undefined && limit !== null && limit > 0);
if (!hasData && !showDetails) {
return null;
}
const percentage =
let displayPercentage =
limit && limit > 0 && remaining !== undefined && remaining !== null
? (remaining / limit) * 100
: undefined;
let displayResetTime = resetTime;
// Fallback to individual bucket if pooled data is missing
if (displayPercentage === undefined && quotas?.buckets) {
const activeBuckets = quotas.buckets.filter(
(b) =>
b.modelId &&
isActiveModel(b.modelId, useGemini3_1, useCustomToolModel) &&
b.remainingFraction !== undefined,
);
if (activeBuckets.length > 0) {
// Use the most restrictive bucket as representative
const representative = activeBuckets.reduce((prev, curr) =>
prev.remainingFraction! < curr.remainingFraction! ? prev : curr,
);
displayPercentage = representative.remainingFraction! * 100;
displayResetTime = representative.resetTime;
}
}
if (displayPercentage === undefined && !showDetails) {
return null;
}
const color =
percentage !== undefined
? getStatusColor(percentage, {
displayPercentage !== undefined
? getStatusColor(displayPercentage, {
green: QUOTA_THRESHOLD_HIGH,
yellow: QUOTA_THRESHOLD_MEDIUM,
})
@@ -50,23 +77,21 @@ export const QuotaStatsInfo: React.FC<QuotaStatsInfoProps> = ({
return (
<Box flexDirection="column" marginTop={0} marginBottom={0}>
{hasData && (
{displayPercentage !== undefined && (
<Text color={color}>
<Text bold>
{remaining === 0
{displayPercentage === 0
? `Limit reached`
: percentage !== undefined
? `${percentage.toFixed(0)}%`
: 'Limit reached'}
: `${displayPercentage.toFixed(0)}%`}
</Text>
{remaining !== 0 && <Text> usage remaining</Text>}
{resetTime &&
{displayPercentage !== 0 && <Text> usage remaining</Text>}
{displayResetTime &&
`, ${(function (t) {
const formatted = formatResetTime(t);
return formatted === 'Resetting...' || formatted === '< 1m'
? formatted
: `resets in ${formatted}`;
})(resetTime)}`}
})(displayResetTime)}`}
</Text>
)}
{showDetails && (
+50 -99
View File
@@ -88,9 +88,7 @@ const buildModelRows = (
useCustomToolModel = false,
) => {
const getBaseModelName = (name: string) => name.replace('-001', '');
const usedModelNames = new Set(
Object.keys(models).map(getBaseModelName).map(getDisplayString),
);
const usedModelNames = new Set(Object.keys(models).map(getBaseModelName));
// 1. Models with active usage
const activeRows = Object.entries(models).map(([name, metrics]) => {
@@ -116,7 +114,7 @@ const buildModelRows = (
(b) =>
b.modelId &&
isActiveModel(b.modelId, useGemini3_1, useCustomToolModel) &&
!usedModelNames.has(getDisplayString(b.modelId)),
!usedModelNames.has(b.modelId),
)
.map((bucket) => ({
key: bucket.modelId!,
@@ -164,34 +162,22 @@ const ModelUsageTable: React.FC<{
const showQuotaColumn = !!quotas;
const nameWidth = 22;
const requestsWidth = 7;
const uncachedWidth = 15;
const cachedWidth = 14;
const outputTokensWidth = 15;
const usageLimitWidth = showQuotaColumn ? 40 : 0;
const modelColumnWidth = 30;
const requestsColumnWidth = 8;
const tokenColumnWidth = 15;
const cacheEfficiencyColor = getStatusColor(cacheEfficiency, {
green: CACHE_EFFICIENCY_HIGH,
yellow: CACHE_EFFICIENCY_MEDIUM,
});
const totalWidth =
nameWidth +
requestsWidth +
(showQuotaColumn
? usageLimitWidth
: uncachedWidth + cachedWidth + outputTokensWidth);
return (
<Box flexDirection="column" marginBottom={1}>
{/* Header */}
<Box alignItems="flex-end">
<Box width={nameWidth}>
<Text bold color={theme.text.primary} wrap="truncate-end">
{modelUsageTitle}
</Text>
</Box>
<Box>
<Text bold color={theme.text.primary} wrap="truncate-end">
{modelUsageTitle}
</Text>
</Box>
<Box flexDirection="column" marginTop={0} marginBottom={1}>
@@ -200,6 +186,9 @@ const ModelUsageTable: React.FC<{
limit={pooledLimit}
resetTime={pooledResetTime}
showDetails={true}
quotas={quotas}
useGemini3_1={useGemini3_1}
useCustomToolModel={useCustomToolModel}
/>
{isAuto && (
<Text color={theme.text.primary}>
@@ -208,18 +197,13 @@ const ModelUsageTable: React.FC<{
)}
</Box>
<Box alignItems="flex-end">
<Box width={nameWidth}>
<Box>
<Box width={modelColumnWidth}>
<Text bold color={theme.text.primary}>
Model
</Text>
</Box>
<Box
width={requestsWidth}
flexDirection="column"
alignItems="flex-end"
flexShrink={0}
>
<Box width={requestsColumnWidth} justifyContent="flex-end">
<Text bold color={theme.text.primary}>
Reqs
</Text>
@@ -227,32 +211,17 @@ const ModelUsageTable: React.FC<{
{!showQuotaColumn && (
<>
<Box
width={uncachedWidth}
flexDirection="column"
alignItems="flex-end"
flexShrink={0}
>
<Box width={tokenColumnWidth} justifyContent="flex-end">
<Text bold color={theme.text.primary}>
Input Tokens
</Text>
</Box>
<Box
width={cachedWidth}
flexDirection="column"
alignItems="flex-end"
flexShrink={0}
>
<Box width={tokenColumnWidth} justifyContent="flex-end">
<Text bold color={theme.text.primary}>
Cache Reads
</Text>
</Box>
<Box
width={outputTokensWidth}
flexDirection="column"
alignItems="flex-end"
flexShrink={0}
>
<Box width={tokenColumnWidth} justifyContent="flex-end">
<Text bold color={theme.text.primary}>
Output Tokens
</Text>
@@ -260,7 +229,7 @@ const ModelUsageTable: React.FC<{
</>
)}
{showQuotaColumn && (
<Box width={usageLimitWidth} justifyContent="flex-end">
<Box flexGrow={1} justifyContent="flex-end">
<Text bold color={theme.text.primary}>
Usage left
</Text>
@@ -270,19 +239,19 @@ const ModelUsageTable: React.FC<{
{/* Divider */}
<Box
borderStyle="round"
borderStyle="single"
borderBottom={true}
borderTop={false}
borderLeft={false}
borderRight={false}
borderColor={theme.border.default}
width={totalWidth}
></Box>
marginBottom={0}
/>
{rows.length > 0 ? (
rows.map((row) => (
<Box key={row.key}>
<Box width={nameWidth}>
<Box width={modelColumnWidth}>
<Text
color={row.isActive ? theme.text.primary : theme.text.secondary}
wrap="truncate-end"
@@ -290,12 +259,7 @@ const ModelUsageTable: React.FC<{
{row.modelName}
</Text>
</Box>
<Box
width={requestsWidth}
flexDirection="column"
alignItems="flex-end"
flexShrink={0}
>
<Box width={requestsColumnWidth} justifyContent="flex-end">
<Text
color={row.isActive ? theme.text.primary : theme.text.secondary}
>
@@ -304,12 +268,7 @@ const ModelUsageTable: React.FC<{
</Box>
{!showQuotaColumn && (
<>
<Box
width={uncachedWidth}
flexDirection="column"
alignItems="flex-end"
flexShrink={0}
>
<Box width={tokenColumnWidth} justifyContent="flex-end">
<Text
color={
row.isActive ? theme.text.primary : theme.text.secondary
@@ -318,20 +277,10 @@ const ModelUsageTable: React.FC<{
{row.inputTokens}
</Text>
</Box>
<Box
width={cachedWidth}
flexDirection="column"
alignItems="flex-end"
flexShrink={0}
>
<Box width={tokenColumnWidth} justifyContent="flex-end">
<Text color={theme.text.secondary}>{row.cachedTokens}</Text>
</Box>
<Box
width={outputTokensWidth}
flexDirection="column"
alignItems="flex-end"
flexShrink={0}
>
<Box width={tokenColumnWidth} justifyContent="flex-end">
<Text
color={
row.isActive ? theme.text.primary : theme.text.secondary
@@ -342,27 +291,29 @@ const ModelUsageTable: React.FC<{
</Box>
</>
)}
<Box width={usageLimitWidth} justifyContent="flex-end">
{row.bucket && row.bucket.remainingFraction != null && (
<Text color={theme.text.secondary}>
{(row.bucket.remainingFraction * 100).toFixed(1)}%
{row.bucket.resetTime && (
<Text color={theme.text.secondary}>
{' '}
(
{(function (t) {
const formatted = formatResetTime(t);
return formatted === 'Resetting...' ||
formatted === '< 1m'
? formatted
: `Resets in ${formatted}`;
})(row.bucket.resetTime)}
)
</Text>
)}
</Text>
)}
</Box>
{showQuotaColumn && (
<Box flexGrow={1} justifyContent="flex-end">
{row.bucket && row.bucket.remainingFraction != null && (
<Text color={theme.text.secondary}>
{(row.bucket.remainingFraction * 100).toFixed(1)}%
{row.bucket.resetTime && (
<Text color={theme.text.secondary}>
{' '}
(
{(function (t) {
const formatted = formatResetTime(t);
return formatted === 'Resetting...' ||
formatted === '< 1m'
? formatted
: `Resets in ${formatted}`;
})(row.bucket.resetTime)}
)
</Text>
)}
</Text>
)}
</Box>
)}
</Box>
))
) : (
@@ -21,8 +21,8 @@ exports[`<StatsDisplay /> > Code Changes Display > displays Code Changes when li
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ No model usage recorded. │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -49,8 +49,8 @@ exports[`<StatsDisplay /> > Code Changes Display > hides Code Changes when no li
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ No model usage recorded. │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -77,8 +77,8 @@ exports[`<StatsDisplay /> > Conditional Color Tests > renders success rate in gr
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ No model usage recorded. │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -105,8 +105,8 @@ exports[`<StatsDisplay /> > Conditional Color Tests > renders success rate in re
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ No model usage recorded. │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -133,8 +133,8 @@ exports[`<StatsDisplay /> > Conditional Color Tests > renders success rate in ye
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ No model usage recorded. │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -161,9 +161,9 @@ exports[`<StatsDisplay /> > Conditional Rendering Tests > hides Efficiency secti
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ gemini-2.5-pro 1 100 0 100
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ gemini-2.5-pro 1 100 0 100 │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
@@ -189,8 +189,8 @@ exports[`<StatsDisplay /> > Conditional Rendering Tests > hides User Agreement w
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ No model usage recorded. │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -214,12 +214,13 @@ exports[`<StatsDisplay /> > Quota Display > renders model table quota percentage
│ » Tool Time: 0s (0.0%) │
│ │
│ Model Usage │
│ 50% usage remaining │
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Usage left
│ ─────────────────────────────────────────────────────────────────────
│ gemini-2.5-flash - 50.0%
│ Model Reqs Usage left │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ gemini-2.5-flash - 50.0% │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
@@ -247,8 +248,8 @@ exports[`<StatsDisplay /> > Quota Display > renders pooled quota even when no mo
│ /auth to upgrade or switch to API key. │
│ For a full token breakdown, run \`/stats model\`. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ No model usage recorded. │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -277,10 +278,10 @@ exports[`<StatsDisplay /> > Quota Display > renders pooled quota information for
│ /auth to upgrade or switch to API key. │
│ For a full token breakdown, run \`/stats model\`. │
│ │
│ Model Reqs Usage left
│ ─────────────────────────────────────────────────────────────────────
│ gemini-2.5-pro - 10.0%
│ gemini-2.5-flash - 70.0%
│ Model Reqs Usage left │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ gemini-2.5-pro - 10.0% │
│ gemini-2.5-flash - 70.0% │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
@@ -303,12 +304,13 @@ exports[`<StatsDisplay /> > Quota Display > renders quota information for unused
│ » Tool Time: 0s (0.0%) │
│ │
│ Model Usage │
│ 50% usage remaining, resets in 2h │
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Usage left
│ ─────────────────────────────────────────────────────────────────────
│ gemini-2.5-flash - 50.0% (Resets in 2h)
│ Model Reqs Usage left │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ gemini-2.5-flash - 50.0% (Resets in 2h) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
@@ -331,12 +333,13 @@ exports[`<StatsDisplay /> > Quota Display > renders quota information when quota
│ » Tool Time: 0s (0.0%) │
│ │
│ Model Usage │
│ 75% usage remaining, resets in 1h 30m │
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Usage left
│ ─────────────────────────────────────────────────────────────────────
│ gemini-2.5-pro 1 75.0% (Resets in 1h 30m)
│ Model Reqs Usage left │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ gemini-2.5-pro 1 75.0% (Resets in 1h 30m) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
@@ -362,8 +365,8 @@ exports[`<StatsDisplay /> > Title Rendering > renders the custom title when a ti
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ No model usage recorded. │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -390,8 +393,8 @@ exports[`<StatsDisplay /> > Title Rendering > renders the default title when no
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ No model usage recorded. │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -418,10 +421,10 @@ exports[`<StatsDisplay /> > renders a table with two models correctly 1`] = `
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ gemini-2.5-pro 3 500 500 2,000
│ gemini-2.5-flash 5 15,000 10,000 15,000
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ gemini-2.5-pro 3 500 500 2,000 │
│ gemini-2.5-flash 5 15,000 10,000 15,000 │
│ │
│ Savings Highlight: 10,500 (40.4%) of input tokens were served from the cache, reducing costs. │
│ │
@@ -450,9 +453,9 @@ exports[`<StatsDisplay /> > renders all sections when all data is present 1`] =
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ gemini-2.5-pro 1 50 50 100
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ gemini-2.5-pro 1 50 50 100 │
│ │
│ Savings Highlight: 50 (50.0%) of input tokens were served from the cache, reducing costs. │
│ │
@@ -480,8 +483,8 @@ exports[`<StatsDisplay /> > renders only the Performance section in its zero sta
│ Usage limits span all sessions and reset daily. │
│ /auth to upgrade or switch to API key. │
│ │
│ Model Reqs Input Tokens Cache Reads Output Tokens
│ ─────────────────────────────────────────────────────────────────────────
│ Model Reqs Input Tokens Cache Reads Output Tokens │
│ ──────────────────────────────────────────────────────────────────────────────────────────────
│ No model usage recorded. │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
+2 -2
View File
@@ -16,8 +16,8 @@ export const USER_AGREEMENT_RATE_MEDIUM = 45;
export const CACHE_EFFICIENCY_HIGH = 40;
export const CACHE_EFFICIENCY_MEDIUM = 15;
export const QUOTA_THRESHOLD_HIGH = 20;
export const QUOTA_THRESHOLD_MEDIUM = 5;
export const QUOTA_THRESHOLD_HIGH = 50;
export const QUOTA_THRESHOLD_MEDIUM = 20;
// --- Color Logic ---
export const getStatusColor = (