HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
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