SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
6.2 KB · 123 lines tsx
Raw Blame History
1import { ExternalLink } from 'lucide-react';2import Link from 'next/link';3import { EntityLink } from '@/components/ui/entity';4import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';5import { Note } from '@/components/ui/section';6import { cn } from '@/lib/cn';7import { fmtDateTime, fmtAgo, fmtUsdPerM, num } from '@/lib/format';8import type { ChangeEvent } from '@/lib/types';910type PriceValue = { input_per_mtok?: unknown; output_per_mtok?: unknown } | null | undefined;1112function pv(v: unknown): { input: number | null; output: number | null } {13  const o = (v && typeof v === 'object' ? v : {}) as PriceValue;14  return { input: num(o?.input_per_mtok), output: num(o?.output_per_mtok) };15}1617function host(url: string): string {18  try {19    return new URL(url).hostname.replace(/^www\./, '');20  } catch {21    return 'source';22  }23}2425/** Relative change in %, null when either side is missing or the base is 0. */26export function pctChange(from: number | null, to: number | null): number | null {27  if (from === null || to === null || from === 0) return null;28  return ((to - from) / Math.abs(from)) * 100;29}3031/** Old → new price cell: strike-through old, bold new, direction sign. Amber = money. */32function Move({ from, to }: { from: number | null; to: number | null }) {33  if (from === null && to === null) return <span className="text-ink-3">—</span>;34  const dir = from !== null && to !== null ? (to < from ? '↓' : to > from ? '↑' : '=') : '';35  return (36    <span className="tnum inline-flex items-center gap-1.5">37      <span className="text-ink-3 line-through decoration-ink-3/60">{fmtUsdPerM(from)}</span>38      <span aria-hidden className="text-ink-3">→</span>39      <span className={dir === '=' ? 'text-ink-2' : 'font-semibold text-accent-2'}>{fmtUsdPerM(to)}</span>40      {dir && dir !== '=' && <span className={dir === '↓' ? 'text-positive' : 'text-danger'} aria-label={dir === '↓' ? 'decrease' : 'increase'}>{dir}</span>}41    </span>42  );43}4445/** ±% chip; green when cheaper, red when dearer. */46export function PctChip({ pct, className }: { pct: number | null; className?: string }) {47  if (pct === null) return <span className={cn('text-ink-3', className)}>—</span>;48  const sign = pct > 0 ? '+' : pct < 0 ? '−' : '';49  return <span className={cn('tnum font-medium', pct < 0 ? 'text-positive' : pct > 0 ? 'text-danger' : 'text-ink-2', className)}>{`${sign}${Math.abs(pct).toFixed(Math.abs(pct) < 10 ? 1 : 0)}%`}</span>;50}5152/**53 * PRICE_CHANGED events as a dense table: model · provider · input move · output move · % change · when · source.54 * `providerHref(slug)` optionally turns the provider cell into a filter link (the price terminal).55 */56export function PriceMovers({ movers, providerHref, limit }: { movers: (ChangeEvent & { percent_change?: unknown })[]; providerHref?: (providerName: string) => string | undefined; limit?: number }) {57  const rows = movers.filter((e) => e.event_type === 'PRICE_CHANGED' || e.category === 'price').slice(0, limit ?? movers.length);58  return (59    <>60      <div className="md:overflow-x-auto">61      <DataTable caption="Recent price changes">62        <thead>63          <tr>64            <Th>Model</Th>65            <Th>Provider</Th>66            <Th>Input / 1M</Th>67            <Th>Output / 1M</Th>68            <Th num>Change</Th>69            <Th>Observed</Th>70            <Th>Source</Th>71          </tr>72        </thead>73        <tbody>74          {rows.length === 0 && <EmptyRow cols={7}>No price change recorded in this window. The first observation of a price is not a change.</EmptyRow>}75          {rows.map((e) => {76            const o = pv(e.old_value);77            const n = pv(e.new_value);78            const provider = typeof e.meta?.provider === 'string' ? e.meta.provider : null;79            const apiPct = num(e.percent_change);80            const pIn = pctChange(o.input, n.input);81            const pOut = pctChange(o.output, n.output);82            const href = provider && providerHref ? providerHref(provider) : undefined;83            return (84              <tr key={e.id}>85                <Td primary>86                  {e.entity ? <EntityLink e={e.entity} /> : <span className="text-ink-3">—</span>}87                  {e.entity?.organization && <span className="ml-2 text-xs text-ink-3">{e.entity.organization.name}</span>}88                </Td>89                <Td label="Provider" className="text-ink-2">{provider ? href ? <Link href={href} className="hover:text-accent" title="Filter by this provider">{provider}</Link> : provider : <span className="text-ink-3">—</span>}</Td>90                <Td label="Input / 1M"><Move from={o.input} to={n.input} /></Td>91                <Td label="Output / 1M"><Move from={o.output} to={n.output} /></Td>92                <Td num label="Change" className="tnum text-xs">93                  {apiPct !== null ? (94                    <PctChip pct={apiPct} />95                  ) : (96                    <span className="inline-flex flex-col items-end gap-0.5 md:flex-row md:items-center md:gap-2">97                      {pIn !== null && <span>in <PctChip pct={pIn} /></span>}98                      {pOut !== null && <span>out <PctChip pct={pOut} /></span>}99                      {pIn === null && pOut === null && <span className="text-ink-3">—</span>}100                    </span>101                  )}102                </Td>103                <Td label="Observed" className="text-ink-2" title={fmtDateTime(e.observed_at)}>{fmtAgo(e.observed_at)}</Td>104                <Td label="Source">105                  {e.source_url ? (106                    <a href={e.source_url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-xs text-ink-2 hover:text-accent">107                      {host(e.source_url)} <ExternalLink className="size-3" aria-hidden />108                    </a>109                  ) : (110                    <span className="text-ink-3">—</span>111                  )}112                </Td>113              </tr>114            );115          })}116        </tbody>117      </DataTable>118      </div>119      {rows.length > 0 && <Note className="mt-3">Movers are PRICE_CHANGED events emitted when a provider&apos;s published price for a model differs from the previous observation. Green = cheaper, red = dearer; % is relative to the previous published price.</Note>}120    </>121  );122}123