mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-08 08:01:52 -07:00
feat(cli): improve quota display layout and fallback logic in /stats
This commit is contained in:
@@ -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 && (
|
||||
|
||||
@@ -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. │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
Reference in New Issue
Block a user