'use client' import { useMemo } from 'react' import { Bar, BarChart, Area, AreaChart, CartesianGrid, XAxis, YAxis, Cell, } from 'recharts' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig, } from '@/components/ui/chart' import type { SessionSummary, DailyUsage } from '@/lib/parse-logs' // ─── 1. Model Distribution (horizontal bar) ──────────────────────── const barConfig = { messages: { label: 'Messages', color: 'var(--chart-1)' }, } satisfies ChartConfig const CHART_COLORS = [ 'var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', 'var(--chart-5)', 'var(--chart-6)', 'var(--chart-7)', 'var(--chart-8)', ] function shortenModelName(name: string): string { return name .replace('claude-', '') .replace('anthropic/', '') .replace(/-\d{8}$/, '') } export function ModelDistributionChart({ sessions }: { sessions: SessionSummary[] }) { const data = useMemo(() => { const counts: Record = {} for (const s of sessions) { for (const [m, count] of Object.entries(s.modelCounts || {})) { if (m !== 'unknown') { counts[m] = (counts[m] || 0) + count } } } return Object.entries(counts) .sort((a, b) => b[1] - a[1]) .map(([name, messages], i) => ({ name: shortenModelName(name), fullName: name, messages, fill: CHART_COLORS[i % CHART_COLORS.length], })) }, [sessions]) if (data.length === 0) { return ( Model Distribution Messages per model
No model data available
) } const total = data.reduce((a, d) => a + d.messages, 0) return ( Model Distribution {total.toLocaleString()} total assistant messages across {data.length} model{data.length !== 1 ? 's' : ''} { if (!active || !payload?.length) return null const d = payload[0].payload const pct = ((d.messages / total) * 100).toFixed(1) return (
{d.fullName}
{d.messages.toLocaleString()} messages ({pct}%)
) }} /> {data.map((entry, i) => ( ))}
) } // ─── 2. Model Usage Over Time (stacked area) ─────────────────────── export function ModelUsageOverTimeChart({ sessions }: { sessions: SessionSummary[] }) { const { data, topModels, config } = useMemo(() => { // Find top models by total message count const totalCounts: Record = {} for (const s of sessions) { for (const [m, count] of Object.entries(s.modelCounts || {})) { if (m !== 'unknown') { totalCounts[m] = (totalCounts[m] || 0) + count } } } const sorted = Object.entries(totalCounts).sort((a, b) => b[1] - a[1]) const top = sorted.slice(0, 6).map(([name]) => name) // Group by date const byDate = new Map>() for (const s of sessions) { const date = s.startTime.slice(0, 10) if (!byDate.has(date)) byDate.set(date, {}) const day = byDate.get(date)! for (const [m, count] of Object.entries(s.modelCounts || {})) { if (m !== 'unknown') { day[m] = (day[m] || 0) + count } } } const sortedDates = Array.from(byDate.keys()).sort() const points = sortedDates.map((date) => { const day = byDate.get(date)! const row: Record = { date: date.slice(5) } for (const m of top) { row[shortenModelName(m)] = day[m] || 0 } return row }) const cfg: ChartConfig = {} for (let i = 0; i < top.length; i++) { const short = shortenModelName(top[i]) cfg[short] = { label: short, color: CHART_COLORS[i % CHART_COLORS.length] } } return { data: points, topModels: top.map(shortenModelName), config: cfg } }, [sessions]) if (data.length === 0) { return ( Model Usage Over Time Daily messages by model
No model data available
) } return ( Model Usage Over Time Daily assistant messages by model } /> {topModels.map((m) => ( ))} ) }