diff --git a/frontend/src/api/admin/usage.ts b/frontend/src/api/admin/usage.ts index d933ac6351f..b37996d61a4 100644 --- a/frontend/src/api/admin/usage.ts +++ b/frontend/src/api/admin/usage.ts @@ -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 diff --git a/frontend/src/components/admin/usage/UsageStatsCards.vue b/frontend/src/components/admin/usage/UsageStatsCards.vue index 905ece61a54..415c7cc186a 100644 --- a/frontend/src/components/admin/usage/UsageStatsCards.vue +++ b/frontend/src/components/admin/usage/UsageStatsCards.vue @@ -15,9 +15,46 @@

{{ t('usage.totalTokens') }}

{{ formatTokens(stats?.total_tokens || 0) }}

-

- {{ t('usage.in') }}: {{ formatTokens(stats?.total_input_tokens || 0) }} / - {{ t('usage.out') }}: {{ formatTokens(stats?.total_output_tokens || 0) }} +

+ {{ t('usage.in') }}: {{ formatTokens(stats?.total_input_tokens || 0) }} + / + {{ t('usage.out') }}: {{ formatTokens(stats?.total_output_tokens || 0) }} + / + + {{ cacheLabel() }}: {{ formatTokens(stats?.total_cache_tokens || 0) }} + + + + + + {{ cacheDetailLabel() }} + + + {{ t('usage.cacheCreationTokensLabel') }} + + {{ formatTokens(stats?.total_cache_creation_tokens || 0) }} + + + + {{ t('usage.cacheReadTokensLabel') }} + + {{ formatTokens(stats?.total_cache_read_tokens || 0) }} + + + +

@@ -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') diff --git a/frontend/src/components/admin/usage/__tests__/UsageStatsCards.spec.ts b/frontend/src/components/admin/usage/__tests__/UsageStatsCards.spec.ts new file mode 100644 index 00000000000..c73dec0b137 --- /dev/null +++ b/frontend/src/components/admin/usage/__tests__/UsageStatsCards.spec.ts @@ -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 = { + '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('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') + }) +}) diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index c26d4d063c8..0ac93a98113 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -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', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 0ce766f9f7e..ec9c51a1717 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -882,6 +882,10 @@ export default { cacheTtlOverridden1h: '按 1h 计费', totalRequests: '总请求数', totalTokens: '总 Token', + cacheTotal: '缓存', + cacheBreakdown: '缓存 Token 明细', + cacheCreationTokensLabel: '缓存创建', + cacheReadTokensLabel: '缓存读取', totalCost: '总消费', standardCost: '标准', actualCost: '实际',