import { format } from "date-fns"; import { useMemo } from "react"; import { Line } from "react-chartjs-2"; import { getOverviewStats, getRecentRequests } from "../api"; import { AgentTokenShare } from "../components/AgentTokenShare"; import { CHART_THEMES } from "../components/chart-shared"; import { formatCost, formatDurationMs, formatInteger, formatRelativeTime } from "../data/formatters"; import { useResource } from "../data/useResource"; import type { MessageStats, TimeRange } from "../types"; import { AsyncBoundary, DataTable, MetricCluster, Panel, Skeleton, StatusPill } from "../ui"; import { useSystemTheme } from "../useSystemTheme"; export interface OverviewRouteProps { active: boolean; range: TimeRange; refreshTrigger: number; onRequestClick: (id: number) => void; } export function OverviewRoute({ active, range, refreshTrigger, onRequestClick }: OverviewRouteProps) { const { data: overview, error: overviewError, loading: overviewLoading, } = useResource(["overview", range, refreshTrigger], signal => getOverviewStats(range, signal), { pollMs: 30000, enabled: active, }); const { data: recentRequests, error: requestsError, loading: requestsLoading, } = useResource(["recent-requests", refreshTrigger], signal => getRecentRequests(50, signal), { pollMs: 30000, enabled: active, }); const theme = useSystemTheme(); const chartTheme = CHART_THEMES[theme]; const chartData = useMemo(() => { if (!overview?.timeSeries) return { labels: [], datasets: [] }; const labels = overview.timeSeries.map(pt => format(new Date(pt.timestamp), range === "1h" || range === "24h" ? "HH:mm" : "MMM d"), ); // Show point markers when the series is sparse (e.g. a quiet 1h window) // so a 1-2 point line is still visible instead of an empty plot. const pointRadius = overview.timeSeries.length <= 2 ? 3 : 0; return { labels, datasets: [ { label: "Requests", data: overview.timeSeries.map(pt => pt.requests), borderColor: "#5ad8e6", backgroundColor: "rgba(90, 216, 230, 0.12)", tension: 0.2, borderWidth: 2, pointRadius, pointHoverRadius: 4, fill: true, }, { label: "Errors", data: overview.timeSeries.map(pt => pt.errors), borderColor: "#ff6b7d", backgroundColor: "rgba(255, 107, 125, 0.12)", tension: 0.2, borderWidth: 2, pointRadius, pointHoverRadius: 4, fill: true, }, ], }; }, [overview?.timeSeries, range]); const chartOptions = useMemo(() => { return { responsive: true, maintainAspectRatio: false, interaction: { mode: "index" as const, intersect: false, }, plugins: { legend: { display: true, position: "top" as const, align: "end" as const, labels: { color: chartTheme.legendLabel, boxWidth: 8, usePointStyle: true, font: { size: 11 }, }, }, tooltip: { backgroundColor: chartTheme.tooltipBackground, titleColor: chartTheme.tooltipTitle, bodyColor: chartTheme.tooltipBody, borderColor: chartTheme.tooltipBorder, borderWidth: 1, cornerRadius: 8, padding: 10, }, }, scales: { x: { grid: { color: chartTheme.grid, drawBorder: false, }, ticks: { color: chartTheme.tick, font: { size: 10 }, }, }, y: { grid: { color: chartTheme.grid, drawBorder: false, }, ticks: { color: chartTheme.tick, font: { size: 10 }, }, min: 0, }, }, }; }, [chartTheme]); const columns = useMemo( () => [ { key: "model", header: "Model", render: (item: MessageStats) => (