diff --git a/apps/web/src/features/monitoring/MonitoringCenterPage.test.tsx b/apps/web/src/features/monitoring/MonitoringCenterPage.test.tsx index 26e6d512..b27fde64 100644 --- a/apps/web/src/features/monitoring/MonitoringCenterPage.test.tsx +++ b/apps/web/src/features/monitoring/MonitoringCenterPage.test.tsx @@ -13,6 +13,7 @@ import { type MonitoringAccountAuthState, } from '@/features/monitoring/accountOverviewState'; import { buildRealtimeSourceDisplay } from '@/features/monitoring/realtimeSourceDisplay'; +import { buildCacheTokenPresentation } from '@/features/monitoring/components/accountOverviewPresentation'; const t = ((key: string, options?: Record) => { const copy: Record = { @@ -269,6 +270,33 @@ describe('MonitoringCenterPage summary cards', () => { }); describe('MonitoringCenterPage account card', () => { + it('formats legacy and fine-grained account cache metrics without changing the token slot', () => { + expect( + buildCacheTokenPresentation( + { cachedTokens: 12_500, cacheReadTokens: 0, cacheCreationTokens: 0 }, + t + ) + ).toMatchObject({ label: 'Cached Tokens', value: '12.5K' }); + expect( + buildCacheTokenPresentation( + { cachedTokens: 0, cacheReadTokens: 1_200, cacheCreationTokens: 300 }, + t + ) + ).toMatchObject({ label: 'CR / CW', value: '1.2K / 300' }); + expect( + buildCacheTokenPresentation( + { cachedTokens: 40, cacheReadTokens: 1_200, cacheCreationTokens: 300 }, + t + ) + ).toMatchObject({ label: 'C / CR / CW', value: '40 / 1.2K / 300' }); + expect( + buildCacheTokenPresentation( + { cachedTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 }, + t + ) + ).toMatchObject({ label: 'Cached Tokens', value: '0' }); + }); + it('prefers readable channel names in realtime source cells', () => { const display = buildRealtimeSourceDisplay( { @@ -688,7 +716,13 @@ describe('MonitoringCenterPage account card', () => { { key: 'total-tokens', label: 'Total Tokens', value: '35.1M' }, { key: 'input-tokens', label: 'Input Tokens', value: '35.0M' }, { key: 'output-tokens', label: 'Output Tokens', value: '68.5K' }, - { key: 'cached-tokens', label: 'Cached Tokens', value: '33.9M' }, + { + key: 'cached-tokens', + label: 'C / CR / CW', + fullLabel: + 'Cached Tokens: 33.9M · Cache Read Tokens: 1.2M · Cache Creation Tokens: 340.0K', + value: '33.9M / 1.2M / 340.0K', + }, ]} onRefreshQuota={() => {}} variant="table" @@ -698,9 +732,8 @@ describe('MonitoringCenterPage account card', () => { expect(html).toContain('Token Structure'); expect(html).toContain('Input Tokens'); expect(html).toContain('Output Tokens'); - expect(html).toContain('Cached Tokens'); - expect(html).not.toContain('Cache Read Tokens'); - expect(html).not.toContain('Cache Creation Tokens'); + expect(html).toContain('C / CR / CW'); + expect(html).toContain('33.9M / 1.2M / 340.0K'); expect(html).toContain('Model Usage Top 2'); expect(html).toContain('View All'); expect(html).not.toContain('Read'); @@ -709,6 +742,7 @@ describe('MonitoringCenterPage account card', () => { expect(html).toContain('Latest request'); expect(html).toContain('gpt-5.5'); expect(html).toContain('codex-auto-review'); + expect(html).toContain('C / CR / CW 32.5M / 1.1M / 300.0K'); expect(html).not.toContain('long-tail-model'); }); diff --git a/apps/web/src/features/monitoring/components/AccountOverviewCard.tsx b/apps/web/src/features/monitoring/components/AccountOverviewCard.tsx index 2a5ca0c4..2bafaf27 100644 --- a/apps/web/src/features/monitoring/components/AccountOverviewCard.tsx +++ b/apps/web/src/features/monitoring/components/AccountOverviewCard.tsx @@ -29,6 +29,7 @@ import { MonitoringHealthStatusBar } from './MonitoringHealthStatusBar'; import { buildAccountSecondaryText, buildAccountSummaryMetrics, + buildCacheTokenPresentation, formatPercent, getAccountStatusDotClassName, getAccountStatusLabel, @@ -595,6 +596,7 @@ function AccountModelUsageList({ {visibleModels.map((model) => { const modelKey = `${row.id}-${model.model}`; const isModelExpanded = Boolean(expandedModels[modelKey]); + const cacheMetric = buildCacheTokenPresentation(model, t); return (
- - {shortLabel( - t, - 'monitoring.cached_tokens_short', - 'monitoring.cached_tokens' - )} - - {formatCompactNumber(model.cachedTokens)} + {cacheMetric.label} + {cacheMetric.value}
@@ -743,25 +739,35 @@ export function AccountModelUsageTable({ - {visibleModels.map((model) => ( - - - - {model.model} - - - {formatCompactNumber(model.totalCalls)} - - {formatPercent(model.successRate)} - - {formatCompactNumber(model.inputTokens)} - {formatCompactNumber(model.outputTokens)} - {formatCompactNumber(model.cachedTokens)} - {formatCompactNumber(model.totalTokens)} - {hasPrices ? formatUsd(model.totalCost) : '--'} - {new Date(model.lastSeenAt).toLocaleString(locale)} - - ))} + {visibleModels.map((model) => { + const cacheMetric = buildCacheTokenPresentation(model, t); + return ( + + + + {model.model} + + + {formatCompactNumber(model.totalCalls)} + + {formatPercent(model.successRate)} + + {formatCompactNumber(model.inputTokens)} + {formatCompactNumber(model.outputTokens)} + + + {cacheMetric.label === + shortLabel(t, 'monitoring.cached_tokens_short', 'monitoring.cached_tokens') + ? cacheMetric.value + : `${cacheMetric.label} ${cacheMetric.value}`} + + + {formatCompactNumber(model.totalTokens)} + {hasPrices ? formatUsd(model.totalCost) : '--'} + {new Date(model.lastSeenAt).toLocaleString(locale)} + + ); + })} ) : ( diff --git a/apps/web/src/features/monitoring/components/accountOverviewPresentation.ts b/apps/web/src/features/monitoring/components/accountOverviewPresentation.ts index 92b77cab..44b4f4f6 100644 --- a/apps/web/src/features/monitoring/components/accountOverviewPresentation.ts +++ b/apps/web/src/features/monitoring/components/accountOverviewPresentation.ts @@ -50,6 +50,12 @@ export type AccountSummaryMetric = { valueClassName?: string; }; +export type CacheTokenPresentation = { + label: string; + fullLabel: string; + value: string; +}; + export const formatPercent = (value: number) => `${(value * 100).toFixed(1)}%`; const joinShort = (values: string[], limit = 2) => { @@ -65,6 +71,61 @@ const shortLabel = (t: TFunction, shortKey: string, fallbackKey: string) => { return label === shortKey ? fallback : label; }; +export const buildCacheTokenPresentation = ( + tokens: Pick, + t: TFunction +): CacheTokenPresentation => { + const cachedTokens = Math.max(tokens.cachedTokens || 0, 0); + const cacheReadTokens = Math.max(tokens.cacheReadTokens || 0, 0); + const cacheCreationTokens = Math.max(tokens.cacheCreationTokens || 0, 0); + const hasFineGrainedCache = cacheReadTokens > 0 || cacheCreationTokens > 0; + + if (!hasFineGrainedCache) { + return { + label: shortLabel(t, 'monitoring.cached_tokens_short', 'monitoring.cached_tokens'), + fullLabel: t('monitoring.cached_tokens'), + value: formatCompactNumber(cachedTokens), + }; + } + + const parts = [ + cachedTokens > 0 + ? { code: 'C', fullLabel: t('monitoring.cached_tokens'), value: cachedTokens } + : null, + cacheReadTokens > 0 + ? { code: 'CR', fullLabel: t('monitoring.cache_read_tokens'), value: cacheReadTokens } + : null, + cacheCreationTokens > 0 + ? { + code: 'CW', + fullLabel: t('monitoring.cache_creation_tokens'), + value: cacheCreationTokens, + } + : null, + ].filter((part): part is { code: string; fullLabel: string; value: number } => part !== null); + + return { + label: parts.map((part) => part.code).join(' / '), + fullLabel: parts + .map((part) => `${part.fullLabel}: ${formatCompactNumber(part.value)}`) + .join(' · '), + value: parts.map((part) => formatCompactNumber(part.value)).join(' / '), + }; +}; + +const buildAccountCacheSummaryMetric = ( + row: MonitoringAccountRow, + t: TFunction +): AccountSummaryMetric => { + const cacheMetric = buildCacheTokenPresentation(row, t); + return { + key: 'cached-tokens', + label: cacheMetric.label, + fullLabel: cacheMetric.fullLabel, + value: cacheMetric.value, + }; +}; + export const getCodexPlanLabel = ( planType: string | null | undefined, t: TFunction @@ -144,12 +205,7 @@ export const buildAccountSummaryMetrics = ( fullLabel: t('monitoring.output_tokens'), value: formatCompactNumber(row.outputTokens), }, - { - key: 'cached-tokens', - label: shortLabel(t, 'monitoring.cached_tokens_short', 'monitoring.cached_tokens'), - fullLabel: t('monitoring.cached_tokens'), - value: formatCompactNumber(row.cachedTokens), - }, + buildAccountCacheSummaryMetric(row, t), { key: 'cache-creation-tokens', label: shortLabel(