HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { CompareButton } from '@/components/compare/compare-button';4import { CompareTrayBar } from '@/components/compare/compare-tray-bar';5import { fmtMemory, HW_KIND_LABELS, interconnect, precisions, releaseOf } from '@/components/hardware/hardware-bits';6import { BTN_GHOST, CTRL, Field, SortTh } from '@/components/intelligence/bits';7import { TerminalLayout } from '@/components/layout/terminal';8import { Chip } from '@/components/ui/badges';9import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';10import { EntityLink } from '@/components/ui/entity';11import { Pagination, withParams } from '@/components/ui/pagination';12import { Note, PageHeader } from '@/components/ui/section';13import { EmptyState, Unavailable } from '@/components/ui/unavailable';14import { intel, safe } from '@/lib/api';15import { fmtDate, fmtInt, num } from '@/lib/format';16import { routes, SITE_NAME, SITE_URL } from '@/lib/site';1718export const revalidate = 600;19type SP = Record<string, string | undefined>;20const LIMIT = 50;21const KEYS = ['q', 'kind', 'manufacturer', 'min_memory', 'sort', 'offset'] as const;22const SORTS = [23 { value: 'memory', label: 'Memory' },24 { value: 'name', label: 'Name' },25 { value: 'updated', label: 'Recently updated' },26];2728const TITLE = 'AI hardware — GPUs, accelerators, SoCs and systems: memory, bandwidth, TDP';29const DESC = 'Accelerators and devices for training and inference with the memory, bandwidth, TDP, precision support and interconnect figures published by their manufacturers — and, for each, which models are estimated to fit.';30export async function generateMetadata({ searchParams }: { searchParams: Promise<SP> }): Promise<Metadata> {31 const sp = await searchParams;32 const filtered = ['q', 'kind', 'manufacturer', 'min_memory', 'offset'].some((k) => sp[k]);33 return { title: TITLE, description: DESC, alternates: { canonical: routes.hardware() }, openGraph: { title: `${TITLE} | ${SITE_NAME}`, description: DESC, url: `${SITE_URL}${routes.hardware()}`, siteName: SITE_NAME }, robots: filtered ? { index: false, follow: true } : undefined };34}3536export default async function HardwarePage({ searchParams }: { searchParams: Promise<SP> }) {37 const sp = await searchParams;38 const cur: Record<string, string | undefined> = {};39 for (const k of KEYS) if (sp[k]) cur[k] = sp[k];40 const offset = Math.max(0, Number(cur.offset) || 0);41 const sort = SORTS.some((s) => s.value === cur.sort) ? (cur.sort as string) : 'memory';42 const page = await safe(intel.hardware({ q: cur.q, kind: cur.kind, manufacturer: cur.manufacturer, min_memory: num(cur.min_memory) ?? undefined, sort, limit: LIMIT, offset }));43 const href = (patch: Record<string, string | number | undefined | null>) => withParams('/hardware', cur, patch);44 const kinds = page?.facets?.kinds ?? [];45 const manufacturers = page?.facets?.manufacturers ?? [];46 const filterCount = ['q', 'kind', 'manufacturer', 'min_memory'].filter((k) => cur[k]).length;47 const items = page?.items ?? [];48 const withTdp = items.filter((e) => num(e.attributes?.tdp_watts) !== null).length;49 const withPrec = items.filter((e) => precisions(e.attributes ?? {}).length).length;5051 const filters = (52 <form action="/hardware" method="get" className="space-y-3" data-hardware-filters>53 <Field label="Name">54 <input name="q" defaultValue={cur.q ?? ''} placeholder="H100, M4 Max…" className={CTRL} />55 </Field>56 <Field label="Kind">57 <select name="kind" defaultValue={cur.kind ?? ''} className={CTRL}>58 <option value="">Any kind</option>59 {kinds.map((k) => (60 <option key={k.value} value={k.value}>61 {HW_KIND_LABELS[k.value] ?? k.value} ({fmtInt(k.count)})62 </option>63 ))}64 {cur.kind && !kinds.some((k) => k.value === cur.kind) && <option value={cur.kind}>{HW_KIND_LABELS[cur.kind] ?? cur.kind}</option>}65 </select>66 </Field>67 <Field label="Manufacturer">68 <select name="manufacturer" defaultValue={cur.manufacturer ?? ''} className={CTRL}>69 <option value="">Any manufacturer</option>70 {manufacturers.map((m) => (71 <option key={m.value} value={m.value}>72 {m.value} ({fmtInt(m.count)})73 </option>74 ))}75 {cur.manufacturer && !manufacturers.some((m) => m.value === cur.manufacturer) && <option value={cur.manufacturer}>{cur.manufacturer}</option>}76 </select>77 </Field>78 <Field label="Memory ≥ (GB)">79 <input name="min_memory" inputMode="numeric" defaultValue={cur.min_memory ?? ''} placeholder="e.g. 64" className={CTRL} />80 </Field>81 <Field label="Sort">82 <select name="sort" defaultValue={sort} className={CTRL}>83 {SORTS.map((s) => (84 <option key={s.value} value={s.value}>85 {s.label}86 </option>87 ))}88 </select>89 </Field>90 <div className="flex gap-2">91 <button type="submit" className="inline-flex h-11 flex-1 items-center justify-center bg-ink lg:h-10 px-3 text-sm font-medium text-canvas hover:opacity-90">92 Apply93 </button>94 <Link href="/hardware" className={BTN_GHOST}>95 Reset96 </Link>97 </div>98 </form>99 );100 const inspector = (101 <div className="space-y-3 text-xs leading-relaxed text-ink-2">102 <p>Figures are the manufacturer's published specifications (spec sheets, tier 2 when read from a reseller). A dash means no source has stated the value — TDP, precision support and interconnect are sparse in the current corpus ({fmtInt(withTdp)} / {fmtInt(withPrec)} of the {fmtInt(items.length)} rows shown carry TDP / precision).</p>103 <p>Memory listed as a range = several configurations (Apple silicon tiers); fit estimates use the largest unless you pick one.</p>104 <p>105 <Link href={routes.runLocally()} className="link">Run locally</Link> · <Link href="/hardware/frontier" className="link">Hardware frontier</Link> · <Link href="/methodology#estimates" className="link">estimate method</Link>106 </p>107 </div>108 );109110 return (111 <TerminalLayout filters={filters} inspector={inspector} filtersTitle="Filters" inspectorTitle="Reading" storageKey="aia-inspector-hardware" filterCount={filterCount}>112 <PageHeader eyebrow="Hardware" title="Hardware" lede="GPUs, accelerators, SoCs and systems with the memory and bandwidth figures their manufacturers publish. Every device links to what it is estimated to run." className="pt-4 md:pt-6" aside={113 <div className="flex flex-col items-start gap-2 md:items-end">114 {page && <p className="tnum text-sm text-ink-3">{fmtInt(page.total)} devices</p>}115 <span className="flex flex-wrap gap-2">116 <Link href="/hardware/frontier" className="inline-flex h-10 items-center border border-rule px-3 text-sm text-ink-2 hover:border-rule-strong hover:text-ink">117 Hardware frontier →118 </Link>119 <Link href={routes.runLocally()} className="inline-flex h-10 items-center border border-rule-strong px-3 text-sm font-medium text-ink hover:border-accent hover:text-accent">120 What fits my machine? →121 </Link>122 </span>123 </div>124 } />125 <div className="pb-16">126 {!page ? (127 <Unavailable what="Hardware" />128 ) : items.length === 0 && !offset ? (129 <EmptyState title={filterCount ? 'No hardware matches these filters' : 'No hardware recorded yet'}>{filterCount ? 'Remove a filter.' : 'Connectors: manufacturer spec pages.'}</EmptyState>130 ) : (131 <>132 <DataTable scroll compact>133 <thead>134 <tr>135 <SortTh active={sort === 'name'} href={href({ sort: 'name', offset: undefined })}>Hardware</SortTh>136 <Th>Kind</Th>137 <SortTh active={sort === 'memory'} href={href({ sort: undefined, offset: undefined })} num dir="desc">Memory</SortTh>138 <Th num>Bandwidth</Th>139 <Th num>TDP</Th>140 <Th>Precision</Th>141 <Th>Interconnect</Th>142 <Th>Release</Th>143 <Th>Fit</Th>144 <Th className="w-24" aria-label="Compare" />145 </tr>146 </thead>147 <tbody>148 {items.length === 0 && <EmptyRow cols={10} />}149 {items.map((e) => {150 const a = e.attributes ?? {};151 const prec = precisions(a);152 const ic = interconnect(a);153 const rel = releaseOf(e);154 return (155 <tr key={e.id} data-hardware-row>156 <Td primary>157 <EntityLink e={e} />158 <span className="block text-xs text-ink-3">{typeof a.manufacturer === 'string' ? a.manufacturer : e.organization?.name ?? '—'}</span>159 </Td>160 <Td label="Kind">{typeof a.kind === 'string' ? <Chip>{HW_KIND_LABELS[a.kind] ?? a.kind}</Chip> : <span className="text-ink-3">—</span>}</Td>161 <Td num label="Memory" className="tnum font-medium">{fmtMemory(a.memory_gb)}{typeof a.memory_type === 'string' && <span className="block text-[10px] font-normal text-ink-3">{a.memory_type}</span>}</Td>162 <Td num label="Bandwidth" className="tnum text-ink-2">{num(a.memory_bandwidth_gbs) === null ? <span className="text-ink-3">—</span> : `${fmtInt(a.memory_bandwidth_gbs)} GB/s`}</Td>163 <Td num label="TDP" className="tnum text-ink-2">{num(a.tdp_watts) === null ? <span className="text-ink-3">—</span> : `${fmtInt(a.tdp_watts)} W`}</Td>164 <Td label="Precision" className="mono text-[11px] text-ink-2">{prec.length ? prec.join(' · ') : <span className="font-sans text-xs text-ink-3">—</span>}</Td>165 <Td label="Interconnect" className="text-xs text-ink-2">{ic ?? <span className="text-ink-3">—</span>}</Td>166 <Td label="Release" className="tnum text-ink-2 whitespace-nowrap">{rel ? fmtDate(rel) : <span className="text-ink-3">—</span>}</Td>167 <Td label="Fit">168 <Link href={`${routes.runLocally()}?hardware=${encodeURIComponent(e.slug)}`} className="link whitespace-nowrap text-xs">169 What can it run? →170 </Link>171 </Td>172 <Td className="text-right">173 <CompareButton e={e} size="sm" />174 </Td>175 </tr>176 );177 })}178 </tbody>179 </DataTable>180 <Pagination total={page.total} limit={LIMIT} offset={offset} makeHref={(o) => href({ offset: o || undefined })} className="mt-4" />181 <Note className="mt-3">182 Each device page lists the models estimated to fit its memory per quantization — labelled as estimates, method on <Link href="/methodology#estimates" className="link">/methodology</Link>. A dash = not stated by any source (never guessed).183 </Note>184 </>185 )}186 </div>187 <CompareTrayBar />188 </TerminalLayout>189 );190}191