From 96961d7b79d80f38d0f37dda137d4bdbc1f60836 Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Thu, 27 Nov 2025 18:04:47 +0800 Subject: [PATCH] feat: add cached and reasoning token metrics with internationalization support --- i18n.js | 4 ++++ index.html | 8 ++++++++ src/modules/usage.js | 35 ++++++++++++++++++++++++++++++++++- styles.css | 24 +++++++++++++++++++++++- 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/i18n.js b/i18n.js index ecc07fd..e5d2c86 100644 --- a/i18n.js +++ b/i18n.js @@ -409,6 +409,8 @@ const i18n = { 'usage_stats.success_requests': '成功请求', 'usage_stats.failed_requests': '失败请求', 'usage_stats.total_tokens': '总Token数', + 'usage_stats.cached_tokens': '缓存 Token 数', + 'usage_stats.reasoning_tokens': '思考 Token 数', 'usage_stats.rpm_30m': 'RPM(近30分钟)', 'usage_stats.tpm_30m': 'TPM(近30分钟)', 'usage_stats.requests_trend': '请求趋势', @@ -959,6 +961,8 @@ const i18n = { 'usage_stats.success_requests': 'Success Requests', 'usage_stats.failed_requests': 'Failed Requests', 'usage_stats.total_tokens': 'Total Tokens', + 'usage_stats.cached_tokens': 'Cached Tokens', + 'usage_stats.reasoning_tokens': 'Reasoning Tokens', 'usage_stats.rpm_30m': 'RPM (last 30 min)', 'usage_stats.tpm_30m': 'TPM (last 30 min)', 'usage_stats.requests_trend': 'Request Trends', diff --git a/index.html b/index.html index 30890d7..ed4117e 100644 --- a/index.html +++ b/index.html @@ -886,6 +886,14 @@
0
总Token数
+
+ 缓存 Token 数: + 0 +
+
+ 思考 Token 数: + 0 +
diff --git a/src/modules/usage.js b/src/modules/usage.js index d55dac1..924d9d0 100644 --- a/src/modules/usage.js +++ b/src/modules/usage.js @@ -111,7 +111,7 @@ export async function loadUsageStats(usageData = null) { this.updateChartLineSelectors(null); // 清空概览数据 - ['total-requests', 'success-requests', 'failed-requests', 'total-tokens', 'rpm-30m', 'tpm-30m'].forEach(id => { + ['total-requests', 'success-requests', 'failed-requests', 'total-tokens', 'cached-tokens', 'reasoning-tokens', 'rpm-30m', 'tpm-30m'].forEach(id => { const el = document.getElementById(id); if (el) el.textContent = '-'; }); @@ -142,6 +142,16 @@ export function updateUsageOverview(data) { const totalTokensValue = safeData.total_tokens ?? 0; document.getElementById('total-tokens').textContent = this.formatTokensInMillions(totalTokensValue); + const tokenBreakdown = this.calculateTokenBreakdown(safeData); + const cachedEl = document.getElementById('cached-tokens'); + const reasoningEl = document.getElementById('reasoning-tokens'); + if (cachedEl) { + cachedEl.textContent = this.formatTokensInMillions(tokenBreakdown.cachedTokens); + } + if (reasoningEl) { + reasoningEl.textContent = this.formatTokensInMillions(tokenBreakdown.reasoningTokens); + } + const recentRate = this.calculateRecentPerMinuteRates(30, safeData); document.getElementById('rpm-30m').textContent = this.formatPerMinuteValue(recentRate.rpm); document.getElementById('tpm-30m').textContent = this.formatPerMinuteValue(recentRate.tpm); @@ -336,6 +346,28 @@ export function collectUsageDetails() { return this.collectUsageDetailsFromUsage(this.currentUsageData); } +export function calculateTokenBreakdown(usage = null) { + const details = this.collectUsageDetailsFromUsage(usage || this.currentUsageData); + if (!details.length) { + return { cachedTokens: 0, reasoningTokens: 0 }; + } + + let cachedTokens = 0; + let reasoningTokens = 0; + + details.forEach(detail => { + const tokens = detail?.tokens || {}; + if (typeof tokens.cached_tokens === 'number') { + cachedTokens += tokens.cached_tokens; + } + if (typeof tokens.reasoning_tokens === 'number') { + reasoningTokens += tokens.reasoning_tokens; + } + }); + + return { cachedTokens, reasoningTokens }; +} + export function calculateRecentPerMinuteRates(windowMinutes = 30, usage = null) { const details = this.collectUsageDetailsFromUsage(usage || this.currentUsageData); const effectiveWindow = Number.isFinite(windowMinutes) && windowMinutes > 0 @@ -792,6 +824,7 @@ export const usageModule = { getActiveChartLineSelections, collectUsageDetailsFromUsage, collectUsageDetails, + calculateTokenBreakdown, calculateRecentPerMinuteRates, createHourlyBucketMeta, buildHourlySeriesByModel, diff --git a/styles.css b/styles.css index f597832..dc3fc06 100644 --- a/styles.css +++ b/styles.css @@ -2988,11 +2988,23 @@ input:checked+.slider:before { /* 使用统计样式 */ .stats-overview { display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; margin-bottom: 30px; } +@media (max-width: 1200px) { + .stats-overview { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 768px) { + .stats-overview { + grid-template-columns: 1fr; + } +} + .usage-filter-bar { display: flex; justify-content: flex-start; @@ -3086,6 +3098,16 @@ input:checked+.slider:before { font-weight: 500; } +.stat-subtext { + font-size: 12px; + color: var(--text-tertiary); + line-height: 1.4; +} + +.stat-subtext:first-of-type { + margin-top: 6px; +} + .charts-container { display: grid; grid-template-columns: 1fr 1fr;