From 63802f9b1a19185ad60dab90f28da718bd637d64 Mon Sep 17 00:00:00 2001 From: Zyy Date: Thu, 8 Oct 2026 11:43:26 +0800 Subject: [PATCH] fix(usage): exclude cached tokens from displayed input Reuse the fresh input calculation across usage records, token details and key usage metrics. Co-authored-by: Codex --- .../src/components/usage/UsageTokenCell.vue | 3 ++- .../src/components/usage/shared/presenter.ts | 21 +++++++++++++++++-- frontend/src/views/key-usage/utils/metrics.ts | 8 ++----- .../components/UsageRecordDetailModal.vue | 3 ++- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/usage/UsageTokenCell.vue b/frontend/src/components/usage/UsageTokenCell.vue index 6910163be..95e0acdb1 100644 --- a/frontend/src/components/usage/UsageTokenCell.vue +++ b/frontend/src/components/usage/UsageTokenCell.vue @@ -4,13 +4,14 @@ import type { UsageListRecord } from '@/api' import { Archive, ArrowDown, ArrowUp } from '@lucide/vue' import { computed } from 'vue' +import { usageTokenDetails } from '@/components/usage/shared/presenter' import UsageDetailPopover from '@/components/usage/UsageDetailPopover.vue' const props = defineProps<{ record: Pick }>() -const tokenDetails = computed(() => props.record.tokenDetails) +const tokenDetails = computed(() => usageTokenDetails(props.record.tokenDetails)) const tokenItems = computed(() => [ { label: '输入 Token', value: tokenDetails.value.inputTokensDisplay }, { label: '输出 Token', value: tokenDetails.value.outputTokensDisplay }, diff --git a/frontend/src/components/usage/shared/presenter.ts b/frontend/src/components/usage/shared/presenter.ts index 76f27ba46..0b0c04b7d 100644 --- a/frontend/src/components/usage/shared/presenter.ts +++ b/frontend/src/components/usage/shared/presenter.ts @@ -1,5 +1,5 @@ -import type { UsageListRecord, UsageRecordDetail } from '@/api' -import { formatCompactNumber, formatDuration } from '@/utils/format' +import type { UsageListRecord, UsageRecordDetail, UsageTokenDetails } from '@/api' +import { formatCompactNumber, formatDuration, formatInteger } from '@/utils/format' // 列表与详情使用独立读模型,展示函数只依赖两者的公共字段。 type UsageCommonRecord = UsageListRecord | UsageRecordDetail @@ -9,6 +9,23 @@ export type UsagePerformanceRecord = UsageLatencyRecord & { tokenDetails: Pick | null } +export function usageFreshInputTokens(input: number, cached: number, written: number) { + // 输入总量包含缓存读写,普通输入与缓存明细分开展示 + return Math.max(0, input - cached - written) +} + +export function usageTokenDetails(details: UsageTokenDetails): UsageTokenDetails { + const inputTokens = details.inputTokens === null + ? null + : usageFreshInputTokens(details.inputTokens, details.cachedTokens ?? 0, details.cacheWriteTokens ?? 0) + + return { + ...details, + inputTokens, + inputTokensDisplay: inputTokens === null ? details.inputTokensDisplay : formatInteger(inputTokens), + } +} + export function usageTransportType(transport?: string | null) { if (transport === 'websocket') return 'WS' diff --git a/frontend/src/views/key-usage/utils/metrics.ts b/frontend/src/views/key-usage/utils/metrics.ts index 021b85cbe..a8c7e8f77 100644 --- a/frontend/src/views/key-usage/utils/metrics.ts +++ b/frontend/src/views/key-usage/utils/metrics.ts @@ -1,5 +1,6 @@ import type { KeyUsageMetrics } from '@/api/modules/key-usage' import { ArrowDown, ArrowUp, Brain, Database, Sparkles } from '@lucide/vue' +import { usageFreshInputTokens } from '@/components/usage/shared/presenter' // 卡片、曲线和图例共用语义与主题色,避免同一指标出现不同颜色。 export const keyUsageTokenMetrics = [ @@ -12,11 +13,6 @@ export const keyUsageTokenMetrics = [ export function keyUsageTokenValue(metrics: KeyUsageMetrics, key: typeof keyUsageTokenMetrics[number]['key']) { return key === 'inputTokens' - ? freshInput(metrics.inputTokens, metrics.cachedTokens, metrics.cacheWriteTokens) + ? usageFreshInputTokens(metrics.inputTokens, metrics.cachedTokens, metrics.cacheWriteTokens) : metrics[key] } - -function freshInput(input: number, cached: number, written: number) { - // 输入总量已经包含缓存读写,不把缓存再次计入消耗。 - return Math.max(0, input - cached - written) -} diff --git a/frontend/src/views/usage/components/UsageRecordDetailModal.vue b/frontend/src/views/usage/components/UsageRecordDetailModal.vue index 9e3923956..59cd3d073 100644 --- a/frontend/src/views/usage/components/UsageRecordDetailModal.vue +++ b/frontend/src/views/usage/components/UsageRecordDetailModal.vue @@ -13,6 +13,7 @@ import { usageLatencyDetails, usageModelDisplay, usageReasoningEffort, + usageTokenDetails, usageTransportType, usageUserAgent, } from '@/components/usage/shared/presenter' @@ -37,7 +38,7 @@ const responseText = computed(() => props.record ? visibleResponseText(props.rec const modelDisplay = computed(() => props.record ? usageModelDisplay(props.record) : { primary: '—', secondary: '' }) -const tokenDetails = computed(() => props.record ? props.record.tokenDetails : null) +const tokenDetails = computed(() => props.record ? usageTokenDetails(props.record.tokenDetails) : null) const billing = computed(() => props.record ? props.record.billing : null) const latencyDetails = computed(() => props.record ? usageLatencyDetails(props.record) : null)