feat(quota): add subscription management and localization for Antigravity plans

This commit is contained in:
LTbinglingfeng
2026-06-17 23:01:52 +08:00
parent c74fa6d400
commit 069eaf21b7
11 changed files with 243 additions and 164 deletions
+166 -64
View File
@@ -7,6 +7,7 @@ import type { ReactNode } from 'react';
import type { TFunction } from 'i18next';
import type {
AntigravityQuotaGroup,
AntigravityQuotaSubscription,
AntigravityQuotaSummaryPayload,
AntigravityQuotaState,
AuthFileItem,
@@ -32,7 +33,13 @@ import type {
XaiBillingSummary,
XaiQuotaState,
} from '@/types';
import { apiCallApi, authFilesApi, getApiCallErrorMessage } from '@/services/api';
import {
antigravitySubscriptionApi,
apiCallApi,
authFilesApi,
getApiCallErrorMessage,
type AntigravitySubscriptionSummary,
} from '@/services/api';
import { useQuotaStore } from '@/stores';
import {
ANTIGRAVITY_QUOTA_URLS,
@@ -95,6 +102,7 @@ type QuotaType = 'antigravity' | 'claude' | 'codex' | 'gemini-cli' | 'kimi' | 'x
type AntigravityQuotaData = {
groups: AntigravityQuotaGroup[];
subscription: AntigravityQuotaSubscription | null;
serverTimeOffsetMs: number | null;
};
@@ -229,6 +237,10 @@ const fetchAntigravityQuota = async (
throw new Error(t('antigravity_quota.missing_project_id'));
}
const requestBody = JSON.stringify({ project: projectId });
const subscriptionPromise = antigravitySubscriptionApi
.get(authIndex)
.then(toAntigravityQuotaSubscription)
.catch(() => null);
let lastError = '';
let lastStatus: number | undefined;
@@ -271,6 +283,7 @@ const fetchAntigravityQuota = async (
return {
groups,
subscription: await subscriptionPromise,
serverTimeOffsetMs: resolveResponseServerTimeOffsetMs(result.header),
};
} catch (err: unknown) {
@@ -286,16 +299,24 @@ const fetchAntigravityQuota = async (
}
if (hadSuccess) {
return { groups: [], serverTimeOffsetMs: null };
return { groups: [], subscription: await subscriptionPromise, serverTimeOffsetMs: null };
}
throw createStatusError(lastError || t('common.unknown_error'), priorityStatus ?? lastStatus);
};
const buildCodexQuotaWindows = (
payload: CodexUsagePayload,
t: TFunction
): CodexQuotaWindow[] => {
const toAntigravityQuotaSubscription = (
summary: AntigravitySubscriptionSummary | null
): AntigravityQuotaSubscription | null => {
if (!summary) return null;
return {
plan: summary.plan,
tierName: summary.tierName,
tierId: summary.tierId,
};
};
const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): CodexQuotaWindow[] => {
const FIVE_HOUR_SECONDS = 18000;
const WEEK_SECONDS = 604800;
const MIN_MONTH_SECONDS = 28 * 24 * 60 * 60;
@@ -912,6 +933,62 @@ const formatAntigravityResetLabel = (
});
};
const ANTIGRAVITY_GROUP_LABEL_KEYS = new Map<string, string>([
['gemini models', 'group_gemini_models'],
['claude and gpt models', 'group_claude_gpt_models'],
]);
const ANTIGRAVITY_BUCKET_LABEL_KEYS = new Map<string, string>([
['weekly limit', 'weekly_limit'],
['daily limit', 'daily_limit'],
['5 hour limit', 'five_hour_limit'],
['5-hour limit', 'five_hour_limit'],
['five hour limit', 'five_hour_limit'],
['monthly limit', 'monthly_limit'],
]);
const normalizeAntigravityQuotaText = (value: string): string =>
value.trim().toLowerCase().replace(/\s+/g, ' ');
const translateAntigravityQuotaLabel = (
value: string,
keys: Map<string, string>,
t: TFunction
): string => {
const key = keys.get(normalizeAntigravityQuotaText(value));
return key ? t(`antigravity_quota.${key}`) : value;
};
const translateAntigravityQuotaDescription = (
value: string | undefined,
t: TFunction
): string | undefined => {
if (!value) return undefined;
const modelsMatch = value.match(/^models within this group:\s*(.+)$/i);
if (modelsMatch) {
return t('antigravity_quota.group_models_description', {
models: modelsMatch[1].trim(),
});
}
return value;
};
const getAntigravityPlanLabel = (
subscription: AntigravityQuotaSubscription | null | undefined,
t: TFunction
): string | null => {
if (!subscription) return null;
if (subscription.plan === 'free') return t('antigravity_subscription.plan_free');
if (subscription.plan === 'pro') return t('antigravity_subscription.plan_pro');
if (subscription.plan === 'ultra') return t('antigravity_subscription.plan_ultra');
if (subscription.plan === 'ultra-lite') return t('antigravity_subscription.plan_ultra_lite');
return (
subscription.tierName ||
subscription.tierId ||
(subscription.plan === 'unknown' ? t('antigravity_subscription.plan_unknown') : null)
);
};
const renderAntigravityItems = (
quota: AntigravityQuotaState,
t: TFunction,
@@ -920,26 +997,61 @@ const renderAntigravityItems = (
const { styles: styleMap, QuotaProgressBar } = helpers;
const { createElement: h, Fragment } = React;
const groups = quota.groups ?? [];
const nodes: ReactNode[] = [];
const planLabel = getAntigravityPlanLabel(quota.subscription, t);
const normalizedPlan = quota.subscription?.plan?.toLowerCase() ?? '';
const isPremiumPlan = normalizedPlan === 'ultra' || normalizedPlan === 'ultra-lite';
if (planLabel) {
nodes.push(
h(
'div',
{ key: 'plan', className: styleMap.codexPlan },
h(
'span',
{ className: styleMap.codexPlanItem },
h('span', { className: styleMap.codexPlanLabel }, t('antigravity_quota.plan_label')),
h(
'span',
{ className: isPremiumPlan ? styleMap.premiumPlanValue : styleMap.codexPlanValue },
planLabel
)
)
)
);
}
if (groups.length === 0) {
return h('div', { className: styleMap.quotaMessage }, t('antigravity_quota.empty_models'));
nodes.push(
h(
'div',
{ key: 'empty', className: styleMap.quotaMessage },
t('antigravity_quota.empty_models')
)
);
return h(Fragment, null, ...nodes);
}
const nowMs = Date.now() + (quota.serverTimeOffsetMs ?? 0);
return h(
Fragment,
null,
...groups.map((group) =>
h(
nodes.push(
...groups.map((group) => {
const groupLabel = translateAntigravityQuotaLabel(
group.label,
ANTIGRAVITY_GROUP_LABEL_KEYS,
t
);
const groupDescription = translateAntigravityQuotaDescription(group.description, t);
return h(
'div',
{ key: group.id, className: styleMap.antigravityQuotaGroup },
h(
'div',
{ className: styleMap.antigravityQuotaGroupHeader },
h('span', { className: styleMap.antigravityQuotaGroupTitle }, group.label),
group.description
? h('span', { className: styleMap.antigravityQuotaGroupDescription }, group.description)
h('span', { className: styleMap.antigravityQuotaGroupTitle }, groupLabel),
groupDescription
? h('span', { className: styleMap.antigravityQuotaGroupDescription }, groupDescription)
: null
),
...group.buckets.map((bucket) => {
@@ -952,6 +1064,12 @@ const renderAntigravityItems = (
percent: Math.round(percent),
});
const resetLabel = formatAntigravityResetLabel(bucket.resetTime, t, nowMs);
const bucketLabel = translateAntigravityQuotaLabel(
bucket.label,
ANTIGRAVITY_BUCKET_LABEL_KEYS,
t
);
const bucketDescription = translateAntigravityQuotaDescription(bucket.description, t);
return h(
'div',
@@ -959,11 +1077,7 @@ const renderAntigravityItems = (
h(
'div',
{ className: styleMap.quotaRowHeader },
h(
'span',
{ className: styleMap.quotaModel, title: bucket.description },
bucket.label
),
h('span', { className: styleMap.quotaModel, title: bucketDescription }, bucketLabel),
h(
'div',
{ className: styleMap.quotaMeta },
@@ -978,9 +1092,11 @@ const renderAntigravityItems = (
})
);
})
)
)
);
})
);
return h(Fragment, null, ...nodes);
};
const PREMIUM_GEMINI_CLI_TIER_IDS = new Set(['g1-ultra-tier']);
@@ -996,8 +1112,7 @@ const renderCodexItems = (
const windows = quota.windows ?? [];
const planType = quota.planType ?? null;
const subscriptionActiveUntil = quota.subscriptionActiveUntil ?? null;
const rateLimitResetCreditsAvailableCount =
quota.rateLimitResetCreditsAvailableCount ?? null;
const rateLimitResetCreditsAvailableCount = quota.rateLimitResetCreditsAvailableCount ?? null;
const getPlanLabel = (pt?: string | null): string | null => {
const normalized = normalizePlanType(pt);
@@ -1020,56 +1135,36 @@ const renderCodexItems = (
if (planLabel || expiryLabel || rateLimitResetCreditsAvailableCount !== null) {
const planValueClass = isPremiumPlan ? styleMap.premiumPlanValue : styleMap.codexPlanValue;
const planNodes: ReactNode[] = [];
const appendSeparator = (key: string) => {
if (planNodes.length === 0) return;
const appendPlanItem = (
key: string,
label: string,
value: string,
valueClassName = styleMap.codexPlanValue
) => {
planNodes.push(
h('span', {
key,
className: styleMap.codexPlanSeparator,
})
h(
'span',
{ key, className: styleMap.codexPlanItem },
h('span', { className: styleMap.codexPlanLabel }, label),
h('span', { className: valueClassName }, value)
)
);
};
if (planLabel) {
planNodes.push(
h(
'span',
{ key: 'plan-label', className: styleMap.codexPlanLabel },
t('codex_quota.plan_label')
),
h('span', { key: 'plan-value', className: planValueClass }, planLabel)
);
appendPlanItem('plan-type', t('codex_quota.plan_label'), planLabel, planValueClass);
}
if (expiryLabel) {
appendSeparator('subscription-expiry-separator');
planNodes.push(
h(
'span',
{ key: 'subscription-expiry-label', className: styleMap.codexPlanLabel },
t('codex_quota.expires_label')
),
h(
'span',
{ key: 'subscription-expiry-value', className: styleMap.codexPlanValue },
expiryLabel
)
);
appendPlanItem('subscription-expiry', t('codex_quota.expires_label'), expiryLabel);
}
if (rateLimitResetCreditsAvailableCount !== null) {
appendSeparator('reset-credits-separator');
planNodes.push(
h(
'span',
{ key: 'reset-credits-label', className: styleMap.codexPlanLabel },
t('codex_quota.reset_credits_label')
),
h(
'span',
{ key: 'reset-credits-value', className: styleMap.codexPlanValue },
rateLimitResetCreditsAvailableCount.toString()
)
appendPlanItem(
'reset-credits',
t('codex_quota.reset_credits_label'),
rateLimitResetCreditsAvailableCount.toString()
);
}
@@ -1468,15 +1563,22 @@ export const ANTIGRAVITY_CONFIG: QuotaConfig<AntigravityQuotaState, AntigravityQ
fetchQuota: fetchAntigravityQuota,
storeSelector: (state) => state.antigravityQuota,
storeSetter: 'setAntigravityQuota',
buildLoadingState: () => ({ status: 'loading', groups: [], serverTimeOffsetMs: null }),
buildLoadingState: () => ({
status: 'loading',
groups: [],
subscription: null,
serverTimeOffsetMs: null,
}),
buildSuccessState: (data) => ({
status: 'success',
groups: data.groups,
subscription: data.subscription,
serverTimeOffsetMs: data.serverTimeOffsetMs,
}),
buildErrorState: (message, status) => ({
status: 'error',
groups: [],
subscription: null,
serverTimeOffsetMs: null,
error: message,
errorStatus: status,
@@ -34,7 +34,6 @@ import {
type ResolvedTheme,
} from '@/features/authFiles/constants';
import type { AuthFileStatusBarData } from '@/features/authFiles/hooks/useAuthFilesStatusBarCache';
import type { AntigravitySubscriptionState } from '@/features/authFiles/hooks/useAntigravitySubscriptions';
import { AuthFileQuotaSection } from '@/features/authFiles/components/AuthFileQuotaSection';
import styles from '@/pages/AuthFilesPage.module.scss';
@@ -50,7 +49,6 @@ export type AuthFileCardProps = {
statusUpdating: Record<string, boolean>;
quotaFilterType: QuotaProviderType | null;
statusBarCache: Map<string, AuthFileStatusBarData>;
antigravitySubscription?: AntigravitySubscriptionState;
onShowModels: (file: AuthFileItem) => void;
onDownload: (name: string) => void;
onOpenPrefixProxyEditor: (file: AuthFileItem) => void;
@@ -77,7 +75,6 @@ export function AuthFileCard(props: AuthFileCardProps) {
statusUpdating,
quotaFilterType,
statusBarCache,
antigravitySubscription,
onShowModels,
onDownload,
onOpenPrefixProxyEditor,
@@ -93,7 +90,6 @@ export function AuthFileCard(props: AuthFileCardProps) {
};
const isRuntimeOnly = isRuntimeOnlyAuthFile(file);
const providerKey = normalizeProviderKey(String(file.type ?? file.provider ?? 'unknown'));
const isAntigravity = providerKey === 'antigravity';
const isAistudio = providerKey === 'aistudio';
const showModelsButton = !isRuntimeOnly || isAistudio;
const typeColor = getTypeColor(providerKey, resolvedTheme);
@@ -131,55 +127,6 @@ export function AuthFileCard(props: AuthFileCardProps) {
const priorityValue = parsePriorityValue(file.priority ?? file['priority']);
const noteValue = typeof file.note === 'string' ? file.note.trim() : '';
const subscription =
isAntigravity && !isRuntimeOnly ? antigravitySubscription : undefined;
const subscriptionData = subscription?.status === 'success' ? subscription.data : undefined;
const subscriptionPlanLabel =
subscriptionData?.plan === 'free'
? t('antigravity_subscription.plan_free')
: subscriptionData?.plan === 'pro'
? t('antigravity_subscription.plan_pro')
: subscriptionData?.plan === 'ultra'
? t('antigravity_subscription.plan_ultra')
: subscriptionData?.plan === 'ultra-lite'
? t('antigravity_subscription.plan_ultra_lite')
: subscriptionData
? subscriptionData.tierName ||
subscriptionData.tierId ||
t('antigravity_subscription.plan_unknown')
: '';
const subscriptionBadgeLabel =
subscription?.status === 'loading'
? t('antigravity_subscription.loading_short')
: subscription?.status === 'error'
? t('antigravity_subscription.error_badge')
: subscriptionData
? t('antigravity_subscription.plan_badge', {
plan: subscriptionPlanLabel,
})
: '';
const subscriptionTitle =
subscription?.status === 'error'
? subscription.error || t('common.unknown_error')
: subscriptionData?.tierName && subscriptionData.tierId
? `${subscriptionData.tierName} (${subscriptionData.tierId})`
: subscriptionData?.tierName || subscriptionData?.tierId || subscriptionBadgeLabel;
const subscriptionBadgeClass =
subscription?.status === 'loading'
? styles.subscriptionBadgeLoading
: subscription?.status === 'error'
? styles.subscriptionBadgeError
: subscriptionData?.plan === 'free'
? styles.subscriptionBadgeFree
: subscriptionData?.plan === 'ultra' || subscriptionData?.plan === 'ultra-lite'
? styles.subscriptionBadgePremium
: subscriptionData?.plan === 'unknown'
? styles.subscriptionBadgeUnknown
: styles.subscriptionBadgePaid;
const subscriptionErrorMessage =
subscription?.status === 'error'
? subscription.error || t('common.unknown_error')
: '';
const stateLabel = isRuntimeOnly
? t('auth_files.type_virtual') || '虚拟认证文件'
: file.disabled
@@ -244,28 +191,10 @@ export function AuthFileCard(props: AuthFileCardProps) {
{typeLabel}
</span>
<span className={`${styles.stateBadge} ${stateBadgeClass}`}>{stateLabel}</span>
{subscriptionBadgeLabel && (
<span
className={`${styles.subscriptionBadge} ${subscriptionBadgeClass}`}
title={subscriptionTitle}
>
{subscriptionBadgeLabel}
</span>
)}
</div>
<span className={styles.fileName} title={file.name}>
{file.name}
</span>
{!compact && subscriptionData?.tierName && (
<div className={styles.subscriptionSubtitle} title={subscriptionTitle}>
<span className={styles.subscriptionSubtitleLabel}>
{t('antigravity_subscription.subscription_label')}
</span>
<span className={styles.subscriptionSubtitleValue}>
{subscriptionData.tierName}
</span>
</div>
)}
{!compact && noteValue && (
<div className={styles.noteText} title={noteValue}>
<span className={styles.noteLabel}>{t('auth_files.note_display')}</span>
@@ -303,17 +232,6 @@ export function AuthFileCard(props: AuthFileCardProps) {
</div>
)}
{subscriptionErrorMessage && (
<div className={styles.subscriptionError} title={subscriptionErrorMessage}>
<IconInfo className={styles.messageIcon} size={14} />
<span>
{t('antigravity_subscription.load_failed', {
message: subscriptionErrorMessage,
})}
</span>
</div>
)}
<div className={`${styles.cardInsights} ${compact ? styles.cardInsightsCompact : ''}`}>
<div className={`${styles.cardStats} ${compact ? styles.cardStatsCompact : ''}`}>
<div className={`${styles.statPill} ${styles.statSuccess}`}>
+8
View File
@@ -402,6 +402,14 @@
"missing_auth_index": "Auth file missing auth_index",
"missing_project_id": "Antigravity credential missing project_id. Re-login or refresh the credential to discover the project.",
"empty_models": "No quota data available",
"plan_label": "Plan",
"group_gemini_models": "Gemini models",
"group_claude_gpt_models": "Claude and GPT models",
"group_models_description": "Models in this group: {{models}}",
"weekly_limit": "Weekly limit",
"daily_limit": "Daily limit",
"five_hour_limit": "5-hour limit",
"monthly_limit": "Monthly limit",
"quota_available": "Quota available",
"remaining_percent": "{{percent}}% remaining",
"refreshes_in": "Refreshes in {{duration}}",
+8
View File
@@ -396,6 +396,14 @@
"missing_auth_index": "В файле авторизации отсутствует auth_index",
"missing_project_id": "В учётных данных Antigravity отсутствует project_id. Войдите заново или обновите учётные данные, чтобы определить проект.",
"empty_models": "Данные по квоте отсутствуют",
"plan_label": "План",
"group_gemini_models": "Модели Gemini",
"group_claude_gpt_models": "Модели Claude и GPT",
"group_models_description": "Модели в этой группе: {{models}}",
"weekly_limit": "Недельный лимит",
"daily_limit": "Дневной лимит",
"five_hour_limit": "Лимит на 5 часов",
"monthly_limit": "Месячный лимит",
"quota_available": "Квота доступна",
"remaining_percent": "Осталось {{percent}}%",
"refreshes_in": "Обновится через {{duration}}",
+8
View File
@@ -402,6 +402,14 @@
"missing_auth_index": "认证文件缺少 auth_index",
"missing_project_id": "Antigravity 凭证缺少 project_id。请重新登录或刷新凭证以发现项目。",
"empty_models": "暂无额度数据",
"plan_label": "套餐",
"group_gemini_models": "Gemini 模型",
"group_claude_gpt_models": "Claude 和 GPT 模型",
"group_models_description": "此分组包含:{{models}}",
"weekly_limit": "周限额",
"daily_limit": "日限额",
"five_hour_limit": "5 小时限额",
"monthly_limit": "月限额",
"quota_available": "额度可用",
"remaining_percent": "剩余 {{percent}}%",
"refreshes_in": "{{duration}} 后刷新",
+8
View File
@@ -402,6 +402,14 @@
"missing_auth_index": "驗證檔案缺少 auth_index",
"missing_project_id": "Antigravity 憑證缺少 project_id。請重新登入或重新整理憑證以探索專案。",
"empty_models": "暫無配額資料",
"plan_label": "方案",
"group_gemini_models": "Gemini 模型",
"group_claude_gpt_models": "Claude 與 GPT 模型",
"group_models_description": "此分組包含:{{models}}",
"weekly_limit": "週限額",
"daily_limit": "日限額",
"five_hour_limit": "5 小時限額",
"monthly_limit": "月限額",
"quota_available": "配額可用",
"remaining_percent": "剩餘 {{percent}}%",
"refreshes_in": "{{duration}} 後重新整理",
+15 -1
View File
@@ -712,19 +712,31 @@
.codexPlan {
display: flex;
align-items: center;
gap: 6px;
gap: 6px 12px;
font-size: 12px;
color: var(--text-secondary);
flex-wrap: wrap;
}
.codexPlanItem {
display: inline-flex;
align-items: center;
gap: 6px;
min-width: 0;
max-width: 100%;
flex-wrap: wrap;
}
.codexPlanLabel {
color: var(--text-tertiary);
flex: 0 0 auto;
}
.codexPlanValue {
font-weight: 600;
color: var(--text-primary);
text-transform: capitalize;
min-width: 0;
}
.codexPlanSeparator {
@@ -741,6 +753,8 @@
font-size: 12px;
padding: 2px 8px;
border-radius: 999px;
white-space: nowrap;
flex: 0 0 auto;
overflow: visible;
isolation: isolate;
background:
+4 -11
View File
@@ -50,7 +50,6 @@ import { useAuthFilesModels } from '@/features/authFiles/hooks/useAuthFilesModel
import { useAuthFilesOauth } from '@/features/authFiles/hooks/useAuthFilesOauth';
import { useAuthFilesPrefixProxyEditor } from '@/features/authFiles/hooks/useAuthFilesPrefixProxyEditor';
import { useAuthFilesStatusBarCache } from '@/features/authFiles/hooks/useAuthFilesStatusBarCache';
import { useAntigravitySubscriptions } from '@/features/authFiles/hooks/useAntigravitySubscriptions';
import {
isAuthFilesSortMode,
readAuthFilesUiState,
@@ -69,8 +68,7 @@ const BATCH_BAR_HIDDEN_TRANSFORM = 'translateX(-50%) translateY(56px)';
const DEFAULT_REGULAR_PAGE_SIZE = 9;
const DEFAULT_COMPACT_PAGE_SIZE = 12;
const escapeWildcardSearchSegment = (value: string) =>
value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const escapeWildcardSearchSegment = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const buildWildcardSearch = (value: string): RegExp | null => {
if (!value.includes('*')) return null;
@@ -204,10 +202,7 @@ export function AuthFilesPage() {
if (typeof persisted.disabledOnly === 'boolean') {
setDisabledOnly(persisted.disabledOnly);
}
if (
typeof persistedCompactMode !== 'boolean' &&
typeof persisted.compactMode === 'boolean'
) {
if (typeof persistedCompactMode !== 'boolean' && typeof persisted.compactMode === 'boolean') {
setCompactMode(persisted.compactMode);
}
if (typeof persisted.search === 'string') {
@@ -223,11 +218,11 @@ export function AuthFilesPage() {
const regularPageSize =
typeof persisted.regularPageSize === 'number' && Number.isFinite(persisted.regularPageSize)
? clampCardPageSize(persisted.regularPageSize)
: legacyPageSize ?? DEFAULT_REGULAR_PAGE_SIZE;
: (legacyPageSize ?? DEFAULT_REGULAR_PAGE_SIZE);
const compactPageSize =
typeof persisted.compactPageSize === 'number' && Number.isFinite(persisted.compactPageSize)
? clampCardPageSize(persisted.compactPageSize)
: legacyPageSize ?? DEFAULT_COMPACT_PAGE_SIZE;
: (legacyPageSize ?? DEFAULT_COMPACT_PAGE_SIZE);
setPageSizeByMode({
regular: regularPageSize,
compact: compactPageSize,
@@ -432,7 +427,6 @@ export function AuthFilesPage() {
const currentPage = Math.min(page, totalPages);
const start = (currentPage - 1) * pageSize;
const pageItems = useMemo(() => sorted.slice(start, start + pageSize), [pageSize, sorted, start]);
const antigravitySubscriptions = useAntigravitySubscriptions(pageItems);
const selectablePageItems = useMemo(
() => pageItems.filter((file) => !isRuntimeOnlyAuthFile(file)),
[pageItems]
@@ -813,7 +807,6 @@ export function AuthFilesPage() {
statusUpdating={statusUpdating}
quotaFilterType={quotaFilterType}
statusBarCache={statusBarCache}
antigravitySubscription={antigravitySubscriptions[file.name]}
onShowModels={showModels}
onDownload={handleDownload}
onOpenPrefixProxyEditor={openPrefixProxyEditor}
+14 -1
View File
@@ -465,20 +465,31 @@
.codexPlan {
display: flex;
align-items: center;
gap: 6px;
gap: 6px 12px;
font-size: 12px;
color: var(--text-secondary);
flex-wrap: wrap;
}
.codexPlanItem {
display: inline-flex;
align-items: center;
gap: 6px;
min-width: 0;
max-width: 100%;
flex-wrap: wrap;
}
.codexPlanLabel {
color: var(--text-tertiary);
flex: 0 0 auto;
}
.codexPlanValue {
font-weight: 600;
color: var(--text-primary);
text-transform: capitalize;
min-width: 0;
}
.codexPlanSeparator {
@@ -495,6 +506,8 @@
font-size: 12px;
padding: 2px 8px;
border-radius: 999px;
white-space: nowrap;
flex: 0 0 auto;
overflow: visible;
isolation: isolate;
background:
+7
View File
@@ -205,6 +205,12 @@ export interface AntigravityQuotaGroup {
buckets: AntigravityQuotaBucket[];
}
export interface AntigravityQuotaSubscription {
plan: string | null;
tierName: string | null;
tierId: string | null;
}
export interface AntigravityQuotaBucket {
id: string;
label: string;
@@ -217,6 +223,7 @@ export interface AntigravityQuotaBucket {
export interface AntigravityQuotaState {
status: 'idle' | 'loading' | 'success' | 'error';
groups: AntigravityQuotaGroup[];
subscription?: AntigravityQuotaSubscription | null;
serverTimeOffsetMs?: number | null;
error?: string;
errorStatus?: number;
+5 -5
View File
@@ -133,11 +133,11 @@ export function buildGeminiCliQuotaBuckets(
}
const ANTIGRAVITY_BUCKET_WINDOW_ORDER = new Map<string, number>([
['weekly', 0],
['week', 0],
['5h', 1],
['five-hour', 1],
['five_hour', 1],
['5h', 0],
['five-hour', 0],
['five_hour', 0],
['weekly', 1],
['week', 1],
]);
function toStableId(value: string, fallback: string): string {