mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-29 08:11:32 +00:00
fix(grok): serve current free quota policy
This commit is contained in:
@@ -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} 后重试',
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user