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;