import { ExternalLink } from 'lucide-react'; import Link from 'next/link'; import { EntityLink } from '@/components/ui/entity'; import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table'; import { Note } from '@/components/ui/section'; import { cn } from '@/lib/cn'; import { fmtDateTime, fmtAgo, fmtUsdPerM, num } from '@/lib/format'; import type { ChangeEvent } from '@/lib/types'; type PriceValue = { input_per_mtok?: unknown; output_per_mtok?: unknown } | null | undefined; function pv(v: unknown): { input: number | null; output: number | null } { const o = (v && typeof v === 'object' ? v : {}) as PriceValue; return { input: num(o?.input_per_mtok), output: num(o?.output_per_mtok) }; } function host(url: string): string { try { return new URL(url).hostname.replace(/^www\./, ''); } catch { return 'source'; } } /** Relative change in %, null when either side is missing or the base is 0. */ export function pctChange(from: number | null, to: number | null): number | null { if (from === null || to === null || from === 0) return null; return ((to - from) / Math.abs(from)) * 100; } /** Old → new price cell: strike-through old, bold new, direction sign. Amber = money. */ function Move({ from, to }: { from: number | null; to: number | null }) { if (from === null && to === null) return —; const dir = from !== null && to !== null ? (to < from ? '↓' : to > from ? '↑' : '=') : ''; return ( {fmtUsdPerM(from)} → {fmtUsdPerM(to)} {dir && dir !== '=' && {dir}} ); } /** ±% chip; green when cheaper, red when dearer. */ export function PctChip({ pct, className }: { pct: number | null; className?: string }) { if (pct === null) return —; const sign = pct > 0 ? '+' : pct < 0 ? '−' : ''; return 0 ? 'text-danger' : 'text-ink-2', className)}>{`${sign}${Math.abs(pct).toFixed(Math.abs(pct) < 10 ? 1 : 0)}%`}; } /** * PRICE_CHANGED events as a dense table: model · provider · input move · output move · % change · when · source. * `providerHref(slug)` optionally turns the provider cell into a filter link (the price terminal). */ export function PriceMovers({ movers, providerHref, limit }: { movers: (ChangeEvent & { percent_change?: unknown })[]; providerHref?: (providerName: string) => string | undefined; limit?: number }) { const rows = movers.filter((e) => e.event_type === 'PRICE_CHANGED' || e.category === 'price').slice(0, limit ?? movers.length); return ( <>
Model Provider Input / 1M Output / 1M Change Observed Source {rows.length === 0 && No price change recorded in this window. The first observation of a price is not a change.} {rows.map((e) => { const o = pv(e.old_value); const n = pv(e.new_value); const provider = typeof e.meta?.provider === 'string' ? e.meta.provider : null; const apiPct = num(e.percent_change); const pIn = pctChange(o.input, n.input); const pOut = pctChange(o.output, n.output); const href = provider && providerHref ? providerHref(provider) : undefined; return ( {e.entity ? : —} {e.entity?.organization && {e.entity.organization.name}} {provider ? href ? {provider} : provider : —} {apiPct !== null ? ( ) : ( {pIn !== null && in } {pOut !== null && out } {pIn === null && pOut === null && —} )} {fmtAgo(e.observed_at)} {e.source_url ? ( {host(e.source_url)} ) : ( — )} ); })}
{rows.length > 0 && Movers are PRICE_CHANGED events emitted when a provider's published price for a model differs from the previous observation. Green = cheaper, red = dearer; % is relative to the previous published price.} ); }