import type { ModelHistogramResponse } from "@/lib/types/logs"; import { useMemo } from "react"; import { Area, AreaChart, Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; import { CHART_COLORS, formatFullTimestamp, formatTimestamp, getModelColor } from "../../utils/chartUtils"; import { ChartErrorBoundary } from "./chartErrorBoundary"; import type { ChartType } from "./chartTypeToggle"; // Sanitize model names to avoid Recharts interpreting dots/brackets as path separators function sanitizeModelKey(model: string): string { return model.replace(/[.\[\]]/g, "_"); } interface ModelUsageChartProps { data: ModelHistogramResponse | null; chartType: ChartType; startTime: number; endTime: number; selectedModel: string; } function CustomTooltip({ active, payload, selectedModel, models }: any) { if (!active || !payload || !payload.length) return null; const data = payload[0]?.payload; if (!data) return null; return (
{formatFullTimestamp(data.timestamp)}
{selectedModel === "all" ? ( <> {models.map((model: string, idx: number) => { const stats = data.by_model?.[model]; if (!stats || stats.total === 0) return null; return (
{model} {stats.total.toLocaleString()}
); })} ) : ( <>
Success {(data.by_model?.[selectedModel]?.success || 0).toLocaleString()}
Error {(data.by_model?.[selectedModel]?.error || 0).toLocaleString()}
)}
); } export function ModelUsageChart({ data, chartType, startTime, endTime, selectedModel }: ModelUsageChartProps) { const { chartData, displayModels } = useMemo(() => { if (!data?.buckets || !data.bucket_size_seconds) { return { chartData: [], displayModels: [] }; } const models = data.models; const processed = data.buckets.map((bucket, index) => { const item: any = { ...bucket, index, formattedTime: formatTimestamp(bucket.timestamp, data.bucket_size_seconds), }; if (selectedModel === "all") { // For "all", show total per model models.forEach((model) => { const safeKey = sanitizeModelKey(model); item[`model_${safeKey}`] = bucket.by_model?.[model]?.total || 0; }); } else { // For specific model, show success/error breakdown const stats = bucket.by_model?.[selectedModel]; item.success = stats?.success || 0; item.error = stats?.error || 0; } return item; }); return { chartData: processed, displayModels: models }; }, [data, selectedModel]); if (!data?.buckets || chartData.length === 0) { return
No data available
; } const commonProps = { data: chartData, margin: { top: 6, right: 4, left: 4, bottom: 0 }, }; return ( {chartType === "bar" ? ( chartData[Math.round(idx)]?.formattedTime || ""} interval="preserveStartEnd" /> v.toLocaleString()} domain={[0, (dataMax: number) => Math.max(dataMax, 1)]} allowDataOverflow={false} /> } cursor={{ fill: "#8c8c8f", fillOpacity: 0.15 }} /> {selectedModel === "all" ? ( displayModels.map((model, idx) => ( )) ) : ( <> )} ) : ( chartData[Math.round(idx)]?.formattedTime || ""} interval="preserveStartEnd" /> v.toLocaleString()} domain={[0, (dataMax: number) => Math.max(dataMax, 1)]} allowDataOverflow={false} /> } /> {selectedModel === "all" ? ( displayModels.map((model, idx) => ( )) ) : ( <> )} )} ); }