mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-22 06:40:21 +08:00
fix(grok): display remaining quota capacity
This commit is contained in:
@@ -386,6 +386,7 @@
|
||||
:label="t('admin.accounts.usageWindow.grokRequests')"
|
||||
:utilization="grokRequestQuotaBar.utilization"
|
||||
:resets-at="grokRequestQuotaBar.resetsAt"
|
||||
:remaining-capacity="true"
|
||||
color="indigo"
|
||||
/>
|
||||
<UsageProgressBar
|
||||
@@ -393,6 +394,7 @@
|
||||
:label="t('admin.accounts.usageWindow.grokTokens')"
|
||||
:utilization="grokTokenQuotaBar.utilization"
|
||||
:resets-at="grokTokenQuotaBar.resetsAt"
|
||||
:remaining-capacity="true"
|
||||
color="emerald"
|
||||
/>
|
||||
<div v-if="grokRetryAfterLabel" class="text-[10px] text-amber-600 dark:text-amber-400">
|
||||
@@ -1036,9 +1038,9 @@ interface GrokQuotaBarInfo {
|
||||
|
||||
const makeGrokQuotaBar = (quota?: { limit?: number | null; remaining?: number | null; reset_at?: string | null } | null): GrokQuotaBarInfo | null => {
|
||||
if (!quota || quota.limit == null || quota.remaining == null || quota.limit <= 0) return null
|
||||
const used = Math.max(0, quota.limit - quota.remaining)
|
||||
const remaining = Math.min(quota.limit, Math.max(0, quota.remaining))
|
||||
return {
|
||||
utilization: (used / quota.limit) * 100,
|
||||
utilization: (remaining / quota.limit) * 100,
|
||||
resetsAt: quota.reset_at || null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ const props = defineProps<{
|
||||
color: 'indigo' | 'emerald' | 'purple' | 'amber'
|
||||
windowStats?: WindowStats | null
|
||||
showNowWhenIdle?: boolean
|
||||
remainingCapacity?: boolean
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -109,6 +110,14 @@ const labelClass = computed(() => {
|
||||
|
||||
// Progress bar color based on utilization
|
||||
const barClass = computed(() => {
|
||||
if (props.remainingCapacity) {
|
||||
if (props.utilization <= 20) {
|
||||
return 'bg-red-500'
|
||||
} else if (props.utilization <= 50) {
|
||||
return 'bg-amber-500'
|
||||
}
|
||||
return 'bg-green-500'
|
||||
}
|
||||
if (props.utilization >= 100) {
|
||||
return 'bg-red-500'
|
||||
} else if (props.utilization >= 80) {
|
||||
@@ -120,6 +129,14 @@ const barClass = computed(() => {
|
||||
|
||||
// Text color based on utilization
|
||||
const textClass = computed(() => {
|
||||
if (props.remainingCapacity) {
|
||||
if (props.utilization <= 20) {
|
||||
return 'text-red-600 dark:text-red-400'
|
||||
} else if (props.utilization <= 50) {
|
||||
return 'text-amber-600 dark:text-amber-400'
|
||||
}
|
||||
return 'text-gray-600 dark:text-gray-400'
|
||||
}
|
||||
if (props.utilization >= 100) {
|
||||
return 'text-red-600 dark:text-red-400'
|
||||
} else if (props.utilization >= 80) {
|
||||
@@ -131,12 +148,16 @@ const textClass = computed(() => {
|
||||
|
||||
// Bar width (capped at 100%)
|
||||
const barWidth = computed(() => {
|
||||
return `${Math.min(props.utilization, 100)}%`
|
||||
return `${Math.min(Math.max(props.utilization, 0), 100)}%`
|
||||
})
|
||||
|
||||
// Display percentage (cap at 999% for readability)
|
||||
const displayPercent = computed(() => {
|
||||
const percent = Math.round(props.utilization)
|
||||
const percent = Math.round(
|
||||
props.remainingCapacity
|
||||
? Math.min(Math.max(props.utilization, 0), 100)
|
||||
: props.utilization
|
||||
)
|
||||
return percent > 999 ? '>999%' : `${percent}%`
|
||||
})
|
||||
|
||||
|
||||
@@ -566,7 +566,7 @@ describe('AccountUsageCell', () => {
|
||||
expect(badges.some(node => node.attributes('title') === 'usage.userBilled')).toBe(true)
|
||||
})
|
||||
|
||||
it('Grok OAuth 会展示本地 user billed 用量并保留超限百分比', async () => {
|
||||
it('Grok OAuth 会展示本地 user billed 用量并把耗尽配额显示为 0% 剩余', async () => {
|
||||
getUsage.mockResolvedValue({
|
||||
grok_local_usage: {
|
||||
requests: 4,
|
||||
@@ -611,13 +611,55 @@ describe('AccountUsageCell', () => {
|
||||
expect(wrapper.text()).toContain('1.2K')
|
||||
expect(wrapper.text()).toContain('A $0.12')
|
||||
expect(wrapper.text()).toContain('U $0.34')
|
||||
expect(wrapper.text()).toContain('admin.accounts.usageWindow.grokRequests|120|2026-07-09T16:00:00Z')
|
||||
expect(wrapper.text()).toContain('admin.accounts.usageWindow.grokRequests|0|2026-07-09T16:00:00Z')
|
||||
|
||||
const badges = wrapper.findAll('span[title]')
|
||||
expect(badges.some(node => node.attributes('title') === 'usage.accountBilled')).toBe(true)
|
||||
expect(badges.some(node => node.attributes('title') === 'usage.userBilled')).toBe(true)
|
||||
})
|
||||
|
||||
it('Grok OAuth 配额条按剩余容量显示 100% 满格和 25% 低量', async () => {
|
||||
getUsage.mockResolvedValue({
|
||||
grok_request_quota: {
|
||||
limit: 100,
|
||||
remaining: 100,
|
||||
reset_at: '2026-07-09T16:00:00Z'
|
||||
},
|
||||
grok_token_quota: {
|
||||
limit: 1000,
|
||||
remaining: 250,
|
||||
reset_at: '2026-07-09T16:00:00Z'
|
||||
},
|
||||
grok_quota_snapshot_state: 'observed'
|
||||
})
|
||||
|
||||
const wrapper = mount(AccountUsageCell, {
|
||||
props: {
|
||||
account: makeAccount({
|
||||
id: 4073,
|
||||
platform: 'grok',
|
||||
type: 'oauth',
|
||||
extra: {}
|
||||
})
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
UsageProgressBar: {
|
||||
props: ['label', 'utilization', 'resetsAt', 'color', 'remainingCapacity'],
|
||||
template: '<div class="usage-bar">{{ label }}|{{ utilization }}|{{ remainingCapacity }}</div>'
|
||||
},
|
||||
AccountQuotaInfo: true,
|
||||
GrokQuotaProbeCell: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('admin.accounts.usageWindow.grokRequests|100|true')
|
||||
expect(wrapper.text()).toContain('admin.accounts.usageWindow.grokTokens|25|true')
|
||||
})
|
||||
|
||||
it('Key 账号在 today stats loading 时显示骨架屏', async () => {
|
||||
const wrapper = mount(AccountUsageCell, {
|
||||
props: {
|
||||
|
||||
@@ -96,4 +96,54 @@ describe('UsageProgressBar', () => {
|
||||
expect(wrapper.text()).toContain('usage.resetNow')
|
||||
expect(wrapper.text()).not.toContain('usage.resetPending')
|
||||
})
|
||||
|
||||
it('剩余容量模式在 100% 时显示满格绿色', () => {
|
||||
const wrapper = mount(UsageProgressBar, {
|
||||
props: {
|
||||
label: 'Req',
|
||||
utilization: 100,
|
||||
remainingCapacity: true,
|
||||
color: 'indigo'
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('100%')
|
||||
expect(wrapper.get('.h-1\\.5 > div').attributes('style')).toContain('width: 100%')
|
||||
expect(wrapper.get('.h-1\\.5 > div').classes()).toContain('bg-green-500')
|
||||
})
|
||||
|
||||
it('剩余容量模式在低量和耗尽时缩短并变红', async () => {
|
||||
const wrapper = mount(UsageProgressBar, {
|
||||
props: {
|
||||
label: 'Req',
|
||||
utilization: 15,
|
||||
remainingCapacity: true,
|
||||
color: 'indigo'
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('15%')
|
||||
expect(wrapper.get('.h-1\\.5 > div').attributes('style')).toContain('width: 15%')
|
||||
expect(wrapper.get('.h-1\\.5 > div').classes()).toContain('bg-red-500')
|
||||
|
||||
await wrapper.setProps({ utilization: 0 })
|
||||
|
||||
expect(wrapper.text()).toContain('0%')
|
||||
expect(wrapper.get('.h-1\\.5 > div').attributes('style')).toContain('width: 0%')
|
||||
expect(wrapper.get('.h-1\\.5 > div').classes()).toContain('bg-red-500')
|
||||
})
|
||||
|
||||
it('默认利用率模式仍把超限显示为满格红色', () => {
|
||||
const wrapper = mount(UsageProgressBar, {
|
||||
props: {
|
||||
label: '5h',
|
||||
utilization: 120,
|
||||
color: 'indigo'
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('120%')
|
||||
expect(wrapper.get('.h-1\\.5 > div').attributes('style')).toContain('width: 100%')
|
||||
expect(wrapper.get('.h-1\\.5 > div').classes()).toContain('bg-red-500')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user