mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 03:52:26 +08:00
feat(accounts): show estimated OpenAI weekly cost
This commit is contained in:
@@ -134,6 +134,7 @@
|
||||
:utilization="usageInfo.seven_day.utilization"
|
||||
:resets-at="usageInfo.seven_day.resets_at"
|
||||
:window-stats="usageInfo.seven_day.window_stats"
|
||||
:estimated-total-cost="openAISevenDayEstimatedTotalCost"
|
||||
:show-now-when-idle="true"
|
||||
color="emerald"
|
||||
/>
|
||||
@@ -784,6 +785,25 @@ const hasOpenAIUsageFallback = computed(() => {
|
||||
return !!usageInfo.value?.five_hour || !!usageInfo.value?.seven_day
|
||||
})
|
||||
|
||||
const openAISevenDayEstimatedTotalCost = computed(() => {
|
||||
const sevenDay = usageInfo.value?.seven_day
|
||||
const utilization = sevenDay?.utilization
|
||||
const currentCost = sevenDay?.window_stats?.cost
|
||||
if (
|
||||
typeof utilization !== 'number' ||
|
||||
typeof currentCost !== 'number' ||
|
||||
!Number.isFinite(utilization) ||
|
||||
!Number.isFinite(currentCost) ||
|
||||
utilization <= 0 ||
|
||||
currentCost <= 0
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
const estimate = (currentCost * 100) / utilization
|
||||
return Number.isFinite(estimate) && estimate > 0 ? estimate : null
|
||||
})
|
||||
|
||||
const openAIUsageRefreshKey = computed(() => buildOpenAIUsageRefreshKey(props.account))
|
||||
|
||||
const shouldAutoLoadUsageOnMount = computed(() => {
|
||||
|
||||
@@ -22,6 +22,14 @@
|
||||
>
|
||||
U ${{ formatUserCost }}
|
||||
</span>
|
||||
<span
|
||||
v-if="estimatedTotalCost != null"
|
||||
data-test="estimated-total-cost"
|
||||
class="rounded bg-gray-100 px-1.5 py-0.5 dark:bg-gray-800"
|
||||
:title="t('admin.accounts.usageWindow.estimatedTotalCostTooltip')"
|
||||
>
|
||||
{{ t('admin.accounts.usageWindow.estimatedTotalCost', { cost: estimatedTotalCost.toFixed(2) }) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -67,6 +75,7 @@ const props = withDefaults(
|
||||
resetsAt?: string | null
|
||||
color: 'indigo' | 'emerald' | 'purple' | 'amber'
|
||||
windowStats?: WindowStats | null
|
||||
estimatedTotalCost?: number | null
|
||||
showNowWhenIdle?: boolean
|
||||
remainingCapacity?: boolean
|
||||
/** fixed: 定宽居中徽章(账号页纵向对齐);auto: 限宽截断左对齐(监控页组合标签) */
|
||||
|
||||
@@ -441,6 +441,84 @@ describe('AccountUsageCell', () => {
|
||||
expect(wrapper.text()).toContain('7d|36|900')
|
||||
})
|
||||
|
||||
it('仅为 OpenAI OAuth 7d 窗口计算预计总费用', async () => {
|
||||
getUsage.mockResolvedValue({
|
||||
five_hour: {
|
||||
utilization: 25,
|
||||
resets_at: null,
|
||||
remaining_seconds: 0,
|
||||
window_stats: { requests: 1, tokens: 100, cost: 2 }
|
||||
},
|
||||
seven_day: {
|
||||
utilization: 40,
|
||||
resets_at: null,
|
||||
remaining_seconds: 0,
|
||||
window_stats: { requests: 2, tokens: 200, cost: 12 }
|
||||
}
|
||||
})
|
||||
|
||||
const wrapper = mount(AccountUsageCell, {
|
||||
props: {
|
||||
account: makeAccount({ id: 6752, platform: 'openai', type: 'oauth' })
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
UsageProgressBar: {
|
||||
props: ['label', 'estimatedTotalCost'],
|
||||
template: '<div class="usage-bar">{{ label }}|{{ estimatedTotalCost ?? "none" }}</div>'
|
||||
},
|
||||
AccountQuotaInfo: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('5h|none')
|
||||
expect(wrapper.text()).toContain('7d|30')
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ id: 6801, utilization: 0, cost: 12 },
|
||||
{ id: 6802, utilization: -1, cost: 12 },
|
||||
{ id: 6803, utilization: Number.NaN, cost: 12 },
|
||||
{ id: 6804, utilization: 40, cost: 0 },
|
||||
{ id: 6805, utilization: 40, cost: Number.POSITIVE_INFINITY }
|
||||
])('OpenAI OAuth 7d 输入无效时不显示预计总费用 (%o)', async ({ id, utilization, cost }) => {
|
||||
getUsage.mockResolvedValue({
|
||||
seven_day: {
|
||||
utilization,
|
||||
resets_at: null,
|
||||
remaining_seconds: 0,
|
||||
window_stats: { requests: 1, tokens: 100, cost }
|
||||
}
|
||||
})
|
||||
|
||||
const wrapper = mount(AccountUsageCell, {
|
||||
props: {
|
||||
account: makeAccount({
|
||||
id,
|
||||
platform: 'openai',
|
||||
type: 'oauth'
|
||||
})
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
UsageProgressBar: {
|
||||
props: ['label', 'estimatedTotalCost'],
|
||||
template: '<div class="usage-bar">{{ label }}|{{ estimatedTotalCost ?? "none" }}</div>'
|
||||
},
|
||||
AccountQuotaInfo: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('7d|none')
|
||||
expect(wrapper.text()).not.toMatch(/Infinity|NaN/)
|
||||
})
|
||||
|
||||
it('OpenAI OAuth 有现成快照时,手动刷新信号会触发 usage 重拉', async () => {
|
||||
getUsage.mockResolvedValue({
|
||||
five_hour: {
|
||||
|
||||
@@ -1534,7 +1534,9 @@ export default {
|
||||
grokLastProbe: 'Probe {time}',
|
||||
grokLastHeadersSeen: 'Headers {time}',
|
||||
passiveSampled: 'Passive',
|
||||
activeQuery: 'Query'
|
||||
activeQuery: 'Query',
|
||||
estimatedTotalCost: 'Est. total ${cost}',
|
||||
estimatedTotalCostTooltip: 'Estimated total cost at 100% utilization, based on current window cost and utilization'
|
||||
},
|
||||
openaiQuotaReset: {
|
||||
count: 'Credits',
|
||||
|
||||
@@ -479,7 +479,9 @@ export default {
|
||||
grokLastProbe: '探测 {time}',
|
||||
grokLastHeadersSeen: '响应头 {time}',
|
||||
passiveSampled: '被动采样',
|
||||
activeQuery: '查询'
|
||||
activeQuery: '查询',
|
||||
estimatedTotalCost: '预计总费用 ${cost}',
|
||||
estimatedTotalCostTooltip: '根据当前窗口费用和使用率估算达到 100% 使用率时的总费用'
|
||||
},
|
||||
openaiQuotaReset: {
|
||||
count: '次数',
|
||||
|
||||
Reference in New Issue
Block a user