mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
fix(usage): 显示缓存 Token 明细
This commit is contained in:
@@ -14,6 +14,8 @@ export interface AdminUsageStatsResponse {
|
||||
total_input_tokens: number
|
||||
total_output_tokens: number
|
||||
total_cache_tokens: number
|
||||
total_cache_creation_tokens: number
|
||||
total_cache_read_tokens: number
|
||||
total_tokens: number
|
||||
total_cost: number
|
||||
total_actual_cost: number
|
||||
|
||||
@@ -15,9 +15,46 @@
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500">{{ t('usage.totalTokens') }}</p>
|
||||
<p class="text-xl font-bold">{{ formatTokens(stats?.total_tokens || 0) }}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ t('usage.in') }}: {{ formatTokens(stats?.total_input_tokens || 0) }} /
|
||||
{{ t('usage.out') }}: {{ formatTokens(stats?.total_output_tokens || 0) }}
|
||||
<p class="flex flex-wrap items-center gap-x-1 text-xs text-gray-500">
|
||||
<span>{{ t('usage.in') }}: {{ formatTokens(stats?.total_input_tokens || 0) }}</span>
|
||||
<span>/</span>
|
||||
<span>{{ t('usage.out') }}: {{ formatTokens(stats?.total_output_tokens || 0) }}</span>
|
||||
<span>/</span>
|
||||
<span class="group relative inline-flex cursor-help items-center gap-0.5" tabindex="0">
|
||||
<span>{{ cacheLabel() }}: {{ formatTokens(stats?.total_cache_tokens || 0) }}</span>
|
||||
<svg
|
||||
class="h-3.5 w-3.5 text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="pointer-events-none absolute left-1/2 top-full z-30 mt-2 w-56 -translate-x-1/2 rounded-lg border border-gray-200 bg-white p-3 text-left text-xs text-gray-700 opacity-0 shadow-lg transition-opacity duration-150 group-hover:opacity-100 group-focus:opacity-100 dark:border-dark-600 dark:bg-dark-800 dark:text-dark-200"
|
||||
>
|
||||
<span class="mb-2 block font-medium text-gray-900 dark:text-white">
|
||||
{{ cacheDetailLabel() }}
|
||||
</span>
|
||||
<span class="flex items-center justify-between gap-3">
|
||||
<span>{{ t('usage.cacheCreationTokensLabel') }}</span>
|
||||
<span class="tabular-nums">
|
||||
{{ formatTokens(stats?.total_cache_creation_tokens || 0) }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="mt-1 flex items-center justify-between gap-3">
|
||||
<span>{{ t('usage.cacheReadTokensLabel') }}</span>
|
||||
<span class="tabular-nums">
|
||||
{{ formatTokens(stats?.total_cache_read_tokens || 0) }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -64,4 +101,7 @@ const formatTokens = (value: number) => {
|
||||
if (value >= 1e3) return (value / 1e3).toFixed(2) + 'K'
|
||||
return value.toLocaleString()
|
||||
}
|
||||
|
||||
const cacheLabel = () => t('usage.cacheTotal')
|
||||
const cacheDetailLabel = () => t('usage.cacheBreakdown')
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
|
||||
import UsageStatsCards from '../UsageStatsCards.vue'
|
||||
|
||||
const messages: Record<string, string> = {
|
||||
'usage.totalRequests': 'Total Requests',
|
||||
'usage.inSelectedRange': 'in selected range',
|
||||
'usage.totalTokens': 'Total Tokens',
|
||||
'usage.in': 'In',
|
||||
'usage.out': 'Out',
|
||||
'usage.cacheTotal': 'Cache',
|
||||
'usage.cacheBreakdown': 'Cache Token Breakdown',
|
||||
'usage.cacheCreationTokensLabel': 'Cache Creation',
|
||||
'usage.cacheReadTokensLabel': 'Cache Read',
|
||||
'usage.totalCost': 'Total Cost',
|
||||
'usage.accountCost': 'Cost',
|
||||
'usage.standardCost': 'Standard',
|
||||
'usage.avgDuration': 'Avg Duration',
|
||||
}
|
||||
|
||||
vi.mock('vue-i18n', async () => {
|
||||
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
|
||||
return {
|
||||
...actual,
|
||||
useI18n: () => ({
|
||||
t: (key: string) => messages[key] ?? key,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const stats = {
|
||||
total_requests: 1,
|
||||
total_input_tokens: 100,
|
||||
total_output_tokens: 50,
|
||||
total_cache_tokens: 34,
|
||||
total_cache_creation_tokens: 12,
|
||||
total_cache_read_tokens: 22,
|
||||
total_tokens: 184,
|
||||
total_cost: 0.001,
|
||||
total_actual_cost: 0.001,
|
||||
total_account_cost: 0.001,
|
||||
average_duration_ms: 250,
|
||||
}
|
||||
|
||||
describe('UsageStatsCards', () => {
|
||||
it('shows cache token breakdown values', () => {
|
||||
const wrapper = mount(UsageStatsCards, {
|
||||
props: {
|
||||
stats,
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
Icon: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const text = wrapper.text()
|
||||
expect(text).toContain('Cache: 34')
|
||||
expect(text).toContain('Cache Token Breakdown')
|
||||
expect(text).toContain('Cache Creation')
|
||||
expect(text).toContain('12')
|
||||
expect(text).toContain('Cache Read')
|
||||
expect(text).toContain('22')
|
||||
})
|
||||
})
|
||||
@@ -878,6 +878,10 @@ export default {
|
||||
cacheTtlOverridden1h: 'Billed as 1h',
|
||||
totalRequests: 'Total Requests',
|
||||
totalTokens: 'Total Tokens',
|
||||
cacheTotal: 'Cache',
|
||||
cacheBreakdown: 'Cache Token Breakdown',
|
||||
cacheCreationTokensLabel: 'Cache Creation',
|
||||
cacheReadTokensLabel: 'Cache Read',
|
||||
totalCost: 'Total Cost',
|
||||
standardCost: 'Standard',
|
||||
actualCost: 'Actual',
|
||||
|
||||
@@ -882,6 +882,10 @@ export default {
|
||||
cacheTtlOverridden1h: '按 1h 计费',
|
||||
totalRequests: '总请求数',
|
||||
totalTokens: '总 Token',
|
||||
cacheTotal: '缓存',
|
||||
cacheBreakdown: '缓存 Token 明细',
|
||||
cacheCreationTokensLabel: '缓存创建',
|
||||
cacheReadTokensLabel: '缓存读取',
|
||||
totalCost: '总消费',
|
||||
standardCost: '标准',
|
||||
actualCost: '实际',
|
||||
|
||||
Reference in New Issue
Block a user