import type { ProviderTokenHistogramResponse } 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, formatTokens, getModelColor } from "../../utils/chartUtils"; import { ChartErrorBoundary } from "./chartErrorBoundary"; import type { ChartType } from "./chartTypeToggle"; interface ProviderTokenChartProps { data: ProviderTokenHistogramResponse | null; chartType: ChartType; startTime: number; endTime: number; selectedProvider: string; } function AllProvidersTooltip({ active, payload, providers }: any) { if (!active || !payload || !payload.length) return null; const data = payload[0]?.payload; if (!data) return null; return (
{formatFullTimestamp(data.timestamp)}
{providers.map((provider: string, idx: number) => { const tokens = data.by_provider?.[provider]?.total_tokens || 0; if (tokens === 0) return null; return (
{provider} {formatTokens(tokens)}
); })}
); } function SingleProviderTooltip({ active, payload, provider }: any) { if (!active || !payload || !payload.length) return null; const data = payload[0]?.payload; if (!data) return null; const stats = data.by_provider?.[provider]; if (!stats) return null; return (
{formatFullTimestamp(data.timestamp)}
Input {formatTokens(stats.prompt_tokens || 0)}
Output {formatTokens(stats.completion_tokens || 0)}
Total {formatTokens(stats.total_tokens || 0)}
); } export function ProviderTokenChart({ data, chartType, startTime, endTime, selectedProvider }: ProviderTokenChartProps) { const { chartData, mode, displayProviders } = useMemo(() => { if (!data?.buckets || !data.bucket_size_seconds) { return { chartData: [], mode: "all" as const, displayProviders: [] }; } const isSingleProvider = selectedProvider !== "all"; const providers = isSingleProvider ? [selectedProvider] : data.providers; const processed = data.buckets.map((bucket, index) => { const item: any = { ...bucket, index, formattedTime: formatTimestamp(bucket.timestamp, data.bucket_size_seconds), }; if (isSingleProvider) { const stats = bucket.by_provider?.[selectedProvider]; item.prompt_tokens = stats?.prompt_tokens || 0; item.completion_tokens = stats?.completion_tokens || 0; } else { providers.forEach((provider, idx) => { item[`provider_${idx}`] = bucket.by_provider?.[provider]?.total_tokens || 0; }); } return item; }); return { chartData: processed, mode: isSingleProvider ? ("single" as const) : ("all" as const), displayProviders: providers }; }, [data, selectedProvider]); 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" /> Math.max(dataMax, 1)]} allowDataOverflow={false} /> {mode === "single" ? ( <> } cursor={{ fill: "#8c8c8f", fillOpacity: 0.15 }} /> ) : ( <> } cursor={{ fill: "#8c8c8f", fillOpacity: 0.15 }} /> {displayProviders.map((provider, idx) => ( ))} )} ) : ( chartData[Math.round(idx)]?.formattedTime || ""} interval="preserveStartEnd" /> Math.max(dataMax, 1)]} allowDataOverflow={false} /> {mode === "single" ? ( <> } /> ) : ( <> } /> {displayProviders.map((provider, idx) => ( ))} )} )} ); }