diff --git a/static/index.html b/static/index.html index 50747c9..061dbb0 100644 --- a/static/index.html +++ b/static/index.html @@ -229,7 +229,7 @@ } .chart-container { position: relative; - height: 300px; + height: 600px; margin-top: 1rem; } .pie-chart-container { @@ -387,64 +387,59 @@ document.addEventListener('DOMContentLoaded', () => { /* ---------- Global renderTimeSeriesChart ---------- */ function renderTimeSeriesChart(timeSeriesData, chart, minutes) { - console.log('renderTimeSeriesChart called with minutes:', minutes, 'data length:', timeSeriesData?.length); - - // Safety check - if (!timeSeriesData || !Array.isArray(timeSeriesData)) { - console.warn('No valid time series data provided'); - chart.data.labels = []; - chart.data.datasets[0].data = []; - chart.data.datasets[1].data = []; - chart.update(); - return; - } - - // Filter data based on selected timeframe (use UTC for consistency) - const now = Date.now(); - const cutoffTime = now - (minutes * 60 * 1000); - - // Group data by hour for better visualization - const groupedData = {}; - timeSeriesData.forEach(item => { - // Database stores UTC timestamps, multiply by 1000 to get milliseconds - const timestampMs = item.timestamp * 1000; - - if (timestampMs >= cutoffTime) { - // Convert UTC timestamp to local time for display - const date = new Date(timestampMs); - // Group by hour and minute in local time - const hourStr = date.toLocaleString([], { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - - if (!groupedData[hourStr]) { - groupedData[hourStr] = { - input: 0, - output: 0, - timestamp: timestampMs // Keep for sorting - }; - } - groupedData[hourStr].input += item.input_tokens || 0; - groupedData[hourStr].output += item.output_tokens || 0; - } - }); - - // Convert to arrays for chart, sorted by timestamp - const sortedEntries = Object.entries(groupedData).sort((a, b) => a[1].timestamp - b[1].timestamp); - const labels = sortedEntries.map(([label]) => label); - const inputData = sortedEntries.map(([, data]) => data.input); - const outputData = sortedEntries.map(([, data]) => data.output); - - console.log('Chart updated with', labels.length, 'data points'); - - // Update chart data - chart.data.labels = labels; - chart.data.datasets[0].data = inputData; - chart.data.datasets[1].data = outputData; + // Guard clause + if (!Array.isArray(timeSeriesData) || !timeSeriesData.length) { + chart.data.labels = []; + chart.data.datasets[0].data = []; + chart.data.datasets[1].data = []; chart.update(); + return; + } + + /* ── 1️⃣ Cut‑off & bucket interval ──────────────────────────────── */ + const nowMs = Date.now(); // UTC millis + const cutoffMs = nowMs - minutes * 60 * 1000; // UTC window start + const intervalMs = 60 * 60 * 1000; // 1 h buckets + + /* ── 2️⃣ Build ordered bucket slots (UTC) ───────────────────────────── */ + const slots = []; + for (let ts = cutoffMs; ts <= nowMs; ts += intervalMs) { + slots.push(ts); + } + + /* ── 3️⃣ Aggregate raw rows into those slots ───────────────────────────── */ + const bucketMap = {}; // epoch ms → {input, output} + timeSeriesData.forEach(row => { + // If your DB already stores ms, drop the * 1000 + const tsMs = row.timestamp * 1000; // <-- keep *1000 **only** if the DB stores seconds + if (tsMs < cutoffMs || tsMs > nowMs) return; + + const slot = Math.floor((tsMs - cutoffMs) / intervalMs) * intervalMs + cutoffMs; + if (!bucketMap[slot]) bucketMap[slot] = { input: 0, output: 0 }; + bucketMap[slot].input += row.input_tokens || 0; + bucketMap[slot].output += row.output_tokens || 0; + }); + + /* ── 4️⃣ Build labels & data arrays (UTC labels) ──────────────────────── */ + const labels = slots.map(ts => { + const d = new Date(ts); // UTC millisecond timestamp + return d.toLocaleString(undefined, { // <-- force UTC for display + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + timeZoneName: 'short' + }).replace(/UTC$/, 'UTC'); // keep the “UTC” suffix if you like + }); + + const inputData = slots.map(ts => (bucketMap[ts]?.input ?? 0)); + const outputData = slots.map(ts => (bucketMap[ts]?.output ?? 0)); + + /* ── 5️⃣ Push into the Chart.js instance ─────────────────────────────── */ + chart.data.labels = labels; + chart.data.datasets[0].data = inputData; + chart.data.datasets[1].data = outputData; + chart.update(); } /* ---------- Utility ---------- */ async function fetchJSON(url) {