fix(grok): use rolling 24h free quota estimate

This commit is contained in:
superman2003
2026-07-14 10:53:43 +08:00
parent 5d1c577cb2
commit 30d4301bea
9 changed files with 235 additions and 44 deletions
+1
View File
@@ -102,6 +102,7 @@ export interface GrokQuotaProbeResult {
model?: string
billing?: GrokBillingSummary | null
snapshot?: GrokQuotaSnapshot | null
local_usage_24h?: WindowStats | null
local_usage_7d?: WindowStats | null
local_usage_monthly?: WindowStats | null
status_code?: number
@@ -407,7 +407,8 @@
/>
<UsageProgressBar
v-if="grokFreeTokenBar"
label="2M"
label="24h"
:title="t('admin.accounts.usageWindow.grokFreeQuota24hHint')"
:utilization="grokFreeTokenBar.utilization"
:show-now-when-idle="true"
color="emerald"
@@ -1065,19 +1066,6 @@ const makeGrokQuotaBar = (quota?: { limit?: number | null; remaining?: number |
const grokRequestQuotaBar = computed(() => makeGrokQuotaBar(usageInfo.value?.grok_request_quota))
const grokTokenQuotaBar = computed(() => makeGrokQuotaBar(usageInfo.value?.grok_token_quota))
const grokLocalUsage = computed(() =>
props.todayStats ||
usageInfo.value?.grok_local_usage ||
usageInfo.value?.grok_local_usage_7d ||
usageInfo.value?.grok_local_usage_monthly ||
null
)
const grokFreeQuotaUsage = computed(() =>
usageInfo.value?.grok_local_usage_7d ||
props.todayStats ||
usageInfo.value?.grok_local_usage ||
null
)
const grokBilling = computed(() => usageInfo.value?.grok_billing || null)
const grokWeeklyBillingBar = computed((): GrokQuotaBarInfo | null => {
const billing = grokBilling.value
@@ -1113,6 +1101,15 @@ const grokIsFree = computed(() => {
) return true
return billing != null
})
const grokFreeQuotaUsage = computed(() => usageInfo.value?.grok_local_usage_24h || null)
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 used = Math.max(0, grokFreeQuotaUsage.value.tokens || 0)
@@ -1360,6 +1357,7 @@ const handleGrokProbed = (result: GrokQuotaProbeResult) => {
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,
@@ -715,7 +715,7 @@ describe('AccountUsageCell', () => {
usage_percent: null,
plan: ''
},
grok_local_usage: {
grok_local_usage_24h: {
requests: 5,
tokens,
cost: 0,
@@ -744,23 +744,23 @@ describe('AccountUsageCell', () => {
await flushPromises()
expect(wrapper.text()).toContain(`2M|${expected}`)
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|')
})
it('Grok Free uses the weekly billing window instead of today-only usage', async () => {
it('Grok Free uses rolling 24h usage instead of today-only usage', async () => {
getUsage.mockResolvedValue({
grok_billing: { period_type: 'weekly', usage_percent: null, plan: '' },
grok_local_usage: {
requests: 2,
tokens: 200_000,
tokens: 250_000,
cost: 0,
standard_cost: 0
},
grok_local_usage_7d: {
grok_local_usage_24h: {
requests: 12,
tokens: 1_500_000,
cost: 0,
@@ -770,13 +770,19 @@ describe('AccountUsageCell', () => {
const wrapper = mount(AccountUsageCell, {
props: {
account: makeAccount({ id: 4398, platform: 'grok', type: 'oauth', extra: {} })
account: makeAccount({ id: 4398, platform: 'grok', type: 'oauth', extra: {} }),
todayStats: {
requests: 2,
tokens: 200_000,
cost: 0,
standard_cost: 0
}
},
global: {
stubs: {
UsageProgressBar: {
props: ['label', 'utilization'],
template: '<div class="usage-bar">{{ label }}|{{ utilization }}</div>'
props: ['label', 'utilization', 'title'],
template: '<div class="usage-bar">{{ label }}|{{ utilization }}|{{ title }}</div>'
},
AccountQuotaInfo: true,
GrokQuotaProbeCell: true
@@ -786,11 +792,14 @@ describe('AccountUsageCell', () => {
await flushPromises()
expect(wrapper.text()).toContain('2M|75')
expect(wrapper.text()).toContain('200.0K')
expect(wrapper.text()).toContain('24h|75|admin.accounts.usageWindow.grokFreeQuota24hHint')
expect(wrapper.text()).toContain('1.5M')
expect(wrapper.text()).not.toContain('7d|')
expect(wrapper.text()).not.toContain('200.0K')
expect(wrapper.text()).not.toContain('250.0K')
})
it('Grok Free falls back to refreshed today stats when weekly usage is unavailable', async () => {
it('Grok Free does not substitute today stats when rolling 24h usage is unavailable', async () => {
getUsage.mockResolvedValue({
grok_billing: { period_type: 'weekly', usage_percent: null, plan: '' },
grok_local_usage: {
@@ -827,9 +836,10 @@ describe('AccountUsageCell', () => {
await flushPromises()
expect(wrapper.text()).toContain('2M|50')
expect(wrapper.text()).toContain('1.0M')
expect(wrapper.text()).not.toContain('250K')
expect(wrapper.findAll('.usage-bar')).toHaveLength(0)
expect(wrapper.text()).not.toContain('24h|')
expect(wrapper.text()).not.toContain('1.0M')
expect(wrapper.text()).not.toContain('250.0K')
})
it('Grok paid plans are not mistaken for Free when weekly usage is temporarily missing', async () => {
@@ -914,7 +924,7 @@ describe('AccountUsageCell', () => {
it('Grok credential Free tier keeps the 2M fallback when billing is unavailable', async () => {
getUsage.mockResolvedValue({
subscription_tier: 'FREE',
grok_local_usage: {
grok_local_usage_24h: {
requests: 3,
tokens: 1_000_000,
cost: 0,
@@ -940,10 +950,10 @@ describe('AccountUsageCell', () => {
await flushPromises()
expect(wrapper.text()).toContain('2M|50')
expect(wrapper.text()).toContain('24h|50')
})
it('Grok manual probes merge billing, quota headers, and local usage', async () => {
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',
@@ -972,6 +982,7 @@ describe('AccountUsageCell', () => {
entitlement_status: 'ACTIVE',
requests: { limit: 100, remaining: 20 }
},
local_usage_24h: { requests: 3, tokens: 750000, cost: 0.75, standard_cost: 0.75, user_cost: 0.25 },
local_usage_7d: { requests: 4, tokens: 1000000, cost: 1, standard_cost: 1, user_cost: 0.5 },
local_usage_monthly: { requests: 7, tokens: 1500000, cost: 2, standard_cost: 2, user_cost: 1 },
status_code: 200,
@@ -989,10 +1000,51 @@ describe('AccountUsageCell', () => {
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 Free manual probes merge rolling 24h usage', async () => {
getUsage.mockResolvedValue({
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: '<div class="usage-bar">{{ label }}|{{ utilization }}</div>'
},
AccountQuotaInfo: true,
GrokQuotaProbeCell: {
emits: ['probed'],
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 },
headers_observed: false,
reset_supported: false,
fetched_at: 1
})">probe</button>`
}
}
}
})
await flushPromises()
await wrapper.get('.probe').trigger('click')
expect(wrapper.text()).toContain('24h|75')
expect(wrapper.text()).toContain('1.5M')
expect(wrapper.text()).not.toContain('7d|')
})
it('Key 账号在 today stats loading 时显示骨架屏', async () => {
const wrapper = mount(AccountUsageCell, {
props: {
@@ -1213,6 +1213,7 @@ export default {
claude: 'Claude',
grokRequests: 'Req',
grokTokens: 'Tok',
grokFreeQuota24hHint: 'Estimated from local token usage over the rolling 24-hour window (2M limit)',
grokWeeklyUsage: 'Weekly {percent}%',
grokUnknown: 'Grok quota is unknown until the first upstream response includes xAI rate-limit headers.',
grokRetryAfter: 'Retry after {time}',
@@ -317,6 +317,7 @@ export default {
claude: 'Claude',
grokRequests: '请求',
grokTokens: 'Token',
grokFreeQuota24hHint: '按 sub2api 近 24 小时本地 Token 用量估算(上限 2M)',
grokWeeklyUsage: '周额度已用 {percent}%',
grokUnknown: 'Grok 配额需等待首次上游响应返回 xAI rate-limit 头后显示。',
grokRetryAfter: '{time} 后重试',
+1
View File
@@ -1077,6 +1077,7 @@ export interface AccountUsageInfo {
grok_last_headers_seen_at?: string
grok_last_status_code?: number
grok_local_usage?: WindowStats | null
grok_local_usage_24h?: WindowStats | null
grok_local_usage_7d?: WindowStats | null
grok_local_usage_monthly?: WindowStats | null
grok_billing?: GrokBillingSummary | null