feat(usage): 修复缓存命中率计算并新增用户端 API 格式统计

- 新增 input_context_expr() 按 api_format 区分 input_tokens 语义
  (OpenAI/Gemini input_tokens 已含 cache_read,Claude 需额外加上)
- 缓存命中率统一改为基于归一化后的 total_input_context 计算
- 用户 /me/usage 接口新增 summary_by_api_format 后端聚合字段
- 前端 API 格式统计改用后端聚合数据,移除前端逐条记录手动统计
- 提取 formatHitRate 到 utils/format.ts 消除三处重复定义
- 移除 PoolManager 中未使用的 select_key 方法

Co-Authored-By: AAEE86 <[email protected]>
This commit is contained in:
fawney19
2026-03-14 00:33:19 +08:00
co-authored by AAEE86
parent e9678ea899
commit bdfe4adc98
14 changed files with 281 additions and 247 deletions
@@ -110,6 +110,8 @@ export function useUsageData(options: UseUsageDataOptions) {
model: item.model,
request_count: item.request_count || 0,
total_tokens: item.total_tokens || 0,
cache_read_tokens: typeof raw.cache_read_tokens === 'number' ? raw.cache_read_tokens : 0,
cache_hit_rate: typeof raw.cache_hit_rate === 'number' ? raw.cache_hit_rate : 0,
total_cost: item.total_cost || 0,
actual_cost: typeof raw.actual_cost === 'number' ? raw.actual_cost : undefined
}
@@ -119,6 +121,8 @@ export function useUsageData(options: UseUsageDataOptions) {
provider: item.provider,
requests: item.request_count,
totalTokens: item.total_tokens || 0,
cacheReadTokens: item.cache_read_tokens || 0,
cacheHitRate: item.cache_hit_rate || 0,
totalCost: item.total_cost,
actualCost: item.actual_cost,
successRate: item.success_rate,
@@ -131,6 +135,8 @@ export function useUsageData(options: UseUsageDataOptions) {
api_format: item.api_format,
request_count: item.request_count || 0,
total_tokens: item.total_tokens || 0,
cache_read_tokens: item.cache_read_tokens || 0,
cache_hit_rate: item.cache_hit_rate || 0,
total_cost: item.total_cost || 0,
actual_cost: item.actual_cost,
avgResponseTime: item.avg_response_time_ms > 0
@@ -163,6 +169,8 @@ export function useUsageData(options: UseUsageDataOptions) {
model: item.model,
request_count: item.requests || 0,
total_tokens: item.total_tokens || 0,
cache_read_tokens: item.cache_read_tokens || 0,
cache_hit_rate: item.cache_hit_rate || 0,
total_cost: item.total_cost_usd || 0,
actual_cost: item.actual_total_cost_usd
}))
@@ -170,7 +178,9 @@ export function useUsageData(options: UseUsageDataOptions) {
providerStats.value = (userData.summary_by_provider || []).map((item) => ({
provider: item.provider,
requests: item.requests || 0,
totalTokens: 0,
totalTokens: item.total_tokens || 0,
cacheReadTokens: item.cache_read_tokens || 0,
cacheHitRate: item.cache_hit_rate || 0,
totalCost: item.total_cost_usd || 0,
successRate: item.success_rate || 0,
avgResponseTime: (item.avg_response_time_ms ?? 0) > 0
@@ -184,57 +194,28 @@ export function useUsageData(options: UseUsageDataOptions) {
currentRecords.value = mergeRecordStatus(currentRecords.value, nextRecords)
totalRecords.value = userData.pagination?.total ?? currentRecords.value.length
// 从记录中提取筛选选项和 API 格式统计
// 从记录中提取筛选选项
const models = new Set<string>()
const providers = new Set<string>()
const apiFormatMap = new Map<string, {
count: number
tokens: number
cost: number
totalResponseTime: number
responseTimeCount: number
}>()
currentRecords.value.forEach(record => {
if (record.model) models.add(record.model)
if (record.provider) providers.add(record.provider)
if (record.api_format) {
const existing = apiFormatMap.get(record.api_format) || {
count: 0,
tokens: 0,
cost: 0,
totalResponseTime: 0,
responseTimeCount: 0
}
existing.count++
existing.tokens += record.total_tokens || 0
existing.cost += record.cost || 0
if (record.response_time_ms) {
existing.totalResponseTime += record.response_time_ms
existing.responseTimeCount++
}
apiFormatMap.set(record.api_format, existing)
}
})
availableModels.value = Array.from(models).sort()
availableProviders.value = Array.from(providers).sort()
// 构建 API 格式统计数据
apiFormatStats.value = Array.from(apiFormatMap.entries())
.map(([format, data]) => {
const avgMs = data.responseTimeCount > 0
? data.totalResponseTime / data.responseTimeCount
: 0
return {
api_format: format,
request_count: data.count,
total_tokens: data.tokens,
total_cost: data.cost,
avgResponseTime: avgMs > 0 ? `${(avgMs / 1000).toFixed(2)}s` : '-'
}
})
.sort((a, b) => b.request_count - a.request_count)
// API 格式统计直接使用后端聚合数据
apiFormatStats.value = (userData.summary_by_api_format || []).map(item => ({
api_format: item.api_format,
request_count: item.request_count || 0,
total_tokens: item.total_tokens || 0,
cache_read_tokens: item.cache_read_tokens || 0,
cache_hit_rate: item.cache_hit_rate || 0,
total_cost: item.total_cost_usd || 0,
avgResponseTime: (item.avg_response_time_ms ?? 0) > 0
? `${((item.avg_response_time_ms ?? 0) / 1000).toFixed(2)}s`
: '-'
}))
}
} catch (error: unknown) {
if (requestId !== loadStatsRequestId) {