fix(grok): serve current free quota policy

This commit is contained in:
superman2003
2026-07-19 12:17:52 +08:00
parent c99e479bcc
commit d2753dc2e2
12 changed files with 65 additions and 23 deletions
@@ -408,7 +408,7 @@
<UsageProgressBar
v-if="grokFreeTokenBar"
label="24h"
:title="t('admin.accounts.usageWindow.grokFreeQuota24hHint')"
:title="t('admin.accounts.usageWindow.grokFreeQuota24hHint', { limit: formatCompactNumber(grokFreeTokenBar.limit) })"
:utilization="grokFreeTokenBar.utilization"
:show-now-when-idle="true"
color="emerald"
@@ -631,8 +631,6 @@ import GrokQuotaProbeCell from './GrokQuotaProbeCell.vue'
// Module-level cache shared across all AccountUsageCell instances
const _usageCache = new Map<number, { data: AccountUsageInfo; ts: number }>()
const USAGE_CACHE_TTL = 5 * 60 * 1000 // 5 minutes
// xAI Free billing exposes a window without usage_percent, so estimate it from local tokens.
const GROK_FREE_TOKEN_LIMIT = 2_000_000
const props = withDefaults(
defineProps<{
@@ -1112,8 +1110,10 @@ const grokLocalUsage = computed(() => {
})
const grokFreeTokenBar = computed(() => {
if (!grokIsFree.value || !grokFreeQuotaUsage.value) return null
const limit = usageInfo.value?.grok_free_token_limit
if (typeof limit !== 'number' || limit <= 0) return null
const used = Math.max(0, grokFreeQuotaUsage.value.tokens || 0)
return { utilization: Math.min(100, (used / GROK_FREE_TOKEN_LIMIT) * 100) }
return { utilization: Math.min(100, (used / limit) * 100), limit }
})
const grokQuotaUnknown = computed(() => {
if (props.account.platform !== 'grok') return false
@@ -705,11 +705,12 @@ describe('AccountUsageCell', () => {
it.each([
{ tokens: 0, expected: 0, compact: '0' },
{ tokens: 1_000_000, expected: 50, compact: '1.0M' },
{ tokens: 2_000_000, expected: 100, compact: '2.0M' },
{ tokens: 2_200_000, expected: 100, compact: '2.2M' }
])('Grok Free derives its 2M quota from local tokens: $tokens -> $expected%', async ({ tokens, expected, compact }) => {
{ 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 }) => {
getUsage.mockResolvedValue({
grok_free_token_limit: 1_000_000,
grok_billing: {
period_type: 'weekly',
usage_percent: null,
@@ -723,7 +724,7 @@ describe('AccountUsageCell', () => {
user_cost: 0
},
grok_request_quota: { limit: 100, remaining: 100 },
grok_token_quota: { limit: 2_000_000, remaining: 2_000_000 }
grok_token_quota: { limit: 1_000_000, remaining: 1_000_000 }
})
const wrapper = mount(AccountUsageCell, {
@@ -753,6 +754,7 @@ describe('AccountUsageCell', () => {
it('Grok Free uses rolling 24h usage instead of today-only usage', async () => {
getUsage.mockResolvedValue({
grok_free_token_limit: 1_000_000,
grok_billing: { period_type: 'weekly', usage_percent: null, plan: '' },
grok_local_usage: {
requests: 2,
@@ -762,7 +764,7 @@ describe('AccountUsageCell', () => {
},
grok_local_usage_24h: {
requests: 12,
tokens: 1_500_000,
tokens: 750_000,
cost: 0,
standard_cost: 0
}
@@ -793,7 +795,7 @@ describe('AccountUsageCell', () => {
await flushPromises()
expect(wrapper.text()).toContain('24h|75|admin.accounts.usageWindow.grokFreeQuota24hHint')
expect(wrapper.text()).toContain('1.5M')
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')
@@ -801,6 +803,7 @@ describe('AccountUsageCell', () => {
it('Grok Free does not substitute today stats when rolling 24h usage is unavailable', async () => {
getUsage.mockResolvedValue({
grok_free_token_limit: 1_000_000,
grok_billing: { period_type: 'weekly', usage_percent: null, plan: '' },
grok_local_usage: {
requests: 1,
@@ -921,8 +924,9 @@ describe('AccountUsageCell', () => {
expect(wrapper.text()).not.toContain('2M|')
})
it('Grok credential Free tier keeps the 2M fallback when billing is unavailable', async () => {
it('Grok credential Free tier keeps the 1M fallback when billing is unavailable', async () => {
getUsage.mockResolvedValue({
grok_free_token_limit: 1_000_000,
subscription_tier: 'FREE',
grok_local_usage_24h: {
requests: 3,
@@ -950,7 +954,7 @@ describe('AccountUsageCell', () => {
await flushPromises()
expect(wrapper.text()).toContain('24h|50')
expect(wrapper.text()).toContain('24h|100')
})
it('Grok paid manual probes keep the weekly/local summary when 24h usage is returned', async () => {
@@ -1165,6 +1169,7 @@ describe('AccountUsageCell', () => {
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'
})
@@ -1185,7 +1190,7 @@ describe('AccountUsageCell', () => {
template: `<button class="probe" @click="$emit('probed', {
source: 'hybrid_probe',
billing: { period_type: 'weekly', usage_percent: null, plan: '' },
local_usage_24h: { requests: 12, tokens: 1500000, cost: 0, standard_cost: 0 },
local_usage_24h: { requests: 12, tokens: 750000, cost: 0, standard_cost: 0 },
headers_observed: false,
reset_supported: false,
fetched_at: 1
@@ -1199,7 +1204,7 @@ describe('AccountUsageCell', () => {
await wrapper.get('.probe').trigger('click')
expect(wrapper.text()).toContain('24h|75')
expect(wrapper.text()).toContain('1.5M')
expect(wrapper.text()).toContain('750.0K')
expect(wrapper.text()).not.toContain('7d|')
})
@@ -1276,7 +1276,7 @@ export default {
claude: 'Claude',
grokRequests: 'Req',
grokTokens: 'Tok',
grokFreeQuota24hHint: 'Estimated from local token usage over the rolling 24-hour window (2M limit)',
grokFreeQuota24hHint: 'Estimated from local token usage over the rolling 24-hour window ({limit} limit)',
grokWeeklyUsage: 'Weekly {percent}%',
grokUnknown: 'Grok quota is unknown until the first upstream response includes xAI rate-limit headers.',
grokRetryAfter: 'Retry after {time}',
@@ -351,7 +351,7 @@ export default {
claude: 'Claude',
grokRequests: '请求',
grokTokens: 'Token',
grokFreeQuota24hHint: '按 sub2api 近 24 小时本地 Token 用量估算(上限 2M)',
grokFreeQuota24hHint: '按 sub2api 近 24 小时本地 Token 用量估算(上限 {limit})',
grokWeeklyUsage: '周额度已用 {percent}%',
grokUnknown: 'Grok 配额需等待首次上游响应返回 xAI rate-limit 头后显示。',
grokRetryAfter: '{time} 后重试',
+1
View File
@@ -1120,6 +1120,7 @@ export interface AccountUsageInfo {
grok_last_quota_probe_at?: string
grok_last_headers_seen_at?: string
grok_last_status_code?: number
grok_free_token_limit?: number
grok_local_usage?: WindowStats | null
grok_local_usage_24h?: WindowStats | null
grok_local_usage_7d?: WindowStats | null