mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-06 16:23:18 +08:00
Merge pull request #5765 from IanShaw027/feat/grok-from-personal-main
feat(grok): 用量条补齐本站 24h/7d/30d 聚合,并隐藏空预付与 0 限额已用
This commit is contained in:
@@ -365,6 +365,7 @@
|
||||
label="24h"
|
||||
:title="t('admin.accounts.usageWindow.grokFreeQuota24hHint', { limit: formatCompactNumber(grokFreeTokenBar.limit) })"
|
||||
:utilization="grokFreeTokenBar.utilization"
|
||||
:window-stats="grokFreeQuotaUsage"
|
||||
:show-now-when-idle="true"
|
||||
color="emerald"
|
||||
/>
|
||||
@@ -378,6 +379,7 @@
|
||||
label="7d"
|
||||
:utilization="grokWeeklyBillingBar.utilization"
|
||||
:resets-at="grokWeeklyBillingBar.resetsAt"
|
||||
:window-stats="grokWeeklyBillingBar.windowStats"
|
||||
:show-now-when-idle="true"
|
||||
color="indigo"
|
||||
/>
|
||||
@@ -386,6 +388,7 @@
|
||||
label="30d"
|
||||
:utilization="grokMonthlyBillingBar.utilization"
|
||||
:resets-at="grokMonthlyBillingBar.resetsAt"
|
||||
:window-stats="grokMonthlyBillingBar.windowStats"
|
||||
:show-now-when-idle="true"
|
||||
color="indigo"
|
||||
/>
|
||||
@@ -394,12 +397,16 @@
|
||||
class="flex flex-wrap items-center gap-1 text-[10px] text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<span
|
||||
v-if="grokPrepaidMoneyLine.showPrepaid"
|
||||
class="rounded bg-emerald-50 px-1 py-0.5 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300"
|
||||
:title="t('admin.accounts.usageWindow.grokPrepaid')"
|
||||
>
|
||||
{{ t('admin.accounts.usageWindow.grokPrepaid') }} ${{ grokPrepaidMoneyLine.prepaid }}
|
||||
</span>
|
||||
<span :title="t('admin.accounts.usageWindow.grokMonthlyLimit')">
|
||||
<span
|
||||
v-if="grokPrepaidMoneyLine.showUsedLimit"
|
||||
:title="t('admin.accounts.usageWindow.grokMonthlyLimit')"
|
||||
>
|
||||
{{ t('admin.accounts.usageWindow.grokUsed') }}
|
||||
{{ grokPrepaidMoneyLine.used }}/{{ grokPrepaidMoneyLine.limit }}
|
||||
</span>
|
||||
@@ -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')
|
||||
|
||||
@@ -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: '<div class="usage-bar">{{ label }}|{{ utilization }}|{{ windowStats?.tokens }}</div>'
|
||||
},
|
||||
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: '<div class="usage-bar">{{ label }}|{{ utilization }}|{{ windowStats?.tokens }}</div>'
|
||||
},
|
||||
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: '<div class="usage-bar">{{ label }}|{{ utilization }}|{{ windowStats?.tokens }}</div>'
|
||||
},
|
||||
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: {
|
||||
|
||||
Reference in New Issue
Block a user