From 069eaf21b7eac6581dcfaeeecfec3665c24415d3 Mon Sep 17 00:00:00 2001 From: LTbinglingfeng Date: Wed, 17 Jun 2026 23:01:52 +0800 Subject: [PATCH] feat(quota): add subscription management and localization for Antigravity plans --- src/components/quota/quotaConfigs.ts | 230 +++++++++++++----- .../authFiles/components/AuthFileCard.tsx | 82 ------- src/i18n/locales/en.json | 8 + src/i18n/locales/ru.json | 8 + src/i18n/locales/zh-CN.json | 8 + src/i18n/locales/zh-TW.json | 8 + src/pages/AuthFilesPage.module.scss | 16 +- src/pages/AuthFilesPage.tsx | 15 +- src/pages/QuotaPage.module.scss | 15 +- src/types/quota.ts | 7 + src/utils/quota/builders.ts | 10 +- 11 files changed, 243 insertions(+), 164 deletions(-) diff --git a/src/components/quota/quotaConfigs.ts b/src/components/quota/quotaConfigs.ts index 5e28d543..2d5c2ff1 100644 --- a/src/components/quota/quotaConfigs.ts +++ b/src/components/quota/quotaConfigs.ts @@ -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([ + ['gemini models', 'group_gemini_models'], + ['claude and gpt models', 'group_claude_gpt_models'], +]); + +const ANTIGRAVITY_BUCKET_LABEL_KEYS = new Map([ + ['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, + 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 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, diff --git a/src/features/authFiles/components/AuthFileCard.tsx b/src/features/authFiles/components/AuthFileCard.tsx index f44e6ac4..4736300d 100644 --- a/src/features/authFiles/components/AuthFileCard.tsx +++ b/src/features/authFiles/components/AuthFileCard.tsx @@ -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; quotaFilterType: QuotaProviderType | null; statusBarCache: Map; - 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} {stateLabel} - {subscriptionBadgeLabel && ( - - {subscriptionBadgeLabel} - - )} {file.name} - {!compact && subscriptionData?.tierName && ( -
- - {t('antigravity_subscription.subscription_label')} - - - {subscriptionData.tierName} - -
- )} {!compact && noteValue && (
{t('auth_files.note_display')} @@ -303,17 +232,6 @@ export function AuthFileCard(props: AuthFileCardProps) {
)} - {subscriptionErrorMessage && ( -
- - - {t('antigravity_subscription.load_failed', { - message: subscriptionErrorMessage, - })} - -
- )} -
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index b94dad44..2b1c8697 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -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}}", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 229ccca4..ad3f1647 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -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}}", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 7f66545a..34d5032e 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -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}} 后刷新", diff --git a/src/i18n/locales/zh-TW.json b/src/i18n/locales/zh-TW.json index 5da0f12c..546d6de4 100644 --- a/src/i18n/locales/zh-TW.json +++ b/src/i18n/locales/zh-TW.json @@ -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}} 後重新整理", diff --git a/src/pages/AuthFilesPage.module.scss b/src/pages/AuthFilesPage.module.scss index 203078e8..b72f0d73 100644 --- a/src/pages/AuthFilesPage.module.scss +++ b/src/pages/AuthFilesPage.module.scss @@ -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: diff --git a/src/pages/AuthFilesPage.tsx b/src/pages/AuthFilesPage.tsx index b94c99b7..3ee2bdca 100644 --- a/src/pages/AuthFilesPage.tsx +++ b/src/pages/AuthFilesPage.tsx @@ -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} diff --git a/src/pages/QuotaPage.module.scss b/src/pages/QuotaPage.module.scss index 7823d979..6685dec7 100644 --- a/src/pages/QuotaPage.module.scss +++ b/src/pages/QuotaPage.module.scss @@ -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: diff --git a/src/types/quota.ts b/src/types/quota.ts index d4d8677b..bc2dad2d 100644 --- a/src/types/quota.ts +++ b/src/types/quota.ts @@ -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; diff --git a/src/utils/quota/builders.ts b/src/utils/quota/builders.ts index 2dc9108f..4429f54e 100644 --- a/src/utils/quota/builders.ts +++ b/src/utils/quota/builders.ts @@ -133,11 +133,11 @@ export function buildGeminiCliQuotaBuckets( } const ANTIGRAVITY_BUCKET_WINDOW_ORDER = new Map([ - ['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 {