'use client'; import { ExternalLink } from 'lucide-react'; import Link from 'next/link'; import { usePathname, useSearchParams } from 'next/navigation'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { Chip } from '@/components/ui/badges'; import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table'; import { EntityLink } from '@/components/ui/entity'; import { Note } from '@/components/ui/section'; import { clientIntel } from '@/lib/client-api'; import { cn } from '@/lib/cn'; import { fmtAgo, fmtInt, fmtTokens, fmtUsdPerM, hostOf, num } from '@/lib/format'; import { routes } from '@/lib/site'; import type { CostContextPayload, CostPayload } from '@/lib/types'; import { CTRL_LG, Field, Methodology } from './bits'; import { ModelPicker } from './model-picker'; /* Cost calculator (client, same-origin API only). Two tabs: - Workload: `/cost?model=&provider=&input_tokens=&output_tokens=&requests_per_day=&cached_share=&batch=` → per request / daily / monthly / annual for EVERY current deployment side by side. The cheapest column is bold — that is a fact about this workload, not a verdict about the model. - Context cost: `/cost/context?tokens=` → "how much does a fully populated N-token context cost?" for every offer whose context ≥ N. All inputs live in the URL (shareable); every number and the methodology come from the API. */ const PRESETS_CTX = [ { value: 128_000, label: '128K' }, { value: 200_000, label: '200K' }, { value: 1_000_000, label: '1M' }, { value: 2_000_000, label: '2M' }, ]; const fmtMoney = (v: unknown, digits?: number) => { const n = num(v); if (n === null) return '—'; if (digits !== undefined) return `$${n.toFixed(digits)}`; if (n === 0) return '$0'; if (n < 0.001) return `$${n.toFixed(6).replace(/0+$/, '')}`; if (n < 1) return `$${n.toFixed(4).replace(/0+$/, '').replace(/\.$/, '')}`; if (n < 1000) return `$${n.toFixed(2)}`; return `$${new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(n)}`; }; type State = { model: string; modelName: string; provider: string; input_tokens: string; output_tokens: string; requests_per_day: string; cached: string; batch: boolean; tab: 'workload' | 'context'; tokens: string }; function read(sp: URLSearchParams): State { return { model: sp.get('model') ?? '', modelName: sp.get('name') ?? '', provider: sp.get('provider') ?? '', input_tokens: sp.get('input_tokens') ?? '1000', output_tokens: sp.get('output_tokens') ?? '500', requests_per_day: sp.get('requests_per_day') ?? '1000', cached: sp.get('cached') ?? '0', batch: sp.get('batch') === '1', tab: sp.get('tab') === 'context' ? 'context' : 'workload', tokens: sp.get('tokens') ?? '1000000', }; } export function Calculator() { const sp = useSearchParams(); const pathname = usePathname(); const [s, setS] = useState(() => read(new URLSearchParams(sp.toString()))); const [cost, setCost] = useState(null); const [ctx, setCtx] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); // URL ← state (debounced, replace) useEffect(() => { const t = setTimeout(() => { const p = new URLSearchParams(); if (s.model) p.set('model', s.model); if (s.modelName && s.modelName !== s.model) p.set('name', s.modelName); if (s.provider) p.set('provider', s.provider); if (s.input_tokens !== '1000') p.set('input_tokens', s.input_tokens); if (s.output_tokens !== '500') p.set('output_tokens', s.output_tokens); if (s.requests_per_day !== '1000') p.set('requests_per_day', s.requests_per_day); if (s.cached !== '0') p.set('cached', s.cached); if (s.batch) p.set('batch', '1'); if (s.tab === 'context') p.set('tab', 'context'); if (s.tokens !== '1000000') p.set('tokens', s.tokens); const q = p.toString(); const next = q ? `${pathname}?${q}` : pathname; const current = `${window.location.pathname}${window.location.search}`; // replaceState (not router.replace): the URL mirrors the inputs without a server round trip on every keystroke if (next !== current) window.history.replaceState(null, '', next); }, 250); return () => clearTimeout(t); }, [s, pathname]); const costQs = useMemo(() => { if (!s.model) return null; const p = new URLSearchParams({ model: s.model }); if (s.provider) p.set('provider', s.provider); p.set('input_tokens', String(Math.max(0, Math.round(num(s.input_tokens) ?? 0)))); p.set('output_tokens', String(Math.max(0, Math.round(num(s.output_tokens) ?? 0)))); p.set('requests_per_day', String(Math.max(0, num(s.requests_per_day) ?? 0))); p.set('cached_share', String(Math.min(1, Math.max(0, (num(s.cached) ?? 0) / 100)))); p.set('batch', s.batch ? '1' : '0'); return p.toString(); }, [s.model, s.provider, s.input_tokens, s.output_tokens, s.requests_per_day, s.cached, s.batch]); useEffect(() => { if (s.tab !== 'workload' || !costQs) return; const ctrl = new AbortController(); setBusy(true); setError(null); const t = setTimeout(() => { clientIntel .cost(costQs, ctrl.signal) .then((r) => { setCost(r); setBusy(false); }) .catch((e) => { if (ctrl.signal.aborted) return; setError(e instanceof Error ? e.message : 'unavailable'); setBusy(false); }); }, 200); return () => { clearTimeout(t); ctrl.abort(); }; }, [costQs, s.tab]); const ctxQs = useMemo(() => { const t = Math.max(1, Math.round(num(s.tokens) ?? 0)); const p = new URLSearchParams({ tokens: String(t), limit: '60' }); if (s.model) p.set('model', s.model); return p.toString(); }, [s.tokens, s.model]); useEffect(() => { if (s.tab !== 'context') return; const ctrl = new AbortController(); setBusy(true); setError(null); const t = setTimeout(() => { clientIntel .costContext(ctxQs, ctrl.signal) .then((r) => { setCtx(r); setBusy(false); }) .catch((e) => { if (ctrl.signal.aborted) return; setError(e instanceof Error ? e.message : 'unavailable'); setBusy(false); }); }, 200); return () => { clearTimeout(t); ctrl.abort(); }; }, [ctxQs, s.tab]); const set = useCallback((k: K, v: State[K]) => setS((p) => ({ ...p, [k]: v })), []); const providers = useMemo(() => { const m = new Map(); for (const it of cost?.items ?? []) m.set(it.deployment.provider.slug, it.deployment.provider.name); return [...m.entries()]; }, [cost]); const cheapest = useMemo(() => { const vals = (cost?.items ?? []).map((it) => num(it.cost.monthly)).filter((v): v is number => v !== null); return vals.length ? Math.min(...vals) : null; }, [cost]); const tabBtn = (id: State['tab'], label: string) => ( ); return (
{tabBtn('workload', 'Workload cost')} {tabBtn('context', 'Context cost')}
{/* --------------------------------------------------------------------------------------------------- inputs */}
{s.model} : 'Any canonical model; suggestions from the atlas.'}> setS((p) => ({ ...p, model: slug, modelName: name, provider: '' }))} /> {s.tab === 'workload' ? ( <> set('requests_per_day', e.target.value.replace(/[^\d.]/g, ''))} className={CTRL_LG} /> set('input_tokens', e.target.value.replace(/[^\d]/g, ''))} className={CTRL_LG} /> set('output_tokens', e.target.value.replace(/[^\d]/g, ''))} className={CTRL_LG} /> set('cached', e.target.value)} className="h-11 w-full accent-[var(--accent)]" aria-label="Cached input share (%)" /> {s.cached}% ) : ( {PRESETS_CTX.map((p) => ( ))} set('tokens', e.target.value.replace(/[^\d]/g, ''))} className={cn(CTRL_LG, 'w-40 flex-none')} aria-label="Custom token count" /> )}
{/* --------------------------------------------------------------------------------------------------- results */} {s.tab === 'workload' ? ( !s.model ? (

Pick a model to price this workload across every provider that currently serves it.

) : error ? (

Cost unavailable ({error}). The slug must be a canonical model — try the suggestions.

) : !cost ? (

Computing…

) : (

{cost.model && } {fmtInt(cost.inputs.input_tokens)} in + {fmtInt(cost.inputs.output_tokens)} out tokens × {fmtInt(cost.inputs.requests_per_day)} req/day · cached {Math.round((num(cost.inputs.cached_share) ?? 0) * 100)}% · batch {cost.inputs.batch ? 'on' : 'off'} {fmtInt(cost.total)} current deployment{cost.total === 1 ? '' : 's'}

Provider Eff. input / 1M Eff. output / 1M Per request Daily Monthly Annual Price rows used Source {cost.items.length === 0 && No current deployment for this model{s.provider ? ' at this provider' : ''}. Without a published price there is nothing to compute.} {cost.items.map((it) => { const d = it.deployment; const c = it.cost; const best = cheapest !== null && num(c.monthly) === cheapest && cost.items.length > 1; return ( {best && cheapest here} {d.status !== 'active' && {d.status}} {d.provider_model_id && {d.provider_model_id}} {c.notes.length > 0 && (
    {c.notes.map((n) => (
  • {n}
  • ))}
)} {fmtUsdPerM(c.effective_input_per_mtok)} {fmtUsdPerM(c.effective_output_per_mtok)} {fmtMoney(c.per_request)} {fmtMoney(c.daily)} {fmtMoney(c.monthly)} {fmtMoney(c.annual)} in {fmtUsdPerM(d.prices.input)} · out {fmtUsdPerM(d.prices.output)} {num(d.prices.cached_input) !== null && <> · cached {fmtUsdPerM(d.prices.cached_input)}} {num(d.prices.batch_input) !== null && <> · batch in {fmtUsdPerM(d.prices.batch_input)}} {num(d.prices.batch_output) !== null && <> · batch out {fmtUsdPerM(d.prices.batch_output)}} {num(d.prices.per_request) !== null && <> · fee {fmtMoney(d.prices.per_request)}/req} {num(d.context_length) !== null && context {fmtTokens(d.context_length)} · observed {fmtAgo(d.observed_at)}} {d.source_url ? ( {hostOf(d.source_url) ?? 'source'} T{d.tier} ) : ( — )} ); })}
Bold = lowest monthly cost for this workload among the deployments listed — not a verdict about the model or the provider. Prices are the current published rows; cached/batch prices apply only when published (otherwise the standard price is used and a note says so). {cost.note && {cost.note}}
) ) : error ? (

Context cost unavailable ({error}).

) : !ctx ? (

Computing…

) : (

How much does a fully populated {fmtTokens(ctx.tokens)}-token context cost? {fmtInt(ctx.total)} offers with context ≥ {fmtTokens(ctx.tokens)}

Model Provider Context Input / 1M Cost per fill Source {ctx.items.length === 0 && No current offer advertises a context window of at least {fmtTokens(ctx.tokens)} tokens{s.model ? ' for this model' : ''}.} {ctx.items.map((it, i) => { const d = it.deployment; return ( {d.model.organization && {d.model.organization.name}} {d.provider.name} {fmtTokens(it.context_length)} {it.context_source} {fmtUsdPerM(d.prices.input)} 1 && 'font-semibold text-accent-2')}>{fmtMoney(it.cost_usd)} {d.source_url ? ( {hostOf(d.source_url) ?? 'source'} T{d.tier} ) : ( — )} ); })} Cheapest first (API order). Bold marks the lowest cost among the offers shown — a fact about the input price, not a quality judgement.
)}
); }