From e1d6600eb289f9312e4070ee0ed4e92988a53234 Mon Sep 17 00:00:00 2001 From: IanShaw027 Date: Sat, 8 Aug 2026 08:48:38 +0800 Subject: [PATCH] fix(admin): simplify Grok usage UI for free 24h and paid windows Show free accounts only the rolling 24h soft-gate bar; paid accounts show 7d/30d and prepaid money. Drop the always-visible manual probe chip from the usage cell in favor of scheduled recovery and usage load. --- .../components/account/AccountUsageCell.vue | 327 ++++---------- .../components/account/GrokQuotaProbeCell.vue | 64 +-- .../__tests__/AccountUsageCell.spec.ts | 414 ++---------------- 3 files changed, 123 insertions(+), 682 deletions(-) diff --git a/frontend/src/components/account/AccountUsageCell.vue b/frontend/src/components/account/AccountUsageCell.vue index d5807fca6a..e7e0a298fc 100644 --- a/frontend/src/components/account/AccountUsageCell.vue +++ b/frontend/src/components/account/AccountUsageCell.vue @@ -113,8 +113,6 @@
-
- -
@@ -356,120 +354,72 @@
- {{ grokEntitlementLabel || t('admin.accounts.forbidden') }} + {{ usageInfo?.grok_entitlement_status || t('admin.accounts.forbidden') }}
-
- - {{ grokEntitlementLabel }} - -
-
-
- - {{ formatWindowRequests(grokLocalUsage) }} req - - - {{ formatWindowTokens(grokLocalUsage) }} - - - A ${{ formatWindowCost(grokLocalUsage) }} - + + + +
+ {{ usageErrorLabel }}
- - -
- - {{ t('admin.accounts.usageWindow.grokPrepaid') }} ${{ grokBillingMoneySummary.prepaid }} - - - {{ t('admin.accounts.usageWindow.grokUsed') }} - {{ grokBillingMoneySummary.used }}/{{ grokBillingMoneySummary.limit }} - - - {{ grokBillingMoneySummary.usedPercent }}% - - - {{ t('admin.accounts.usageWindow.grokOverageShort') }} - {{ grokBillingMoneySummary.onDemandUsed }}/{{ grokBillingMoneySummary.onDemandCap }} - -
- - -
{{ t('admin.accounts.usageWindow.grokRetryAfter', { time: grokRetryAfterLabel }) }}
-
- {{ grokQuotaUnknownLabel }} -
-
- {{ usageErrorLabel }} -
-
- {{ grokQuotaStatusLine }} -
- + +
+
+
-
+
-
-
@@ -667,11 +617,10 @@ import { ref, computed, onMounted, onBeforeUnmount, onUnmounted, watch } from 'vue' import { useI18n } from 'vue-i18n' import { adminAPI } from '@/api/admin' -import type { GrokQuotaProbeResult } from '@/api/admin/grok' import type { Account, AccountUsageInfo, GeminiCredentials, WindowStats } from '@/types' import { buildOpenAIUsageRefreshKey } from '@/utils/accountUsageRefresh' import { enqueueUsageRequest } from '@/utils/usageLoadQueue' -import { formatCompactNumber, formatRelativeTime } from '@/utils/format' +import { formatCompactNumber } from '@/utils/format' import UsageProgressBar from './UsageProgressBar.vue' import AccountQuotaInfo from './AccountQuotaInfo.vue' import OpenAIQuotaResetCell from './OpenAIQuotaResetCell.vue' @@ -1124,17 +1073,6 @@ interface GrokQuotaBarInfo { resetsAt: string | null } -const makeGrokQuotaBar = (quota?: { limit?: number | null; remaining?: number | null; reset_at?: string | null } | null): GrokQuotaBarInfo | null => { - if (!quota || quota.limit == null || quota.remaining == null || quota.limit <= 0) return null - const remaining = Math.min(quota.limit, Math.max(0, quota.remaining)) - return { - utilization: (remaining / quota.limit) * 100, - resetsAt: quota.reset_at || null - } -} - -const grokRequestQuotaBar = computed(() => makeGrokQuotaBar(usageInfo.value?.grok_request_quota)) -const grokTokenQuotaBar = computed(() => makeGrokQuotaBar(usageInfo.value?.grok_token_quota)) const grokBilling = computed(() => usageInfo.value?.grok_billing || null) const grokWeeklyBillingBar = computed((): GrokQuotaBarInfo | null => { const billing = grokBilling.value @@ -1177,49 +1115,30 @@ const formatGrokMoney = (value?: number | null) => { if (value >= 10) return value.toFixed(1) return value.toFixed(2) } -// Absolute money: prefer prepaid/monthly_used fields; fall back to cents/100. -const grokBillingMoneySummary = computed(() => { +// Prepaid money line for paid Grok: show when prepaid_balance is present. +// Monthly used/limit numbers are optional context; primary progress is the 30d bar. +const grokPrepaidMoneyLine = computed(() => { const billing = grokBilling.value if (!billing) return null - const prepaid = - billing.prepaid_balance != null - ? billing.prepaid_balance - : null + const prepaid = billing.prepaid_balance + // "只针对预付": only render when prepaid field exists (including $0.00). + if (prepaid == null || !Number.isFinite(prepaid)) return null const used = billing.monthly_used != null ? billing.monthly_used : billing.used_cents != null ? billing.used_cents / 100 - : null + : 0 const limit = billing.monthly_limit != null ? billing.monthly_limit : billing.monthly_limit_cents != null ? billing.monthly_limit_cents / 100 - : null - const onDemandUsed = billing.on_demand_used ?? null - const onDemandCap = billing.on_demand_cap ?? null - const hasAny = - (prepaid ?? 0) > 0 || - (used ?? 0) > 0 || - (limit ?? 0) > 0 || - (onDemandCap ?? 0) > 0 || - (onDemandUsed ?? 0) > 0 - if (!hasAny) return null - let usedPercent: number | null = null - if (billing.used_percent != null && Number.isFinite(billing.used_percent)) { - usedPercent = Math.round(Math.min(100, Math.max(0, billing.used_percent))) - } else if (limit != null && limit > 0 && used != null) { - usedPercent = Math.round(Math.min(100, Math.max(0, (used / limit) * 100))) - } + : 0 return { - prepaid: prepaid != null ? formatGrokMoney(prepaid) : null, - used: formatGrokMoney(used ?? 0), - limit: formatGrokMoney(limit ?? 0), - onDemandUsed: formatGrokMoney(onDemandUsed ?? 0), - onDemandCap: formatGrokMoney(onDemandCap ?? 0), - showOverage: (onDemandCap ?? 0) > 0 || (onDemandUsed ?? 0) > 0, - usedPercent + prepaid: formatGrokMoney(prepaid), + used: formatGrokMoney(used), + limit: formatGrokMoney(limit) } }) const grokPlanLabelIsFree = (value: string) => value.includes('free') || value.includes('basic') @@ -1247,18 +1166,6 @@ const grokIsFree = computed(() => { return billing != null }) const grokFreeQuotaUsage = computed(() => usageInfo.value?.grok_local_usage_24h || null) -const showGrokStandaloneLocalStats = computed(() => { - // Official 7d/30d bars already embed window_stats — avoid duplicate local row. - return !usageInfo.value?.seven_day && !usageInfo.value?.thirty_day -}) -const grokLocalUsage = computed(() => { - if (grokIsFree.value) return grokFreeQuotaUsage.value - return props.todayStats || - usageInfo.value?.grok_local_usage || - usageInfo.value?.grok_local_usage_7d || - usageInfo.value?.grok_local_usage_monthly || - null -}) const grokFreeTokenBar = computed(() => { if (!grokIsFree.value || !grokFreeQuotaUsage.value) return null const limit = usageInfo.value?.grok_free_token_limit @@ -1268,14 +1175,10 @@ const grokFreeTokenBar = computed(() => { }) const grokQuotaUnknown = computed(() => { if (props.account.platform !== 'grok') return false - if ( - grokBilling.value || - grokBillingMoneySummary.value || - grokMonthlyBillingBar.value || - grokFreeTokenBar.value || - grokRequestQuotaBar.value || - grokTokenQuotaBar.value - ) { + if (grokIsFree.value) { + return !grokFreeTokenBar.value + } + if (grokWeeklyBillingBar.value || grokMonthlyBillingBar.value || grokPrepaidMoneyLine.value) { return false } return usageInfo.value?.grok_quota_snapshot_state !== 'observed' @@ -1285,33 +1188,6 @@ const grokQuotaUnknownLabel = computed(() => { ? t('admin.accounts.usageWindow.grokNoHeaders') : t('admin.accounts.usageWindow.grokUnknown') }) -const grokQuotaStatusLine = computed(() => { - if (props.account.platform !== 'grok') return null - const parts: string[] = [] - const status = usageInfo.value?.grok_last_status_code - if (status) { - parts.push(t('admin.accounts.usageWindow.grokLastStatus', { status })) - } - if (usageInfo.value?.grok_last_quota_probe_at) { - parts.push( - t('admin.accounts.usageWindow.grokLastProbe', { - time: formatRelativeTime(usageInfo.value.grok_last_quota_probe_at) - }) - ) - } - if (usageInfo.value?.grok_last_headers_seen_at) { - parts.push( - t('admin.accounts.usageWindow.grokLastHeadersSeen', { - time: formatRelativeTime(usageInfo.value.grok_last_headers_seen_at) - }) - ) - } - return parts.length > 0 ? parts.join(' | ') : null -}) -const grokEntitlementLabel = computed(() => { - const status = (usageInfo.value?.grok_entitlement_status || '').trim() - return status || null -}) const grokRetryAfterLabel = computed(() => { const seconds = usageInfo.value?.grok_retry_after_seconds if (seconds == null || seconds <= 0) return null @@ -1320,11 +1196,6 @@ const grokRetryAfterLabel = computed(() => { return `${minutes}m` }) -const formatWindowRequests = (stats: WindowStats) => formatCompactNumber(stats.requests, { allowBillions: false }) -const formatWindowTokens = (stats: WindowStats) => formatCompactNumber(stats.tokens) -const formatWindowCost = (stats: WindowStats) => stats.cost.toFixed(2) -const formatWindowUserCost = (stats: WindowStats) => (stats.user_cost ?? 0).toFixed(2) - // 账户类型显示标签 const antigravityTierLabel = computed(() => { switch (antigravityTier.value) { @@ -1447,9 +1318,9 @@ const loadUsage = async (options?: { source?: 'passive' | 'active'; bypassCache? error.value = null try { - const fetchFn = () => options?.source - ? adminAPI.accounts.getUsage(props.account.id, options.source) - : adminAPI.accounts.getUsage(props.account.id) + const fetchFn = () => options?.source + ? adminAPI.accounts.getUsage(props.account.id, options.source, options.bypassCache === true) + : adminAPI.accounts.getUsage(props.account.id) const result = await enqueueUsageRequest(props.account, fetchFn) if (!unmounted.value) { usageInfo.value = result @@ -1526,54 +1397,10 @@ const loadActiveUsage = async () => { } } -const handleGrokProbed = (result: GrokQuotaProbeResult) => { - const current = usageInfo.value - if (!current) return - const snapshot = result.snapshot - const statusCode = snapshot?.status_code ?? result.status_code - const hasActiveProbeSnapshot = snapshot != null && ( - result.source === 'active_probe' || - result.source === 'hybrid_probe' || - snapshot.observation_source === 'active_probe' - ) - const probeSucceeded = hasActiveProbeSnapshot && - statusCode != null && statusCode >= 200 && statusCode < 300 - const snapshotEntitlement = snapshot?.entitlement_status?.trim() - const currentEntitlement = current.grok_entitlement_status?.trim() - const entitlementStatus = snapshotEntitlement || ( - probeSucceeded && currentEntitlement?.toLowerCase() === 'forbidden' - ? undefined - : current.grok_entitlement_status - ) - const merged: AccountUsageInfo = { - ...current, - grok_billing: result.billing ?? current.grok_billing, - grok_local_usage_24h: result.local_usage_24h ?? current.grok_local_usage_24h, - grok_local_usage_7d: result.local_usage_7d ?? current.grok_local_usage_7d, - grok_local_usage_monthly: result.local_usage_monthly ?? current.grok_local_usage_monthly, - grok_request_quota: snapshot?.requests ?? current.grok_request_quota, - grok_token_quota: snapshot?.tokens ?? current.grok_token_quota, - grok_retry_after_seconds: snapshot?.retry_after_seconds ?? current.grok_retry_after_seconds, - grok_entitlement_status: entitlementStatus, - grok_quota_snapshot_state: result.billing - ? 'billing_observed' - : snapshot?.headers_observed - ? 'observed' - : current.grok_quota_snapshot_state, - grok_last_quota_probe_at: result.billing?.fetched_at ?? snapshot?.last_probe_at ?? current.grok_last_quota_probe_at, - grok_last_headers_seen_at: snapshot?.last_headers_seen_at ?? current.grok_last_headers_seen_at, - grok_last_status_code: result.status_code ?? snapshot?.status_code ?? current.grok_last_status_code, - is_forbidden: probeSucceeded ? false : current.is_forbidden, - forbidden_reason: probeSucceeded ? undefined : current.forbidden_reason, - forbidden_type: probeSucceeded ? undefined : current.forbidden_type, - validation_url: probeSucceeded ? undefined : current.validation_url, - needs_verify: probeSucceeded ? false : current.needs_verify, - is_banned: probeSucceeded ? false : current.is_banned, - error: result.billing || snapshot ? undefined : current.error, - error_code: result.billing || snapshot ? undefined : current.error_code - } - usageInfo.value = merged - _usageCache.set(props.account.id, { data: merged, ts: Date.now() }) +// The probe persists upstream quota state; refresh this cell so its compact +// bars and entitlement status reflect the newly observed snapshot. +const handleGrokProbed = async () => { + await loadUsage({ source: 'active', bypassCache: true }) } // ===== API Key quota progress bars ===== diff --git a/frontend/src/components/account/GrokQuotaProbeCell.vue b/frontend/src/components/account/GrokQuotaProbeCell.vue index fa6fe29bec..e5f84077a2 100644 --- a/frontend/src/components/account/GrokQuotaProbeCell.vue +++ b/frontend/src/components/account/GrokQuotaProbeCell.vue @@ -24,18 +24,13 @@ {{ t('admin.accounts.usageWindow.grokProbe') }} - -
-
+ +
{{ summary }}
@@ -48,12 +43,17 @@ import { computed, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' import { adminAPI } from '@/api/admin' -import type { GrokQuotaProbeResult, GrokQuotaWindow } from '@/api/admin/grok' +import type { GrokQuotaProbeResult } from '@/api/admin/grok' import type { Account } from '@/types' -const props = defineProps<{ - account: Account -}>() +const props = withDefaults( + defineProps<{ + account: Account + /** When true, only show the probe button (+ errors). No duplicate weekly summary. */ + compact?: boolean + }>(), + { compact: false } +) const emit = defineEmits<{ probed: [result: GrokQuotaProbeResult] }>() @@ -79,42 +79,16 @@ const extractErrorMessage = (e: unknown): string => { ) } -const formatWindow = (label: string, window?: GrokQuotaWindow | null): string | null => { - if (!window || window.limit == null || window.remaining == null) return null - return `${label} ${window.remaining}/${window.limit}` -} - -const retryAfterLabel = computed(() => { - const seconds = data.value?.snapshot?.retry_after_seconds - if (seconds == null || seconds <= 0) return null - if (seconds < 60) return `${seconds}s` - return `${Math.ceil(seconds / 60)}m` -}) - const summary = computed(() => { - const snapshot = data.value?.snapshot - if (!data.value) return '' + if (props.compact || !data.value) return '' + // Non-compact fallback (rarely used): brief weekly percent if present. const billing = data.value.billing - const parts: Array = [] if (billing?.period_type?.toLowerCase() === 'weekly' && billing.usage_percent != null) { - parts.push(t('admin.accounts.usageWindow.grokWeeklyUsage', { + return t('admin.accounts.usageWindow.grokWeeklyUsage', { percent: Math.round(Math.min(100, Math.max(0, billing.usage_percent))) - })) + }) } - if (snapshot) { - parts.push( - formatWindow(t('admin.accounts.usageWindow.grokRequests'), snapshot.requests), - formatWindow(t('admin.accounts.usageWindow.grokTokens'), snapshot.tokens) - ) - } - if (retryAfterLabel.value) { - parts.push(t('admin.accounts.usageWindow.grokRetryAfter', { time: retryAfterLabel.value })) - } - if (snapshot?.entitlement_status) { - parts.push(snapshot.entitlement_status) - } - const visibleParts = parts.filter((part): part is string => Boolean(part)) - return visibleParts.length > 0 ? visibleParts.join(' | ') : t('admin.accounts.usageWindow.grokNoHeaders') + return '' }) const truncatedError = computed(() => { diff --git a/frontend/src/components/account/__tests__/AccountUsageCell.spec.ts b/frontend/src/components/account/__tests__/AccountUsageCell.spec.ts index 6fcc963145..46f3908254 100644 --- a/frontend/src/components/account/__tests__/AccountUsageCell.spec.ts +++ b/frontend/src/components/account/__tests__/AccountUsageCell.spec.ts @@ -651,7 +651,7 @@ describe('AccountUsageCell', () => { expect(badges.some(node => node.attributes('title') === 'usage.userBilled')).toBe(true) }) - it('Grok OAuth 会展示本地 user billed 用量并把耗尽配额显示为 0% 剩余', async () => { + it('Grok OAuth compact UI drops local chips and header quota bars', async () => { getUsage.mockResolvedValue({ grok_local_usage: { requests: 4, @@ -670,12 +670,7 @@ describe('AccountUsageCell', () => { const wrapper = mount(AccountUsageCell, { props: { - account: makeAccount({ - id: 3861, - platform: 'grok', - type: 'oauth', - extra: {} - }) + account: makeAccount({ id: 3861, platform: 'grok', type: 'oauth', extra: {} }) }, global: { stubs: { @@ -683,66 +678,49 @@ describe('AccountUsageCell', () => { props: ['label', 'utilization', 'resetsAt', 'color'], template: '
{{ label }}|{{ utilization }}|{{ resetsAt }}
' }, - AccountQuotaInfo: true, - GrokQuotaProbeCell: true + AccountQuotaInfo: true } } }) await flushPromises() - expect(getUsage).toHaveBeenCalledWith(3861) - expect(wrapper.text()).toContain('4 req') - expect(wrapper.text()).toContain('1.2K') - expect(wrapper.text()).toContain('A $0.12') - expect(wrapper.text()).toContain('U $0.34') - expect(wrapper.text()).toContain('admin.accounts.usageWindow.grokRequests|0|2026-07-09T16:00:00Z') - - const badges = wrapper.findAll('span[title]') - expect(badges.some(node => node.attributes('title') === 'usage.accountBilled')).toBe(true) - expect(badges.some(node => node.attributes('title') === 'usage.userBilled')).toBe(true) + expect(wrapper.text()).not.toContain('4 req') + expect(wrapper.text()).not.toContain('admin.accounts.usageWindow.grokRequests|') }) - it('Grok OAuth 配额条按剩余容量显示 100% 满格和 25% 低量', async () => { + it('Grok paid monthly limits show 30d bar without free 24h', async () => { getUsage.mockResolvedValue({ - grok_request_quota: { - limit: 100, - remaining: 100, - reset_at: '2026-07-09T16:00:00Z' + grok_billing: { + period_type: 'weekly', + usage_percent: null, + used_percent: 12, + monthly_limit_cents: 25_000, + used_cents: 3_000, + plan: '' }, - grok_token_quota: { - limit: 1000, - remaining: 250, - reset_at: '2026-07-09T16:00:00Z' - }, - grok_quota_snapshot_state: 'observed' + grok_entitlement_status: 'free', + grok_token_quota: { limit: 1_000, remaining: 250 } }) const wrapper = mount(AccountUsageCell, { props: { - account: makeAccount({ - id: 4073, - platform: 'grok', - type: 'oauth', - extra: {} - }) + account: makeAccount({ id: 4402, platform: 'grok', type: 'oauth', extra: {} }) }, global: { stubs: { UsageProgressBar: { - props: ['label', 'utilization', 'resetsAt', 'color', 'remainingCapacity'], - template: '
{{ label }}|{{ utilization }}|{{ remainingCapacity }}
' + props: ['label', 'utilization'], + template: '
{{ label }}|{{ utilization }}
' }, - AccountQuotaInfo: true, - GrokQuotaProbeCell: true + AccountQuotaInfo: true } } }) await flushPromises() - - expect(wrapper.text()).toContain('admin.accounts.usageWindow.grokRequests|100|true') - expect(wrapper.text()).toContain('admin.accounts.usageWindow.grokTokens|25|true') + expect(wrapper.text()).toContain('30d|') + expect(wrapper.text()).not.toContain('24h|') }) it('Grok OAuth uses the official weekly billing percentage when available', async () => { @@ -775,7 +753,6 @@ describe('AccountUsageCell', () => { template: '
{{ label }}|{{ utilization }}|{{ resetsAt }}|{{ remainingCapacity }}
' }, AccountQuotaInfo: true, - GrokQuotaProbeCell: true } } }) @@ -789,11 +766,11 @@ describe('AccountUsageCell', () => { }) it.each([ - { tokens: 0, expected: 0, compact: '0' }, - { tokens: 500_000, expected: 50, compact: '500.0K' }, - { tokens: 1_000_000, expected: 100, compact: '1.0M' }, - { tokens: 1_100_000, expected: 100, compact: '1.1M' } - ])('Grok Free derives its 1M quota from local tokens: $tokens -> $expected%', async ({ tokens, expected, compact }) => { + { tokens: 0, expected: 0 }, + { tokens: 500_000, expected: 50 }, + { tokens: 1_000_000, expected: 100 }, + { tokens: 1_100_000, expected: 100 } + ])('Grok Free derives its 1M quota from local tokens: $tokens -> $expected%', async ({ tokens, expected }) => { getUsage.mockResolvedValue({ grok_free_token_limit: 1_000_000, grok_billing: { @@ -823,7 +800,6 @@ describe('AccountUsageCell', () => { template: '
{{ label }}|{{ utilization }}
' }, AccountQuotaInfo: true, - GrokQuotaProbeCell: true } } }) @@ -831,10 +807,10 @@ describe('AccountUsageCell', () => { await flushPromises() expect(wrapper.text()).toContain(`24h|${expected}`) - expect(wrapper.findAll('span').filter((node) => node.text() === compact)).toHaveLength(1) expect(wrapper.findAll('.usage-bar')).toHaveLength(1) expect(wrapper.text()).not.toContain('admin.accounts.usageWindow.grokRequests|') expect(wrapper.text()).not.toContain('admin.accounts.usageWindow.grokTokens|') + expect(wrapper.text()).not.toContain('7d|') }) it('Grok Free uses rolling 24h usage instead of today-only usage', async () => { @@ -872,7 +848,6 @@ describe('AccountUsageCell', () => { template: '
{{ label }}|{{ utilization }}|{{ title }}
' }, AccountQuotaInfo: true, - GrokQuotaProbeCell: true } } }) @@ -880,7 +855,6 @@ describe('AccountUsageCell', () => { await flushPromises() expect(wrapper.text()).toContain('24h|75|admin.accounts.usageWindow.grokFreeQuota24hHint') - expect(wrapper.text()).toContain('750.0K') expect(wrapper.text()).not.toContain('7d|') expect(wrapper.text()).not.toContain('200.0K') expect(wrapper.text()).not.toContain('250.0K') @@ -917,7 +891,6 @@ describe('AccountUsageCell', () => { template: '
{{ label }}|{{ utilization }}
' }, AccountQuotaInfo: true, - GrokQuotaProbeCell: true } } }) @@ -930,85 +903,6 @@ describe('AccountUsageCell', () => { expect(wrapper.text()).not.toContain('250.0K') }) - it('Grok paid plans are not mistaken for Free when weekly usage is temporarily missing', async () => { - getUsage.mockResolvedValue({ - grok_billing: { - period_type: 'weekly', - usage_percent: null, - plan: 'SuperGrok Heavy' - }, - grok_entitlement_status: 'free', - grok_local_usage: { - requests: 2, - tokens: 2_000_000, - cost: 1, - standard_cost: 1 - }, - grok_token_quota: { limit: 1_000, remaining: 250 } - }) - - const wrapper = mount(AccountUsageCell, { - props: { - account: makeAccount({ id: 4401, platform: 'grok', type: 'oauth', extra: {} }) - }, - global: { - stubs: { - UsageProgressBar: { - props: ['label', 'utilization'], - template: '
{{ label }}|{{ utilization }}
' - }, - AccountQuotaInfo: true, - GrokQuotaProbeCell: true - } - } - }) - - await flushPromises() - - expect(wrapper.text()).toContain('admin.accounts.usageWindow.grokTokens|25') - expect(wrapper.text()).not.toContain('2M|') - }) - - it('Grok custom paid monthly limits override stale Free entitlement', async () => { - getUsage.mockResolvedValue({ - grok_billing: { - period_type: 'weekly', - usage_percent: null, - monthly_limit_cents: 25_000, - plan: '' - }, - grok_entitlement_status: 'free', - grok_local_usage: { - requests: 2, - tokens: 2_000_000, - cost: 1, - standard_cost: 1 - }, - grok_token_quota: { limit: 1_000, remaining: 250 } - }) - - const wrapper = mount(AccountUsageCell, { - props: { - account: makeAccount({ id: 4402, platform: 'grok', type: 'oauth', extra: {} }) - }, - global: { - stubs: { - UsageProgressBar: { - props: ['label', 'utilization'], - template: '
{{ label }}|{{ utilization }}
' - }, - AccountQuotaInfo: true, - GrokQuotaProbeCell: true - } - } - }) - - await flushPromises() - - expect(wrapper.text()).toContain('admin.accounts.usageWindow.grokTokens|25') - expect(wrapper.text()).not.toContain('2M|') - }) - it('Grok credential Free tier keeps the 1M fallback when billing is unavailable', async () => { getUsage.mockResolvedValue({ grok_free_token_limit: 1_000_000, @@ -1032,7 +926,6 @@ describe('AccountUsageCell', () => { template: '
{{ label }}|{{ utilization }}
' }, AccountQuotaInfo: true, - GrokQuotaProbeCell: true } } }) @@ -1042,257 +935,6 @@ describe('AccountUsageCell', () => { expect(wrapper.text()).toContain('24h|100') }) - it('Grok paid manual probes keep the weekly/local summary when 24h usage is returned', async () => { - getUsage.mockResolvedValue({ - grok_quota_snapshot_state: 'no_headers', - error: 'stale error', - error_code: 'quota_unknown' - }) - - const wrapper = mount(AccountUsageCell, { - props: { - account: makeAccount({ id: 4501, platform: 'grok', type: 'oauth', extra: {} }) - }, - global: { - stubs: { - UsageProgressBar: { - props: ['label', 'utilization', 'resetsAt'], - template: '
{{ label }}|{{ utilization }}|{{ resetsAt }}
' - }, - AccountQuotaInfo: true, - GrokQuotaProbeCell: { - emits: ['probed'], - template: `` - } - } - } - }) - - await flushPromises() - await wrapper.get('.probe').trigger('click') - - expect(wrapper.text()).toContain('7d|42|2026-07-17T00:00:00Z') - expect(wrapper.text()).toContain('1.0M') - expect(wrapper.text()).not.toContain('750.0K') - expect(wrapper.text()).toContain('ACTIVE') - expect(wrapper.text()).not.toContain('stale error') - }) - - it('Grok successful probes immediately clear stale forbidden state', async () => { - getUsage.mockResolvedValue({ - is_forbidden: true, - forbidden_reason: 'stale forbidden response', - forbidden_type: 'validation', - validation_url: 'https://example.com/verify', - needs_verify: true, - is_banned: true, - grok_entitlement_status: 'forbidden', - grok_quota_snapshot_state: 'no_headers', - error: 'stale forbidden response', - error_code: 'forbidden' - }) - - const wrapper = mount(AccountUsageCell, { - props: { - account: makeAccount({ id: 4503, platform: 'grok', type: 'oauth', extra: {} }) - }, - global: { - stubs: { - UsageProgressBar: true, - AccountQuotaInfo: true, - GrokQuotaProbeCell: true - } - } - }) - - await flushPromises() - expect(wrapper.text()).toContain('forbidden') - - const setupState = wrapper.vm.$.setupState as { - handleGrokProbed: (result: Record) => void - usageInfo: Record | null - } - setupState.handleGrokProbed({ - source: 'active_probe', - snapshot: { - headers_observed: false, - updated_at: '2026-07-18T00:00:00Z', - status_code: 200 - }, - status_code: 200, - headers_observed: false, - reset_supported: false, - fetched_at: 1 - }) - await wrapper.vm.$nextTick() - - expect(setupState.usageInfo).toMatchObject({ - is_forbidden: false, - needs_verify: false, - is_banned: false, - grok_last_status_code: 200 - }) - expect(setupState.usageInfo?.forbidden_reason).toBeUndefined() - expect(setupState.usageInfo?.forbidden_type).toBeUndefined() - expect(setupState.usageInfo?.validation_url).toBeUndefined() - expect(setupState.usageInfo?.grok_entitlement_status).toBeUndefined() - expect(wrapper.text()).not.toContain('admin.accounts.forbidden') - }) - - it('Grok successful probes preserve the entitlement reported by the latest snapshot', async () => { - getUsage.mockResolvedValue({ - is_forbidden: true, - grok_entitlement_status: 'forbidden' - }) - - const wrapper = mount(AccountUsageCell, { - props: { - account: makeAccount({ id: 4504, platform: 'grok', type: 'oauth', extra: {} }) - }, - global: { - stubs: { - UsageProgressBar: true, - AccountQuotaInfo: true, - GrokQuotaProbeCell: true - } - } - }) - - await flushPromises() - - const setupState = wrapper.vm.$.setupState as { - handleGrokProbed: (result: Record) => void - usageInfo: Record | null - } - setupState.handleGrokProbed({ - source: 'active_probe', - snapshot: { - headers_observed: true, - updated_at: '2026-07-18T00:00:00Z', - entitlement_status: 'ACTIVE', - status_code: 200 - }, - status_code: 200, - headers_observed: true, - reset_supported: false, - fetched_at: 1 - }) - await wrapper.vm.$nextTick() - - expect(setupState.usageInfo?.grok_entitlement_status).toBe('ACTIVE') - expect(wrapper.text()).toContain('ACTIVE') - expect(wrapper.text()).not.toContain('admin.accounts.forbidden') - }) - - it('Grok billing-only success does not clear an active-probe forbidden state', async () => { - getUsage.mockResolvedValue({ - is_forbidden: true, - forbidden_type: 'forbidden', - needs_verify: true, - is_banned: true, - grok_entitlement_status: 'forbidden' - }) - - const wrapper = mount(AccountUsageCell, { - props: { - account: makeAccount({ id: 4505, platform: 'grok', type: 'oauth', extra: {} }) - }, - global: { - stubs: { - UsageProgressBar: true, - AccountQuotaInfo: true, - GrokQuotaProbeCell: true - } - } - }) - - await flushPromises() - - const setupState = wrapper.vm.$.setupState as { - handleGrokProbed: (result: Record) => void - usageInfo: Record | null - } - setupState.handleGrokProbed({ - source: 'billing_probe', - billing: { - period_type: 'weekly', - usage_percent: 10, - plan: 'SuperGrok' - }, - status_code: 200, - headers_observed: false, - reset_supported: false, - fetched_at: 1 - }) - await wrapper.vm.$nextTick() - - expect(setupState.usageInfo).toMatchObject({ - is_forbidden: true, - forbidden_type: 'forbidden', - needs_verify: true, - is_banned: true, - grok_entitlement_status: 'forbidden' - }) - expect(wrapper.text()).toContain('forbidden') - }) - - it('Grok Free manual probes merge rolling 24h usage', async () => { - getUsage.mockResolvedValue({ - grok_free_token_limit: 1_000_000, - subscription_tier: 'FREE', - grok_quota_snapshot_state: 'no_headers' - }) - - const wrapper = mount(AccountUsageCell, { - props: { - account: makeAccount({ id: 4502, platform: 'grok', type: 'oauth', extra: {} }) - }, - global: { - stubs: { - UsageProgressBar: { - props: ['label', 'utilization'], - template: '
{{ label }}|{{ utilization }}
' - }, - AccountQuotaInfo: true, - GrokQuotaProbeCell: { - emits: ['probed'], - template: `` - } - } - } - }) - - await flushPromises() - await wrapper.get('.probe').trigger('click') - - expect(wrapper.text()).toContain('24h|75') - expect(wrapper.text()).toContain('750.0K') - expect(wrapper.text()).not.toContain('7d|') - }) - it('Key 账号在 today stats loading 时显示骨架屏', async () => { const wrapper = mount(AccountUsageCell, { props: { @@ -1424,7 +1066,6 @@ describe('AccountUsageCell', () => { template: '
{{ label }}|{{ utilization }}
' }, AccountQuotaInfo: true, - GrokQuotaProbeCell: true } } }) @@ -1468,7 +1109,6 @@ describe('AccountUsageCell', () => { template: '
{{ label }}|{{ utilization }}
' }, AccountQuotaInfo: true, - GrokQuotaProbeCell: true } } })