mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-19 19:20:49 +08:00
feat: add cached and reasoning token metrics with internationalization support
This commit is contained in:
4
i18n.js
4
i18n.js
@@ -409,6 +409,8 @@ const i18n = {
|
|||||||
'usage_stats.success_requests': '成功请求',
|
'usage_stats.success_requests': '成功请求',
|
||||||
'usage_stats.failed_requests': '失败请求',
|
'usage_stats.failed_requests': '失败请求',
|
||||||
'usage_stats.total_tokens': '总Token数',
|
'usage_stats.total_tokens': '总Token数',
|
||||||
|
'usage_stats.cached_tokens': '缓存 Token 数',
|
||||||
|
'usage_stats.reasoning_tokens': '思考 Token 数',
|
||||||
'usage_stats.rpm_30m': 'RPM(近30分钟)',
|
'usage_stats.rpm_30m': 'RPM(近30分钟)',
|
||||||
'usage_stats.tpm_30m': 'TPM(近30分钟)',
|
'usage_stats.tpm_30m': 'TPM(近30分钟)',
|
||||||
'usage_stats.requests_trend': '请求趋势',
|
'usage_stats.requests_trend': '请求趋势',
|
||||||
@@ -959,6 +961,8 @@ const i18n = {
|
|||||||
'usage_stats.success_requests': 'Success Requests',
|
'usage_stats.success_requests': 'Success Requests',
|
||||||
'usage_stats.failed_requests': 'Failed Requests',
|
'usage_stats.failed_requests': 'Failed Requests',
|
||||||
'usage_stats.total_tokens': 'Total Tokens',
|
'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.rpm_30m': 'RPM (last 30 min)',
|
||||||
'usage_stats.tpm_30m': 'TPM (last 30 min)',
|
'usage_stats.tpm_30m': 'TPM (last 30 min)',
|
||||||
'usage_stats.requests_trend': 'Request Trends',
|
'usage_stats.requests_trend': 'Request Trends',
|
||||||
|
|||||||
@@ -886,6 +886,14 @@
|
|||||||
<div class="stat-content">
|
<div class="stat-content">
|
||||||
<div class="stat-number" id="total-tokens">0</div>
|
<div class="stat-number" id="total-tokens">0</div>
|
||||||
<div class="stat-label" data-i18n="usage_stats.total_tokens">总Token数</div>
|
<div class="stat-label" data-i18n="usage_stats.total_tokens">总Token数</div>
|
||||||
|
<div class="stat-subtext">
|
||||||
|
<span data-i18n="usage_stats.cached_tokens">缓存 Token 数</span>:
|
||||||
|
<span id="cached-tokens">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-subtext">
|
||||||
|
<span data-i18n="usage_stats.reasoning_tokens">思考 Token 数</span>:
|
||||||
|
<span id="reasoning-tokens">0</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export async function loadUsageStats(usageData = null) {
|
|||||||
this.updateChartLineSelectors(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);
|
const el = document.getElementById(id);
|
||||||
if (el) el.textContent = '-';
|
if (el) el.textContent = '-';
|
||||||
});
|
});
|
||||||
@@ -142,6 +142,16 @@ export function updateUsageOverview(data) {
|
|||||||
const totalTokensValue = safeData.total_tokens ?? 0;
|
const totalTokensValue = safeData.total_tokens ?? 0;
|
||||||
document.getElementById('total-tokens').textContent = this.formatTokensInMillions(totalTokensValue);
|
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);
|
const recentRate = this.calculateRecentPerMinuteRates(30, safeData);
|
||||||
document.getElementById('rpm-30m').textContent = this.formatPerMinuteValue(recentRate.rpm);
|
document.getElementById('rpm-30m').textContent = this.formatPerMinuteValue(recentRate.rpm);
|
||||||
document.getElementById('tpm-30m').textContent = this.formatPerMinuteValue(recentRate.tpm);
|
document.getElementById('tpm-30m').textContent = this.formatPerMinuteValue(recentRate.tpm);
|
||||||
@@ -336,6 +346,28 @@ export function collectUsageDetails() {
|
|||||||
return this.collectUsageDetailsFromUsage(this.currentUsageData);
|
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) {
|
export function calculateRecentPerMinuteRates(windowMinutes = 30, usage = null) {
|
||||||
const details = this.collectUsageDetailsFromUsage(usage || this.currentUsageData);
|
const details = this.collectUsageDetailsFromUsage(usage || this.currentUsageData);
|
||||||
const effectiveWindow = Number.isFinite(windowMinutes) && windowMinutes > 0
|
const effectiveWindow = Number.isFinite(windowMinutes) && windowMinutes > 0
|
||||||
@@ -792,6 +824,7 @@ export const usageModule = {
|
|||||||
getActiveChartLineSelections,
|
getActiveChartLineSelections,
|
||||||
collectUsageDetailsFromUsage,
|
collectUsageDetailsFromUsage,
|
||||||
collectUsageDetails,
|
collectUsageDetails,
|
||||||
|
calculateTokenBreakdown,
|
||||||
calculateRecentPerMinuteRates,
|
calculateRecentPerMinuteRates,
|
||||||
createHourlyBucketMeta,
|
createHourlyBucketMeta,
|
||||||
buildHourlySeriesByModel,
|
buildHourlySeriesByModel,
|
||||||
|
|||||||
24
styles.css
24
styles.css
@@ -2988,11 +2988,23 @@ input:checked+.slider:before {
|
|||||||
/* 使用统计样式 */
|
/* 使用统计样式 */
|
||||||
.stats-overview {
|
.stats-overview {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-bottom: 30px;
|
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 {
|
.usage-filter-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
@@ -3086,6 +3098,16 @@ input:checked+.slider:before {
|
|||||||
font-weight: 500;
|
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 {
|
.charts-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|||||||
Reference in New Issue
Block a user