mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 03:10:50 +08:00
139 lines
3.9 KiB
TypeScript
139 lines
3.9 KiB
TypeScript
import { useCallback, useMemo } from 'react';
|
|
import { collectUsageDetails, extractTotalTokens } from '@/utils/usage';
|
|
import type { UsagePayload } from './useUsageData';
|
|
|
|
export interface SparklineData {
|
|
labels: string[];
|
|
datasets: [
|
|
{
|
|
data: number[];
|
|
borderColor: string;
|
|
backgroundColor: string;
|
|
fill: boolean;
|
|
tension: number;
|
|
pointRadius: number;
|
|
borderWidth: number;
|
|
}
|
|
];
|
|
}
|
|
|
|
export interface SparklineBundle {
|
|
data: SparklineData;
|
|
}
|
|
|
|
export interface UseSparklinesOptions {
|
|
usage: UsagePayload | null;
|
|
loading: boolean;
|
|
}
|
|
|
|
export interface UseSparklinesReturn {
|
|
requestsSparkline: SparklineBundle | null;
|
|
tokensSparkline: SparklineBundle | null;
|
|
rpmSparkline: SparklineBundle | null;
|
|
tpmSparkline: SparklineBundle | null;
|
|
costSparkline: SparklineBundle | null;
|
|
}
|
|
|
|
export function useSparklines({ usage, loading }: UseSparklinesOptions): UseSparklinesReturn {
|
|
const buildLastHourSeries = useCallback(
|
|
(metric: 'requests' | 'tokens'): { labels: string[]; data: number[] } => {
|
|
if (!usage) return { labels: [], data: [] };
|
|
const details = collectUsageDetails(usage);
|
|
if (!details.length) return { labels: [], data: [] };
|
|
|
|
const windowMinutes = 60;
|
|
const now = Date.now();
|
|
const windowStart = now - windowMinutes * 60 * 1000;
|
|
const buckets = new Array(windowMinutes).fill(0);
|
|
|
|
details.forEach((detail) => {
|
|
const timestamp = Date.parse(detail.timestamp);
|
|
if (Number.isNaN(timestamp) || timestamp < windowStart) {
|
|
return;
|
|
}
|
|
const minuteIndex = Math.min(
|
|
windowMinutes - 1,
|
|
Math.floor((timestamp - windowStart) / 60000)
|
|
);
|
|
const increment = metric === 'tokens' ? extractTotalTokens(detail) : 1;
|
|
buckets[minuteIndex] += increment;
|
|
});
|
|
|
|
const labels = buckets.map((_, idx) => {
|
|
const date = new Date(windowStart + (idx + 1) * 60000);
|
|
const h = date.getHours().toString().padStart(2, '0');
|
|
const m = date.getMinutes().toString().padStart(2, '0');
|
|
return `${h}:${m}`;
|
|
});
|
|
|
|
return { labels, data: buckets };
|
|
},
|
|
[usage]
|
|
);
|
|
|
|
const buildSparkline = useCallback(
|
|
(
|
|
series: { labels: string[]; data: number[] },
|
|
color: string,
|
|
backgroundColor: string
|
|
): SparklineBundle | null => {
|
|
if (loading || !series?.data?.length) {
|
|
return null;
|
|
}
|
|
const sliceStart = Math.max(series.data.length - 60, 0);
|
|
const labels = series.labels.slice(sliceStart);
|
|
const points = series.data.slice(sliceStart);
|
|
return {
|
|
data: {
|
|
labels,
|
|
datasets: [
|
|
{
|
|
data: points,
|
|
borderColor: color,
|
|
backgroundColor,
|
|
fill: true,
|
|
tension: 0.45,
|
|
pointRadius: 0,
|
|
borderWidth: 2
|
|
}
|
|
]
|
|
}
|
|
};
|
|
},
|
|
[loading]
|
|
);
|
|
|
|
const requestsSparkline = useMemo(
|
|
() => buildSparkline(buildLastHourSeries('requests'), '#3b82f6', 'rgba(59, 130, 246, 0.18)'),
|
|
[buildLastHourSeries, buildSparkline]
|
|
);
|
|
|
|
const tokensSparkline = useMemo(
|
|
() => buildSparkline(buildLastHourSeries('tokens'), '#8b5cf6', 'rgba(139, 92, 246, 0.18)'),
|
|
[buildLastHourSeries, buildSparkline]
|
|
);
|
|
|
|
const rpmSparkline = useMemo(
|
|
() => buildSparkline(buildLastHourSeries('requests'), '#22c55e', 'rgba(34, 197, 94, 0.18)'),
|
|
[buildLastHourSeries, buildSparkline]
|
|
);
|
|
|
|
const tpmSparkline = useMemo(
|
|
() => buildSparkline(buildLastHourSeries('tokens'), '#f97316', 'rgba(249, 115, 22, 0.18)'),
|
|
[buildLastHourSeries, buildSparkline]
|
|
);
|
|
|
|
const costSparkline = useMemo(
|
|
() => buildSparkline(buildLastHourSeries('tokens'), '#f59e0b', 'rgba(245, 158, 11, 0.18)'),
|
|
[buildLastHourSeries, buildSparkline]
|
|
);
|
|
|
|
return {
|
|
requestsSparkline,
|
|
tokensSparkline,
|
|
rpmSparkline,
|
|
tpmSparkline,
|
|
costSparkline
|
|
};
|
|
}
|