import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, } from 'recharts'; import { MonitoringMessage, LLMCall } from '../../types/monitoring'; interface TrafficChartProps { messages: MonitoringMessage[]; llmCalls: LLMCall[]; loading?: boolean; } interface ChartDataPoint { time: string; timestamp: number; messages: number; llmCalls: number; } export default function TrafficChart({ messages, llmCalls, loading, }: TrafficChartProps) { const { t } = useTranslation(); const chartData = useMemo(() => { if (!messages.length && !llmCalls.length) { return []; } // Combine all timestamps and find the range const allTimestamps = [ ...messages.map((m) => m.timestamp.getTime()), ...llmCalls.map((c) => c.timestamp.getTime()), ]; if (allTimestamps.length === 0) return []; const minTime = Math.min(...allTimestamps); const maxTime = Math.max(...allTimestamps); const timeRange = maxTime - minTime; // Determine bucket size based on time range let bucketSize: number; let formatTime: (date: Date) => string; if (timeRange <= 60 * 60 * 1000) { // <= 1 hour: 5-minute buckets bucketSize = 5 * 60 * 1000; formatTime = (date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } else if (timeRange <= 6 * 60 * 60 * 1000) { // <= 6 hours: 15-minute buckets bucketSize = 15 * 60 * 1000; formatTime = (date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } else if (timeRange <= 24 * 60 * 60 * 1000) { // <= 24 hours: 1-hour buckets bucketSize = 60 * 60 * 1000; formatTime = (date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } else if (timeRange <= 7 * 24 * 60 * 60 * 1000) { // <= 7 days: 4-hour buckets bucketSize = 4 * 60 * 60 * 1000; formatTime = (date) => `${date.toLocaleDateString([], { month: 'short', day: 'numeric', })} ${date.toLocaleTimeString([], { hour: '2-digit' })}`; } else { // > 7 days: 1-day buckets bucketSize = 24 * 60 * 60 * 1000; formatTime = (date) => date.toLocaleDateString([], { month: 'short', day: 'numeric' }); } // Create buckets const buckets: Map = new Map(); const startBucket = Math.floor(minTime / bucketSize) * bucketSize; const endBucket = Math.ceil(maxTime / bucketSize) * bucketSize; for (let bucket = startBucket; bucket <= endBucket; bucket += bucketSize) { buckets.set(bucket, { time: formatTime(new Date(bucket)), timestamp: bucket, messages: 0, llmCalls: 0, }); } // Count messages per bucket messages.forEach((msg) => { const bucket = Math.floor(msg.timestamp.getTime() / bucketSize) * bucketSize; const point = buckets.get(bucket); if (point) { point.messages++; } }); // Count LLM calls per bucket llmCalls.forEach((call) => { const bucket = Math.floor(call.timestamp.getTime() / bucketSize) * bucketSize; const point = buckets.get(bucket); if (point) { point.llmCalls++; } }); return Array.from(buckets.values()).sort( (a, b) => a.timestamp - b.timestamp, ); }, [messages, llmCalls]); if (loading) { return (
); } if (chartData.length === 0) { return (

{t('monitoring.trafficChart.title')}

{t('monitoring.trafficChart.noData')}
); } return (

{t('monitoring.trafficChart.title')}

); }