fix(monitor): fix statistics logic issues in monitor charts

Changes:
- Fix hourly token chart incorrectly counting failed request tokens
- Fix daily trend chart using UTC date causing cross-day statistics errors
- Daily trend chart now distinguishes success/failed request counts
- Token statistics only count successful requests for more accurate data

Modified files:
- src/components/monitor/DailyTrendChart.tsx (modified)
- src/components/monitor/HourlyTokenChart.tsx (modified)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kongkongyo
2026-01-14 17:34:37 +08:00
parent 76bfa26d3e
commit 5123d254f2
2 changed files with 31 additions and 6 deletions

View File

@@ -14,6 +14,8 @@ interface DailyTrendChartProps {
interface DailyStat { interface DailyStat {
date: string; date: string;
requests: number; requests: number;
successRequests: number;
failedRequests: number;
inputTokens: number; inputTokens: number;
outputTokens: number; outputTokens: number;
reasoningTokens: number; reasoningTokens: number;
@@ -29,19 +31,33 @@ export function DailyTrendChart({ data, loading, isDark, timeRange }: DailyTrend
const dailyStats: Record<string, { const dailyStats: Record<string, {
requests: number; requests: number;
successRequests: number;
failedRequests: number;
inputTokens: number; inputTokens: number;
outputTokens: number; outputTokens: number;
reasoningTokens: number; reasoningTokens: number;
cachedTokens: number; cachedTokens: number;
}> = {}; }> = {};
// 辅助函数:获取本地日期字符串 YYYY-MM-DD
const getLocalDateString = (timestamp: string): string => {
const date = new Date(timestamp);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
Object.values(data.apis).forEach((apiData) => { Object.values(data.apis).forEach((apiData) => {
Object.values(apiData.models).forEach((modelData) => { Object.values(apiData.models).forEach((modelData) => {
modelData.details.forEach((detail) => { modelData.details.forEach((detail) => {
const date = new Date(detail.timestamp).toISOString().split('T')[0]; // 使用本地日期而非 UTC 日期
const date = getLocalDateString(detail.timestamp);
if (!dailyStats[date]) { if (!dailyStats[date]) {
dailyStats[date] = { dailyStats[date] = {
requests: 0, requests: 0,
successRequests: 0,
failedRequests: 0,
inputTokens: 0, inputTokens: 0,
outputTokens: 0, outputTokens: 0,
reasoningTokens: 0, reasoningTokens: 0,
@@ -49,10 +65,16 @@ export function DailyTrendChart({ data, loading, isDark, timeRange }: DailyTrend
}; };
} }
dailyStats[date].requests++; dailyStats[date].requests++;
if (detail.failed) {
dailyStats[date].failedRequests++;
} else {
dailyStats[date].successRequests++;
// 只统计成功请求的 Token
dailyStats[date].inputTokens += detail.tokens.input_tokens || 0; dailyStats[date].inputTokens += detail.tokens.input_tokens || 0;
dailyStats[date].outputTokens += detail.tokens.output_tokens || 0; dailyStats[date].outputTokens += detail.tokens.output_tokens || 0;
dailyStats[date].reasoningTokens += detail.tokens.reasoning_tokens || 0; dailyStats[date].reasoningTokens += detail.tokens.reasoning_tokens || 0;
dailyStats[date].cachedTokens += detail.tokens.cached_tokens || 0; dailyStats[date].cachedTokens += detail.tokens.cached_tokens || 0;
}
}); });
}); });
}); });

View File

@@ -48,10 +48,13 @@ export function HourlyTokenChart({ data, loading, isDark }: HourlyTokenChartProp
hourlyStats[hour] = { total: 0, input: 0, output: 0, reasoning: 0, cached: 0 }; hourlyStats[hour] = { total: 0, input: 0, output: 0, reasoning: 0, cached: 0 };
}); });
// 收集每小时的 Token 数据 // 收集每小时的 Token 数据(只统计成功请求)
Object.values(data.apis).forEach((apiData) => { Object.values(data.apis).forEach((apiData) => {
Object.values(apiData.models).forEach((modelData) => { Object.values(apiData.models).forEach((modelData) => {
modelData.details.forEach((detail) => { modelData.details.forEach((detail) => {
// 跳过失败请求,失败请求的 Token 数据不准确
if (detail.failed) return;
const timestamp = new Date(detail.timestamp); const timestamp = new Date(detail.timestamp);
if (timestamp < cutoffTime) return; if (timestamp < cutoffTime) return;