+
{{ t('admin.accounts.usageWindow.grokUsed') }}
{{ grokPrepaidMoneyLine.used }}/{{ grokPrepaidMoneyLine.limit }}
@@ -1108,9 +1115,16 @@ const geminiUsageBars = computed(() => {
interface GrokQuotaBarInfo {
utilization: number
resetsAt: string | null
+ windowStats?: WindowStats | null
}
const grokBilling = computed(() => usageInfo.value?.grok_billing || null)
+const grokLocalUsage7d = computed(() => (
+ usageInfo.value?.grok_local_usage_7d || usageInfo.value?.seven_day?.window_stats || null
+))
+const grokLocalUsageMonthly = computed(() => (
+ usageInfo.value?.grok_local_usage_monthly || usageInfo.value?.thirty_day?.window_stats || null
+))
const grokWeeklyBillingBar = computed((): GrokQuotaBarInfo | null => {
const billing = grokBilling.value
if (billing?.period_type?.toLowerCase() !== 'weekly' || billing.usage_percent == null) {
@@ -1118,7 +1132,8 @@ const grokWeeklyBillingBar = computed((): GrokQuotaBarInfo | null => {
}
return {
utilization: Math.min(100, Math.max(0, billing.usage_percent)),
- resetsAt: billing.period_end || null
+ resetsAt: billing.period_end || null,
+ windowStats: grokLocalUsage7d.value
}
})
// Monthly used/limit % from billing probe (used_percent or derived from cents).
@@ -1142,7 +1157,8 @@ const grokMonthlyBillingBar = computed((): GrokQuotaBarInfo | null => {
}
return {
utilization: Math.min(100, Math.max(0, utilization)),
- resetsAt: billing.billing_period_end || billing.period_end || null
+ resetsAt: billing.billing_period_end || billing.period_end || null,
+ windowStats: grokLocalUsageMonthly.value
}
})
const formatGrokMoney = (value?: number | null) => {
@@ -1152,30 +1168,33 @@ const formatGrokMoney = (value?: number | null) => {
if (value >= 10) return value.toFixed(1)
return value.toFixed(2)
}
-// 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.
+// Prepaid chip only when there is a positive prepaid balance.
+// Used/limit only when monthly limit is a positive number (0 means unlimited / unset).
const grokPrepaidMoneyLine = computed(() => {
const billing = grokBilling.value
if (!billing) return null
const prepaid = billing.prepaid_balance
- // "只针对预付": only render when prepaid field exists (including $0.00).
- if (prepaid == null || !Number.isFinite(prepaid)) return null
+ const showPrepaid = prepaid != null && Number.isFinite(prepaid) && prepaid > 0
+ const limitRaw =
+ billing.monthly_limit != null
+ ? billing.monthly_limit
+ : billing.monthly_limit_cents != null
+ ? billing.monthly_limit_cents / 100
+ : null
+ const showUsedLimit = limitRaw != null && Number.isFinite(limitRaw) && limitRaw > 0
+ if (!showPrepaid && !showUsedLimit) return null
const used =
billing.monthly_used != null
? billing.monthly_used
: billing.used_cents != null
? billing.used_cents / 100
: 0
- const limit =
- billing.monthly_limit != null
- ? billing.monthly_limit
- : billing.monthly_limit_cents != null
- ? billing.monthly_limit_cents / 100
- : 0
return {
- prepaid: formatGrokMoney(prepaid),
- used: formatGrokMoney(used),
- limit: formatGrokMoney(limit)
+ showPrepaid,
+ showUsedLimit,
+ prepaid: showPrepaid ? formatGrokMoney(prepaid) : null,
+ used: showUsedLimit ? formatGrokMoney(used) : null,
+ limit: showUsedLimit ? formatGrokMoney(limitRaw) : null
}
})
const grokPlanLabelIsFree = (value: string) => value.includes('free') || value.includes('basic')
diff --git a/frontend/src/components/account/__tests__/AccountUsageCell.spec.ts b/frontend/src/components/account/__tests__/AccountUsageCell.spec.ts
index dc071b6de5..1ad6866dd7 100644
--- a/frontend/src/components/account/__tests__/AccountUsageCell.spec.ts
+++ b/frontend/src/components/account/__tests__/AccountUsageCell.spec.ts
@@ -1017,6 +1017,245 @@ describe('AccountUsageCell', () => {
expect(wrapper.text()).toContain('24h|100')
})
+ it('Grok Free 24h bar shows rolling local usage chips', async () => {
+ getUsage.mockResolvedValue({
+ grok_free_token_limit: 1_000_000,
+ grok_billing: { period_type: 'weekly', usage_percent: null, plan: '' },
+ grok_local_usage: {
+ requests: 2,
+ tokens: 250_000,
+ cost: 0,
+ standard_cost: 0
+ },
+ grok_local_usage_24h: {
+ requests: 12,
+ tokens: 750_000,
+ cost: 0.12,
+ standard_cost: 0.12,
+ user_cost: 0.04
+ }
+ })
+
+ const wrapper = mount(AccountUsageCell, {
+ props: {
+ account: makeAccount({ id: 4410, platform: 'grok', type: 'oauth', extra: {} })
+ },
+ global: {
+ stubs: {
+ UsageProgressBar: {
+ props: ['label', 'utilization', 'windowStats'],
+ template: '{{ label }}|{{ utilization }}|{{ windowStats?.tokens }}
'
+ },
+ AccountQuotaInfo: true
+ }
+ }
+ })
+
+ await flushPromises()
+ expect(wrapper.text()).toContain('24h|75|750000')
+ expect(wrapper.text()).not.toContain('|250000')
+ expect(wrapper.text()).not.toContain('7d|')
+ })
+
+ it('Grok SuperGrok and Heavy bars show period-aligned local 7d and 30d usage', async () => {
+ getUsage.mockResolvedValue({
+ subscription_tier: 'SuperGrok Heavy',
+ grok_billing: {
+ period_type: 'weekly',
+ usage_percent: 37,
+ used_percent: 12,
+ monthly_limit_cents: 150_000,
+ period_end: '2026-07-16T03:25:00Z',
+ billing_period_end: '2026-08-01T00:00:00Z',
+ plan: 'SuperGrok Heavy'
+ },
+ grok_local_usage: {
+ requests: 1,
+ tokens: 99,
+ cost: 0,
+ standard_cost: 0
+ },
+ grok_local_usage_24h: {
+ requests: 2,
+ tokens: 100,
+ cost: 0,
+ standard_cost: 0
+ },
+ grok_local_usage_7d: {
+ requests: 8,
+ tokens: 2_200_000,
+ cost: 4.42,
+ standard_cost: 4.42
+ },
+ grok_local_usage_monthly: {
+ requests: 20,
+ tokens: 8_000_000,
+ cost: 18.5,
+ standard_cost: 18.5
+ }
+ })
+
+ const wrapper = mount(AccountUsageCell, {
+ props: {
+ account: makeAccount({ id: 4411, platform: 'grok', type: 'oauth', extra: {} })
+ },
+ global: {
+ stubs: {
+ UsageProgressBar: {
+ props: ['label', 'utilization', 'windowStats'],
+ template: '{{ label }}|{{ utilization }}|{{ windowStats?.tokens }}
'
+ },
+ AccountQuotaInfo: true
+ }
+ }
+ })
+
+ await flushPromises()
+ expect(wrapper.text()).toContain('7d|37|2200000')
+ expect(wrapper.text()).toContain('30d|12|8000000')
+ expect(wrapper.text()).not.toContain('|99')
+ expect(wrapper.text()).not.toContain('|100')
+ expect(wrapper.text()).not.toContain('24h|')
+ })
+
+ it('Grok paid bars fall back to official seven_day and thirty_day window_stats', async () => {
+ getUsage.mockResolvedValue({
+ subscription_tier: 'SuperGrok',
+ grok_billing: {
+ period_type: 'weekly',
+ usage_percent: 20,
+ used_percent: 8,
+ monthly_limit_cents: 25_000,
+ plan: 'SuperGrok'
+ },
+ seven_day: {
+ utilization: 20,
+ window_stats: {
+ requests: 6,
+ tokens: 1_500_000,
+ cost: 3.1,
+ standard_cost: 3.1
+ }
+ },
+ thirty_day: {
+ utilization: 8,
+ window_stats: {
+ requests: 14,
+ tokens: 4_400_000,
+ cost: 9.2,
+ standard_cost: 9.2
+ }
+ }
+ })
+
+ const wrapper = mount(AccountUsageCell, {
+ props: {
+ account: makeAccount({ id: 4412, platform: 'grok', type: 'oauth', extra: {} })
+ },
+ global: {
+ stubs: {
+ UsageProgressBar: {
+ props: ['label', 'utilization', 'windowStats'],
+ template: '{{ label }}|{{ utilization }}|{{ windowStats?.tokens }}
'
+ },
+ AccountQuotaInfo: true
+ }
+ }
+ })
+
+ await flushPromises()
+ expect(wrapper.text()).toContain('7d|20|1500000')
+ expect(wrapper.text()).toContain('30d|8|4400000')
+ })
+
+ it('Grok paid hides zero prepaid and hides used/limit when monthly limit is 0', async () => {
+ getUsage.mockResolvedValue({
+ subscription_tier: 'SuperGrok',
+ grok_billing: {
+ period_type: 'weekly',
+ usage_percent: 20,
+ prepaid_balance: 0,
+ monthly_limit: 0,
+ monthly_used: 3.5,
+ plan: 'SuperGrok'
+ }
+ })
+
+ const wrapper = mount(AccountUsageCell, {
+ props: {
+ account: makeAccount({ id: 4413, platform: 'grok', type: 'oauth', extra: {} })
+ },
+ global: {
+ stubs: {
+ UsageProgressBar: true,
+ AccountQuotaInfo: true
+ }
+ }
+ })
+
+ await flushPromises()
+ expect(wrapper.text()).not.toContain('admin.accounts.usageWindow.grokPrepaid')
+ expect(wrapper.text()).not.toContain('admin.accounts.usageWindow.grokUsed')
+ expect(wrapper.text()).not.toContain('3.5/0')
+ })
+
+ it('Grok paid shows used/limit without prepaid, and prepaid without a zero monthly limit', async () => {
+ getUsage.mockResolvedValueOnce({
+ subscription_tier: 'SuperGrok',
+ grok_billing: {
+ period_type: 'weekly',
+ usage_percent: 20,
+ monthly_limit: 25,
+ monthly_used: 3.5,
+ plan: 'SuperGrok'
+ }
+ })
+
+ const usedOnly = mount(AccountUsageCell, {
+ props: {
+ account: makeAccount({ id: 4414, platform: 'grok', type: 'oauth', extra: {} })
+ },
+ global: {
+ stubs: {
+ UsageProgressBar: true,
+ AccountQuotaInfo: true
+ }
+ }
+ })
+ await flushPromises()
+ expect(usedOnly.text()).not.toContain('admin.accounts.usageWindow.grokPrepaid')
+ expect(usedOnly.text()).toContain('admin.accounts.usageWindow.grokUsed')
+ expect(usedOnly.text()).toContain('3.50/25.0')
+
+ getUsage.mockResolvedValueOnce({
+ subscription_tier: 'SuperGrok Heavy',
+ grok_billing: {
+ period_type: 'weekly',
+ usage_percent: 20,
+ prepaid_balance: 12.5,
+ monthly_limit: 0,
+ monthly_used: 8,
+ plan: 'SuperGrok Heavy'
+ }
+ })
+ const prepaidOnly = mount(AccountUsageCell, {
+ props: {
+ account: makeAccount({ id: 4415, platform: 'grok', type: 'oauth', extra: {} })
+ },
+ global: {
+ stubs: {
+ UsageProgressBar: true,
+ AccountQuotaInfo: true
+ }
+ }
+ })
+ await flushPromises()
+ expect(prepaidOnly.text()).toContain('admin.accounts.usageWindow.grokPrepaid')
+ expect(prepaidOnly.text()).toContain('$12.5')
+ expect(prepaidOnly.text()).not.toContain('admin.accounts.usageWindow.grokUsed')
+ expect(prepaidOnly.text()).not.toContain('8.00/0')
+ })
+
it('Key 账号在 today stats loading 时显示骨架屏', async () => {
const wrapper = mount(AccountUsageCell, {
props: {