mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-05 14:41:35 -07:00
refactor(cli): consolidate quota reset formatting and restore conservative defaults
This commit is contained in:
@@ -85,13 +85,7 @@ export const QuotaStatsInfo: React.FC<QuotaStatsInfoProps> = ({
|
||||
: `${displayPercentage.toFixed(0)}%`}
|
||||
</Text>
|
||||
{displayPercentage !== 0 && <Text> usage remaining</Text>}
|
||||
{displayResetTime &&
|
||||
`, ${(function (t) {
|
||||
const formatted = formatResetTime(t);
|
||||
return formatted === 'Resetting...' || formatted === '< 1m'
|
||||
? formatted
|
||||
: `resets in ${formatted}`;
|
||||
})(displayResetTime)}`}
|
||||
{displayResetTime && `, ${formatResetTime(displayResetTime)}`}
|
||||
</Text>
|
||||
)}
|
||||
{showDetails && (
|
||||
|
||||
@@ -278,7 +278,13 @@ const ModelUsageTable: React.FC<{
|
||||
</Text>
|
||||
</Box>
|
||||
<Box width={tokenColumnWidth} justifyContent="flex-end">
|
||||
<Text color={theme.text.secondary}>{row.cachedTokens}</Text>
|
||||
<Text
|
||||
color={
|
||||
row.isActive ? theme.text.primary : theme.text.secondary
|
||||
}
|
||||
>
|
||||
{row.cachedTokens}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box width={tokenColumnWidth} justifyContent="flex-end">
|
||||
<Text
|
||||
@@ -294,19 +300,19 @@ const ModelUsageTable: React.FC<{
|
||||
{showQuotaColumn && (
|
||||
<Box flexGrow={1} justifyContent="flex-end">
|
||||
{row.bucket && row.bucket.remainingFraction != null && (
|
||||
<Text color={theme.text.secondary}>
|
||||
<Text
|
||||
color={
|
||||
row.isActive ? theme.text.primary : 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)}
|
||||
{formatResetTime(row.bucket.resetTime, {
|
||||
capitalize: true,
|
||||
})}
|
||||
)
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -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 = 50;
|
||||
export const QUOTA_THRESHOLD_MEDIUM = 20;
|
||||
export const QUOTA_THRESHOLD_HIGH = 20;
|
||||
export const QUOTA_THRESHOLD_MEDIUM = 5;
|
||||
|
||||
// --- Color Logic ---
|
||||
export const getStatusColor = (
|
||||
|
||||
@@ -97,17 +97,24 @@ export function stripReferenceContent(text: string): string {
|
||||
|
||||
return text.replace(pattern, '').trim();
|
||||
}
|
||||
const UNIX_SECONDS_THRESHOLD = 10_000_000_000;
|
||||
|
||||
export const formatResetTime = (resetTime: string): string => {
|
||||
export const formatResetTime = (
|
||||
resetTime: string,
|
||||
options: { capitalize?: boolean; includePrefix?: boolean } = {},
|
||||
): string => {
|
||||
const { capitalize = false, includePrefix = true } = options;
|
||||
let date = new Date(resetTime);
|
||||
|
||||
// If invalid, try parsing as a number (unix timestamp)
|
||||
if (isNaN(date.getTime())) {
|
||||
const timestamp = parseInt(resetTime, 10);
|
||||
if (!isNaN(timestamp)) {
|
||||
// Heuristic: If the timestamp is less than 10^12, it is likely in seconds
|
||||
// (as 10^12 ms is year 2001, while 10^12 s is far in the future).
|
||||
date = new Date(timestamp < 10000000000 ? timestamp * 1000 : timestamp);
|
||||
// Heuristic: If the timestamp is less than 10^10, it is likely in seconds
|
||||
// (as 10^10 ms is year 1970, while 10^10 s is year 2286).
|
||||
date = new Date(
|
||||
timestamp < UNIX_SECONDS_THRESHOLD ? timestamp * 1000 : timestamp,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,19 +130,34 @@ export const formatResetTime = (resetTime: string): string => {
|
||||
const minutes = totalMinutes % 60;
|
||||
|
||||
const fmt = (val: number, unit: 'hour' | 'minute') =>
|
||||
new Intl.NumberFormat('en', {
|
||||
style: 'unit',
|
||||
unit,
|
||||
unitDisplay: 'narrow',
|
||||
}).format(val);
|
||||
new Intl.RelativeTimeFormat('en', {
|
||||
style: 'narrow',
|
||||
numeric: 'always',
|
||||
})
|
||||
.formatToParts(val, unit)
|
||||
.filter(
|
||||
(p) =>
|
||||
p.type !== 'literal' || (p.value !== 'in ' && p.value !== ' ago'),
|
||||
)
|
||||
.map((p) => p.value)
|
||||
.join('')
|
||||
.trim();
|
||||
|
||||
let timeStr = '';
|
||||
if (hours > 0 && minutes > 0) {
|
||||
return `${fmt(hours, 'hour')} ${fmt(minutes, 'minute')}`;
|
||||
timeStr = `${fmt(hours, 'hour')} ${fmt(minutes, 'minute')}`;
|
||||
} else if (hours > 0) {
|
||||
return fmt(hours, 'hour');
|
||||
timeStr = fmt(hours, 'hour');
|
||||
} else if (minutes > 0) {
|
||||
return fmt(minutes, 'minute');
|
||||
timeStr = fmt(minutes, 'minute');
|
||||
} else {
|
||||
return '< 1m';
|
||||
timeStr = '< 1m';
|
||||
}
|
||||
|
||||
if (timeStr === '< 1m' || !includePrefix) {
|
||||
return timeStr;
|
||||
}
|
||||
|
||||
const prefix = capitalize ? 'Resets in ' : 'resets in ';
|
||||
return `${prefix}${timeStr}`;
|
||||
};
|
||||
|
||||
@@ -68,7 +68,10 @@ import { ideContextStore } from '../ide/ideContext.js';
|
||||
import { WriteTodosTool } from '../tools/write-todos.js';
|
||||
import type { FileSystemService } from '../services/fileSystemService.js';
|
||||
import { StandardFileSystemService } from '../services/fileSystemService.js';
|
||||
import { logRipgrepFallback, logFlashFallback } from '../telemetry/loggers.js';
|
||||
import { logRipgrepFallback, logFlashFallback ,
|
||||
logApprovalModeSwitch,
|
||||
logApprovalModeDuration,
|
||||
} from '../telemetry/loggers.js';
|
||||
import {
|
||||
RipgrepFallbackEvent,
|
||||
FlashFallbackEvent,
|
||||
@@ -103,9 +106,7 @@ import type { EventEmitter } from 'node:events';
|
||||
import { PolicyEngine } from '../policy/policy-engine.js';
|
||||
import { ApprovalMode, type PolicyEngineConfig } from '../policy/types.js';
|
||||
import { HookSystem } from '../hooks/index.js';
|
||||
import type { UserTierId } from '../code_assist/types.js';
|
||||
import type { RetrieveUserQuotaResponse } from '../code_assist/types.js';
|
||||
import type { AdminControlsSettings } from '../code_assist/types.js';
|
||||
import type { UserTierId , RetrieveUserQuotaResponse , AdminControlsSettings } from '../code_assist/types.js';
|
||||
import type { HierarchicalMemory } from './memory.js';
|
||||
import { getCodeAssistServer } from '../code_assist/codeAssist.js';
|
||||
import type { Experiments } from '../code_assist/experiments/experiments.js';
|
||||
@@ -119,10 +120,6 @@ import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { SkillManager, type SkillDefinition } from '../skills/skillManager.js';
|
||||
import { startupProfiler } from '../telemetry/startupProfiler.js';
|
||||
import type { AgentDefinition } from '../agents/types.js';
|
||||
import {
|
||||
logApprovalModeSwitch,
|
||||
logApprovalModeDuration,
|
||||
} from '../telemetry/loggers.js';
|
||||
import { fetchAdminControls } from '../code_assist/admin/admin_controls.js';
|
||||
import { isSubpath } from '../utils/paths.js';
|
||||
import { UserHintService } from './userHintService.js';
|
||||
@@ -1335,12 +1332,11 @@ export class Config {
|
||||
const flashQuota = this.modelQuotas.get(flashModel);
|
||||
|
||||
if (proQuota || flashQuota) {
|
||||
// For reset time, take the one that is nearest in the future (soonest reset)
|
||||
// For reset time, take the one that is furthest in the future (most conservative)
|
||||
const resetTime = [proQuota?.resetTime, flashQuota?.resetTime]
|
||||
.filter((t): t is string => !!t)
|
||||
.map((t) => new Date(t))
|
||||
.sort((a, b) => a.getTime() - b.getTime())[0]
|
||||
?.toISOString();
|
||||
.sort()
|
||||
.reverse()[0];
|
||||
|
||||
return {
|
||||
remaining: (proQuota?.remaining ?? 0) + (flashQuota?.remaining ?? 0),
|
||||
|
||||
Reference in New Issue
Block a user