mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-09-24 16:04:35 +08:00
feat(quota): add subscription management and localization for Antigravity plans
This commit is contained in:
@@ -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}`}>
|
||||
|
||||
@@ -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}}",
|
||||
|
||||
@@ -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}}",
|
||||
|
||||
@@ -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}} 后刷新",
|
||||
|
||||
@@ -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}} 後重新整理",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user