fix: always show usage stats for OpenAI OAuth and hide zero-value badges

- Simplify OpenAI rendering: always fetch /usage, prefer fetched data over
  codex snapshot (snapshot serves as loading placeholder only)
- Remove dead code: preferFetchedOpenAIUsage, isOpenAICodexSnapshotStale,
  and unreachable template branch
- Add today-stats support for key accounts (req/tokens/A/U badges)
- Use formatCompactNumber for consistent number formatting
- Add A/U badge titles for clarity
- Filter zero-value window stats in UsageProgressBar to avoid empty badges
- Update tests to match new fetched-data-first behavior
This commit is contained in:
Ethan0x0000
2026-03-16 16:23:13 +08:00
parent fbffb08aae
commit 1acfc46f46
3 changed files with 292 additions and 63 deletions

View File

@@ -198,7 +198,34 @@ describe('AccountUsageCell', () => {
expect(wrapper.text()).toContain('7d|77|300')
})
it('OpenAI OAuth 有现成快照且未限额时不会首屏请求 usage', async () => {
it('OpenAI OAuth 有现成快照时首屏先显示快照再加载 usage 覆盖', async () => {
getUsage.mockResolvedValue({
five_hour: {
utilization: 18,
resets_at: '2099-03-07T12:00:00Z',
remaining_seconds: 3600,
window_stats: {
requests: 9,
tokens: 900,
cost: 0.09,
standard_cost: 0.09,
user_cost: 0.09
}
},
seven_day: {
utilization: 36,
resets_at: '2099-03-13T12:00:00Z',
remaining_seconds: 3600,
window_stats: {
requests: 9,
tokens: 900,
cost: 0.09,
standard_cost: 0.09,
user_cost: 0.09
}
}
})
const wrapper = mount(AccountUsageCell, {
props: {
account: makeAccount({
@@ -218,7 +245,7 @@ describe('AccountUsageCell', () => {
stubs: {
UsageProgressBar: {
props: ['label', 'utilization', 'resetsAt', 'windowStats', 'color'],
template: '<div class="usage-bar">{{ label }}|{{ utilization }}</div>'
template: '<div class="usage-bar">{{ label }}|{{ utilization }}|{{ windowStats?.tokens }}</div>'
},
AccountQuotaInfo: true
}
@@ -227,9 +254,80 @@ describe('AccountUsageCell', () => {
await flushPromises()
expect(getUsage).not.toHaveBeenCalled()
expect(wrapper.text()).toContain('5h|12')
expect(wrapper.text()).toContain('7d|34')
// 始终拉 usagefetched data 优先显示(包含 window_stats
expect(getUsage).toHaveBeenCalledWith(2001)
expect(wrapper.text()).toContain('5h|18|900')
expect(wrapper.text()).toContain('7d|36|900')
})
it('OpenAI OAuth 有现成快照时,手动刷新信号会触发 usage 重拉', async () => {
getUsage.mockResolvedValue({
five_hour: {
utilization: 18,
resets_at: '2099-03-07T12:00:00Z',
remaining_seconds: 3600,
window_stats: {
requests: 9,
tokens: 900,
cost: 0.09,
standard_cost: 0.09,
user_cost: 0.09
}
},
seven_day: {
utilization: 36,
resets_at: '2099-03-13T12:00:00Z',
remaining_seconds: 3600,
window_stats: {
requests: 9,
tokens: 900,
cost: 0.09,
standard_cost: 0.09,
user_cost: 0.09
}
}
})
const wrapper = mount(AccountUsageCell, {
props: {
account: makeAccount({
id: 2010,
platform: 'openai',
type: 'oauth',
extra: {
codex_usage_updated_at: '2099-03-07T10:00:00Z',
codex_5h_used_percent: 12,
codex_5h_reset_at: '2099-03-07T12:00:00Z',
codex_7d_used_percent: 34,
codex_7d_reset_at: '2099-03-13T12:00:00Z'
},
rate_limit_reset_at: null
}),
manualRefreshToken: 0
},
global: {
stubs: {
UsageProgressBar: {
props: ['label', 'utilization', 'resetsAt', 'windowStats', 'color'],
template: '<div class="usage-bar">{{ label }}|{{ utilization }}|{{ windowStats?.tokens }}</div>'
},
AccountQuotaInfo: true
}
}
})
await flushPromises()
// mount 时已经拉取一次
expect(getUsage).toHaveBeenCalledTimes(1)
await wrapper.setProps({ manualRefreshToken: 1 })
await flushPromises()
// 手动刷新再拉一次
expect(getUsage).toHaveBeenCalledTimes(2)
expect(getUsage).toHaveBeenCalledWith(2010)
// fetched data 优先显示,包含 window_stats
expect(wrapper.text()).toContain('5h|18|900')
})
it('OpenAI OAuth 在无 codex 快照时会回退显示 usage 接口窗口', async () => {
@@ -414,9 +512,96 @@ describe('AccountUsageCell', () => {
await flushPromises()
expect(getUsage).toHaveBeenCalledWith(2004)
expect(wrapper.text()).toContain('5h|100|106540000')
expect(wrapper.text()).toContain('7d|100|106540000')
expect(wrapper.text()).not.toContain('5h|0|')
expect(getUsage).toHaveBeenCalledWith(2004)
expect(wrapper.text()).toContain('5h|100|106540000')
expect(wrapper.text()).toContain('7d|100|106540000')
expect(wrapper.text()).not.toContain('5h|0|')
})
it('Key 账号会展示 today stats 徽章并带 A/U 提示', async () => {
const wrapper = mount(AccountUsageCell, {
props: {
account: makeAccount({
id: 3001,
platform: 'anthropic',
type: 'apikey'
}),
todayStats: {
requests: 1_000_000,
tokens: 1_000_000_000,
cost: 12.345,
standard_cost: 12.345,
user_cost: 6.789
}
},
global: {
stubs: {
UsageProgressBar: true,
AccountQuotaInfo: true
}
}
})
await flushPromises()
expect(wrapper.text()).toContain('1.0M req')
expect(wrapper.text()).toContain('1.0B')
expect(wrapper.text()).toContain('A $12.35')
expect(wrapper.text()).toContain('U $6.79')
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('Key 账号在 today stats loading 时显示骨架屏', async () => {
const wrapper = mount(AccountUsageCell, {
props: {
account: makeAccount({
id: 3002,
platform: 'anthropic',
type: 'apikey'
}),
todayStats: null,
todayStatsLoading: true
},
global: {
stubs: {
UsageProgressBar: true,
AccountQuotaInfo: true
}
}
})
await flushPromises()
expect(wrapper.findAll('.animate-pulse').length).toBeGreaterThan(0)
})
it('Key 账号在无 today stats 且无配额时显示兜底短横线', async () => {
const wrapper = mount(AccountUsageCell, {
props: {
account: makeAccount({
id: 3003,
platform: 'anthropic',
type: 'apikey',
quota_limit: 0,
quota_daily_limit: 0,
quota_weekly_limit: 0
}),
todayStats: null,
todayStatsLoading: false
},
global: {
stubs: {
UsageProgressBar: true,
AccountQuotaInfo: true
}
}
})
await flushPromises()
expect(wrapper.text().trim()).toBe('-')
})
})