mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-19 03:00:49 +08:00
feat(usage): add cost trend chart with hourly/daily toggle
This commit is contained in:
@@ -1395,3 +1395,90 @@ export function buildDailyTokenBreakdown(usageData: unknown): TokenBreakdownSeri
|
||||
|
||||
return { labels, dataByCategory, hasData };
|
||||
}
|
||||
|
||||
export interface CostSeries {
|
||||
labels: string[];
|
||||
data: number[];
|
||||
hasData: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按小时构建费用时间序列
|
||||
*/
|
||||
export function buildHourlyCostSeries(
|
||||
usageData: unknown,
|
||||
modelPrices: Record<string, ModelPrice>,
|
||||
hourWindow: number = 24
|
||||
): CostSeries {
|
||||
const hourMs = 60 * 60 * 1000;
|
||||
const resolvedHourWindow =
|
||||
Number.isFinite(hourWindow) && hourWindow > 0
|
||||
? Math.min(Math.max(Math.floor(hourWindow), 1), 24 * 31)
|
||||
: 24;
|
||||
const now = new Date();
|
||||
const currentHour = new Date(now);
|
||||
currentHour.setMinutes(0, 0, 0);
|
||||
|
||||
const earliestBucket = new Date(currentHour);
|
||||
earliestBucket.setHours(earliestBucket.getHours() - (resolvedHourWindow - 1));
|
||||
const earliestTime = earliestBucket.getTime();
|
||||
|
||||
const labels: string[] = [];
|
||||
for (let i = 0; i < resolvedHourWindow; i++) {
|
||||
labels.push(formatHourLabel(new Date(earliestTime + i * hourMs)));
|
||||
}
|
||||
|
||||
const data = new Array(labels.length).fill(0);
|
||||
const details = collectUsageDetails(usageData);
|
||||
let hasData = false;
|
||||
|
||||
details.forEach((detail) => {
|
||||
const timestamp = Date.parse(detail.timestamp);
|
||||
if (Number.isNaN(timestamp)) return;
|
||||
const normalized = new Date(timestamp);
|
||||
normalized.setMinutes(0, 0, 0);
|
||||
const bucketStart = normalized.getTime();
|
||||
const lastBucketTime = earliestTime + (labels.length - 1) * hourMs;
|
||||
if (bucketStart < earliestTime || bucketStart > lastBucketTime) return;
|
||||
const bucketIndex = Math.floor((bucketStart - earliestTime) / hourMs);
|
||||
if (bucketIndex < 0 || bucketIndex >= labels.length) return;
|
||||
|
||||
const cost = calculateCost(detail, modelPrices);
|
||||
if (cost > 0) {
|
||||
data[bucketIndex] += cost;
|
||||
hasData = true;
|
||||
}
|
||||
});
|
||||
|
||||
return { labels, data, hasData };
|
||||
}
|
||||
|
||||
/**
|
||||
* 按天构建费用时间序列
|
||||
*/
|
||||
export function buildDailyCostSeries(
|
||||
usageData: unknown,
|
||||
modelPrices: Record<string, ModelPrice>
|
||||
): CostSeries {
|
||||
const details = collectUsageDetails(usageData);
|
||||
const dayMap: Record<string, number> = {};
|
||||
let hasData = false;
|
||||
|
||||
details.forEach((detail) => {
|
||||
const timestamp = Date.parse(detail.timestamp);
|
||||
if (Number.isNaN(timestamp)) return;
|
||||
const dayLabel = formatDayLabel(new Date(timestamp));
|
||||
if (!dayLabel) return;
|
||||
|
||||
const cost = calculateCost(detail, modelPrices);
|
||||
if (cost > 0) {
|
||||
dayMap[dayLabel] = (dayMap[dayLabel] || 0) + cost;
|
||||
hasData = true;
|
||||
}
|
||||
});
|
||||
|
||||
const labels = Object.keys(dayMap).sort();
|
||||
const data = labels.map((l) => dayMap[l]);
|
||||
|
||||
return { labels, data, hasData };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user