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 <noreply@openai.com>
This commit is contained in:
Zyy
2026-10-08 11:43:26 +08:00
co-authored by Codex
parent de599ed768
commit 63802f9b1a
4 changed files with 25 additions and 10 deletions
@@ -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<UsageListRecord, 'tokenDetails'>
}>()
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 },
@@ -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<UsageListRecord['tokenDetails'], 'outputTokens'> | 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'
@@ -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)
}
@@ -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)