import { useCallback, useEffect, useState } from 'react' import { CliErrorPanel } from '../components/CliErrorPanel' import { Dropdown } from '../components/Dropdown' import { EmptyNote } from '../components/EmptyState' import { Panel } from '../components/Panel' import { SectionSkeleton } from '../components/Skeleton' import { usePolled } from '../hooks/usePolled' import { formatCompact, formatUsd } from '../lib/format' import { codeburn } from '../lib/ipc' import type { CompareJsonReport, ComparisonRow, DateRange, ModelStats, Period, WorkingStyleRow } from '../lib/types' function fmtMetric(v: number | null, fn: 'cost' | 'number' | 'percent' | 'decimal'): string { if (v === null) return '—' if (fn === 'cost') return formatUsd(v) if (fn === 'percent') return `${v.toFixed(0)}%` if (fn === 'decimal') return v.toFixed(2) return Math.round(v).toLocaleString('en-US') } // The CLI `compare` command has no --from/--to, so a custom range falls back to // the selected period. Say so instead of silently ignoring the dates. function RangeNote() { return (

Compare uses the selected period, custom dates are not supported yet.

) } export function Compare({ period, provider, range = null, refreshToken = 0, ready = true, }: { period: Period provider: string range?: DateRange | null refreshToken?: number ready?: boolean }) { const models = usePolled( () => codeburn.getCompareModels(period, provider), [period, provider, refreshToken], { enabled: ready, memoKey: `comparemodels|${period}|${provider}` }, ) const [modelA, setModelA] = useState(null) const [modelB, setModelB] = useState(null) useEffect(() => { if (!models.data) return const available = new Set(models.data.map(model => model.model)) setModelA(current => current && available.has(current) ? current : models.data?.[0]?.model ?? null) setModelB(current => current && available.has(current) ? current : models.data?.[1]?.model ?? null) }, [models.data]) const resetToDefaults = useCallback(() => { if (!models.data) return setModelA(models.data[0]?.model ?? null) setModelB(models.data[1]?.model ?? null) }, [models.data]) if (!models.data) { if (models.error) return return } if (models.data.length < 2) { return ( Need at least two models with usage in this range to compare. ) } const modelRows = models.data const nudgeDistinct = (chosen: string) => modelRows.find(model => model.model !== chosen)?.model ?? null return ( <> {range && }
({ value: model.model, label: `${model.model} · ${model.calls.toLocaleString()} calls` }))} onChange={next => { setModelA(next) if (next === modelB) setModelB(nudgeDistinct(next)) }} /> vs ({ value: model.model, label: `${model.model} · ${model.calls.toLocaleString()} calls` }))} onChange={next => { setModelB(next) if (next === modelA) setModelA(nudgeDistinct(next)) }} />
{modelA && modelB && modelA !== modelB && ( )} ) } function CompareReport({ period, provider, modelA, modelB, refreshToken, onError, }: { period: Period provider: string modelA: string modelB: string refreshToken: number onError: () => void }) { const report = usePolled( () => codeburn.getCompare(period, provider, modelA, modelB), [period, provider, modelA, modelB, refreshToken], { memoKey: `compare|${period}|${provider}|${modelA}|${modelB}` }, ) useEffect(() => { if (report.error) onError() }, [report.error, onError]) if (!report.data) { if (report.error) return return } const performance = report.data.metrics.filter(metric => metric.section === 'Performance') const efficiency = report.data.metrics.filter(metric => metric.section === 'Efficiency') return (
) } function MetricCard({ title, rows, modelA, modelB, showWinners = false, }: { title: string rows: Array modelA: string modelB: string showWinners?: boolean }) { return (

{title}

{rows.map(row => { const winner = 'winner' in row ? row.winner : 'none' return (
{row.label} {fmtMetric(row.valueA, row.formatFn)} {fmtMetric(row.valueB, row.formatFn)}
) })}
{showWinners &&
Green = better on that metric.
}
) } function MetricHeader({ modelA, modelB }: { modelA: string; modelB: string }) { return
Metric{modelA}{modelB}
} function CategoryCard({ report }: { report: CompareJsonReport }) { return (

Category head-to-head

One-shot rate · edit turns
{report.modelA.model} {report.modelB.model}
{report.categories.map(category => (
{category.category}
{fmtMetric(category.oneShotRateA, 'percent')} ({category.editTurnsA})
{fmtMetric(category.oneShotRateB, 'percent')} ({category.editTurnsB})
))}
) } function cacheHitRate(model: ModelStats): string { // reads over reads + fresh input (matches menubar-json + compare-stats). const total = model.inputTokens + model.cacheReadTokens return total > 0 ? `${Math.round(model.cacheReadTokens / total * 100)}%` : '—' } function daysOfData(model: ModelStats): string { if (!model.firstSeen || !model.lastSeen) return '—' return String(Math.max(1, Math.round((new Date(model.lastSeen).getTime() - new Date(model.firstSeen).getTime()) / 86_400_000) + 1)) } function ContextCard({ modelA, modelB }: { modelA: ModelStats; modelB: ModelStats }) { const rows = [ ['Calls', modelA.calls.toLocaleString(), modelB.calls.toLocaleString()], ['Total cost', formatUsd(modelA.cost), formatUsd(modelB.cost)], ['Input tokens', formatCompact(modelA.inputTokens), formatCompact(modelB.inputTokens)], ['Output tokens', formatCompact(modelA.outputTokens), formatCompact(modelB.outputTokens)], ['Edit turns', modelA.editTurns.toLocaleString(), modelB.editTurns.toLocaleString()], ['Self-corrections', modelA.selfCorrections.toLocaleString(), modelB.selfCorrections.toLocaleString()], ['Cache hit rate', cacheHitRate(modelA), cacheHitRate(modelB)], ['Days of data', daysOfData(modelA), daysOfData(modelB)], ] return (

Context

{rows.map(([label, valueA, valueB]) => (
{label}{valueA}{valueB}
))}
) }