diff --git a/i18n.js b/i18n.js
index 1290754..ecc07fd 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.rpm_30m': 'RPM(近30分钟)',
+ 'usage_stats.tpm_30m': 'TPM(近30分钟)',
'usage_stats.requests_trend': '请求趋势',
'usage_stats.tokens_trend': 'Token 使用趋势',
'usage_stats.api_details': 'API 详细统计',
@@ -957,6 +959,8 @@ const i18n = {
'usage_stats.success_requests': 'Success Requests',
'usage_stats.failed_requests': 'Failed Requests',
'usage_stats.total_tokens': 'Total Tokens',
+ 'usage_stats.rpm_30m': 'RPM (last 30 min)',
+ 'usage_stats.tpm_30m': 'TPM (last 30 min)',
'usage_stats.requests_trend': 'Request Trends',
'usage_stats.tokens_trend': 'Token Usage Trends',
'usage_stats.api_details': 'API Details',
diff --git a/index.html b/index.html
index 4e2e8a3..30890d7 100644
--- a/index.html
+++ b/index.html
@@ -888,6 +888,26 @@
总Token数
+
+
+
+
diff --git a/src/modules/usage.js b/src/modules/usage.js
index d2bd8c0..d55dac1 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'].forEach(id => {
+ ['total-requests', 'success-requests', 'failed-requests', 'total-tokens', 'rpm-30m', 'tpm-30m'].forEach(id => {
const el = document.getElementById(id);
if (el) el.textContent = '-';
});
@@ -139,7 +139,38 @@ export function updateUsageOverview(data) {
document.getElementById('total-requests').textContent = safeData.total_requests ?? 0;
document.getElementById('success-requests').textContent = safeData.success_count ?? 0;
document.getElementById('failed-requests').textContent = safeData.failure_count ?? 0;
- document.getElementById('total-tokens').textContent = safeData.total_tokens ?? 0;
+ const totalTokensValue = safeData.total_tokens ?? 0;
+ document.getElementById('total-tokens').textContent = this.formatTokensInMillions(totalTokensValue);
+
+ const recentRate = this.calculateRecentPerMinuteRates(30, safeData);
+ document.getElementById('rpm-30m').textContent = this.formatPerMinuteValue(recentRate.rpm);
+ document.getElementById('tpm-30m').textContent = this.formatPerMinuteValue(recentRate.tpm);
+}
+
+export function formatTokensInMillions(value) {
+ const num = Number(value);
+ if (!Number.isFinite(num)) {
+ return '0.00M';
+ }
+ return `${(num / 1_000_000).toFixed(2)}M`;
+}
+
+export function formatPerMinuteValue(value) {
+ const num = Number(value);
+ if (!Number.isFinite(num)) {
+ return '0.00';
+ }
+ const abs = Math.abs(num);
+ if (abs >= 1000) {
+ return Math.round(num).toLocaleString();
+ }
+ if (abs >= 100) {
+ return num.toFixed(0);
+ }
+ if (abs >= 10) {
+ return num.toFixed(1);
+ }
+ return num.toFixed(2);
}
export function getModelNamesFromUsage(usage) {
@@ -305,6 +336,40 @@ export function collectUsageDetails() {
return this.collectUsageDetailsFromUsage(this.currentUsageData);
}
+export function calculateRecentPerMinuteRates(windowMinutes = 30, usage = null) {
+ const details = this.collectUsageDetailsFromUsage(usage || this.currentUsageData);
+ const effectiveWindow = Number.isFinite(windowMinutes) && windowMinutes > 0
+ ? windowMinutes
+ : 30;
+
+ if (!details.length) {
+ return { rpm: 0, tpm: 0, windowMinutes: effectiveWindow, requestCount: 0, tokenCount: 0 };
+ }
+
+ const now = Date.now();
+ const windowStart = now - effectiveWindow * 60 * 1000;
+ let requestCount = 0;
+ let tokenCount = 0;
+
+ details.forEach(detail => {
+ const timestamp = Date.parse(detail.timestamp);
+ if (Number.isNaN(timestamp) || timestamp < windowStart) {
+ return;
+ }
+ requestCount += 1;
+ tokenCount += this.extractTotalTokens(detail);
+ });
+
+ const denominator = effectiveWindow > 0 ? effectiveWindow : 1;
+ return {
+ rpm: requestCount / denominator,
+ tpm: tokenCount / denominator,
+ windowMinutes: effectiveWindow,
+ requestCount,
+ tokenCount
+ };
+}
+
export function createHourlyBucketMeta() {
const hourMs = 60 * 60 * 1000;
const now = new Date();
@@ -690,7 +755,7 @@ export function updateApiStatsTable(data) {
modelsHtml = '';
Object.entries(apiData.models).forEach(([modelName, modelData]) => {
const modelRequests = modelData.total_requests ?? 0;
- const modelTokens = modelData.total_tokens ?? 0;
+ const modelTokens = this.formatTokensInMillions(modelData.total_tokens ?? 0);
modelsHtml += `
${modelName}
@@ -705,7 +770,7 @@ export function updateApiStatsTable(data) {
| ${endpoint} |
${totalRequests} |
- ${apiData.total_tokens || 0} |
+ ${this.formatTokensInMillions(apiData.total_tokens || 0)} |
${successRate !== null ? successRate + '%' : '-'} |
${modelsHtml || '-'} |
@@ -727,11 +792,14 @@ export const usageModule = {
getActiveChartLineSelections,
collectUsageDetailsFromUsage,
collectUsageDetails,
+ calculateRecentPerMinuteRates,
createHourlyBucketMeta,
buildHourlySeriesByModel,
buildDailySeriesByModel,
buildChartDataForMetric,
formatHourLabel,
+ formatTokensInMillions,
+ formatPerMinuteValue,
formatDayLabel,
extractTotalTokens,
initializeCharts,