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%
4.3 KB · 93 lines tsx
Raw Blame History
1import { ExternalLink } from 'lucide-react';2import Link from 'next/link';3import { hostOf, fmtInt, num, plural } from '@/lib/format';4import { routes } from '@/lib/site';5import type { TodayItem, TodaySection } from '@/lib/types';6import { ChangeRow } from './change-row';78/** Fixed order of the Today in AI 2.0 sections (API keys) — sections absent from the payload are listed as quiet. */9export const TODAY_ORDER = ['MAJOR_RELEASES', 'PRICE_MOVES', 'BENCHMARK_MOVES', 'MODEL_CHANGES', 'RESEARCH', 'OPEN_WEIGHT_RELEASES', 'DEPRECATIONS', 'PROVIDER_CHANGES', 'HARDWARE'] as const;10export const TODAY_LABELS: Record<string, string> = {11  MAJOR_RELEASES: 'Major releases',12  PRICE_MOVES: 'Price moves',13  BENCHMARK_MOVES: 'Benchmark moves',14  MODEL_CHANGES: 'Model changes',15  RESEARCH: 'Research',16  OPEN_WEIGHT_RELEASES: 'Open-weight releases',17  DEPRECATIONS: 'Deprecations',18  PROVIDER_CHANGES: 'Provider changes',19  HARDWARE: 'Hardware',20};21/** Section key → the `/changes` filter that lists everything behind it. */22export const TODAY_FEED_HREF: Record<string, (date: string) => string> = {23  MAJOR_RELEASES: (d) => `/changes?importance_min=3&since=${d}&until=${d}`,24  PRICE_MOVES: (d) => `/changes?category=price&since=${d}&until=${d}`,25  BENCHMARK_MOVES: (d) => `/changes?category=benchmark&since=${d}&until=${d}`,26  MODEL_CHANGES: (d) => `/changes?category=model&since=${d}&until=${d}`,27  RESEARCH: (d) => `/changes?category=paper&since=${d}&until=${d}`,28  OPEN_WEIGHT_RELEASES: (d) => `/changes?type=NEW_MODEL&since=${d}&until=${d}`,29  DEPRECATIONS: (d) => `/changes?type=DEPRECATION_ANNOUNCED&since=${d}&until=${d}`,30  PROVIDER_CHANGES: (d) => `/changes?category=provider&since=${d}&until=${d}`,31  HARDWARE: (d) => `/changes?category=hardware&since=${d}&until=${d}`,32};3334export function orderedSections(today: TodaySection[] | undefined): { present: TodaySection[]; quiet: string[] } {35  const byKey = new Map((today ?? []).map((s) => [s.key, s]));36  const present: TodaySection[] = [];37  const quiet: string[] = [];38  for (const k of TODAY_ORDER) {39    const s = byKey.get(k);40    if (s && s.items.length) present.push(s);41    else quiet.push(k);42  }43  for (const s of today ?? []) if (!(TODAY_ORDER as readonly string[]).includes(s.key) && s.items.length) present.push(s);44  return { present, quiet };45}4647/** "1 release · 4 documents" + expandable source list for grouped items (events sharing a group_key). */48export function GroupedSources({ it }: { it: TodayItem }) {49  const grouped = num(it.grouped_events) ?? 1;50  const docs = it.documents ?? [];51  const sources = num(it.sources) ?? docs.length;52  if (grouped <= 1 && docs.length <= 1) return null;53  const noun = it.event_type === 'RELEASE' || it.event_type === 'NEW_MODEL' ? 'release' : it.event_type === 'NEW_PAPER' ? 'paper' : 'event';54  return (55    <details className="mt-1 text-xs text-ink-3" data-grouped>56      <summary className="cursor-pointer select-none hover:text-ink">57        1 {noun} · {fmtInt(grouped)} {plural(grouped, 'event')} · {fmtInt(docs.length || sources)} {plural(docs.length || sources, 'document')}58        {sources > 1 && docs.length !== sources ? ` · ${fmtInt(sources)} sources` : ''}59      </summary>60      <ul className="mt-1 space-y-0.5 pl-3">61        {docs.map((u) => (62          <li key={u}>63            <a href={u} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-ink-2 hover:text-accent">64              {hostOf(u) ?? u} <ExternalLink className="size-3" aria-hidden />65              <span className="truncate text-ink-3">{u.replace(/^https?:\/\/[^/]+/, '').slice(0, 60)}</span>66            </a>67          </li>68        ))}69        {docs.length === 0 && <li>Documents not listed by the API for this group.</li>}70      </ul>71    </details>72  );73}7475export function TodayItemRow({ it }: { it: TodayItem }) {76  return <ChangeRow e={it} showDate live={false} trailing={<GroupedSources it={it} />} />;77}7879export function QuietLine({ quiet, labels }: { quiet: string[]; labels?: Record<string, string> }) {80  if (!quiet.length) return null;81  return (82    <p className="mt-6 text-xs text-ink-3">83      Quiet today: {quiet.map((k, i) => (84        <span key={k}>85          {i > 0 && ' · '}86          {labels?.[k] ?? TODAY_LABELS[k] ?? k}87        </span>88      ))}89      . <Link href={routes.timeline()} className="link">Timeline →</Link>90    </p>91  );92}93