import type { Metadata } from 'next'; import Link from 'next/link'; import { ComparePicker } from '@/components/compare/compare-picker'; import type { TrayItem } from '@/components/compare/compare-store'; import { CompareTerminal } from '@/components/compare/compare-terminal'; import { EntityBadge } from '@/components/ui/badges'; import { Container, Note, PageHeader } from '@/components/ui/section'; import { EmptyState, Unavailable } from '@/components/ui/unavailable'; import { api, ApiError, apiD1, safe } from '@/lib/api'; import { cn } from '@/lib/cn'; import { fmtInt } from '@/lib/format'; import { routes, SITE_NAME, SITE_URL, typeLabel } from '@/lib/site'; import type { ComparePayload11 } from '@/lib/types'; export const revalidate = 300; type SP = { ids?: string; mode?: string; diff_only?: string }; const MODES = [ { id: 'models', label: 'Models', type: 'model' }, { id: 'providers', label: 'Providers', type: 'provider' }, { id: 'hardware', label: 'Hardware', type: 'hardware' }, { id: 'companies', label: 'Companies', type: 'company' }, { id: 'frameworks', label: 'Frameworks', type: 'framework' }, ]; function parseIds(raw: string | undefined): string[] { return [...new Set((raw ?? '').split(',').map((s) => s.trim()).filter(Boolean))].slice(0, 6); } /** Resolve the comparison, distinguishing a semantic 400 (mixed types / unknown slug) from an outage. */ async function load(ids: string[], mode: string | undefined, diffOnly: boolean): Promise<{ res: ComparePayload11 | null; error: string | null }> { if (ids.length < 2) return { res: null, error: null }; try { return { res: await apiD1.compare(ids, { diff_only: diffOnly, mode }), error: null }; } catch (e) { if (e instanceof ApiError && (e.status === 400 || e.status === 404 || e.status === 422)) return { res: null, error: e.detail ?? 'These entities cannot be compared.' }; return { res: null, error: null }; } } export async function generateMetadata({ searchParams }: { searchParams: Promise }): Promise { const sp = await searchParams; const ids = parseIds(sp.ids); const res = ids.length >= 2 ? await safe(apiD1.compare(ids, { mode: sp.mode })) : null; const canonical = routes.compare(ids.length >= 2 ? ids : undefined); if (!res) return { title: 'Compare — models, providers, hardware side by side', description: 'Compare 2–6 entities of one type on every recorded dimension — architecture, context, capabilities, comparable benchmarks, cheapest deployments, licences — with the source of each value.', alternates: { canonical } }; const names = res.items.map((i) => i.entity.name); const title = `${names.join(' vs ')} — ${typeLabel(res.entity_type).toLowerCase()} comparison`; const description = `${names.join(', ')} compared on ${res.dimensions.length} recorded dimensions (comparable benchmark groups only) with the source of every value. ${SITE_NAME}.`.slice(0, 300); return { title, description, alternates: { canonical }, openGraph: { title: `${title} | ${SITE_NAME}`, description, url: `${SITE_URL}${canonical}`, type: 'website' } }; } export default async function ComparePage({ searchParams }: { searchParams: Promise }) { const sp = await searchParams; const ids = parseIds(sp.ids); const diffOnly = sp.diff_only === '1'; const mode = MODES.some((m) => m.id === sp.mode) ? sp.mode : undefined; const [{ res, error }, examples] = await Promise.all([load(ids, mode, diffOnly), ids.length < 2 ? safe(api.models({ limit: 3, sort: 'quality' })) : Promise.resolve(null)]); const initial: TrayItem[] = res ? res.items.map((it) => ({ slug: it.entity.slug, name: it.entity.name, entity_type: it.entity.entity_type, organization: it.entity.organization?.name ?? null })) : []; const exampleHref = examples && examples.items.length >= 2 ? routes.compare(examples.items.slice(0, 3).map((m) => m.slug)) : null; const activeMode = res ? MODES.find((m) => m.type === res.entity_type || (m.type === 'company' && ['organization', 'lab', 'university'].includes(res.entity_type)) || (m.type === 'framework' && ['library', 'runtime'].includes(res.entity_type)))?.id ?? 'models' : mode ?? 'models'; const ld = res ? { '@context': 'https://schema.org', '@type': 'Dataset', name: `${res.items.map((i) => i.entity.name).join(' vs ')} — comparison`, url: `${SITE_URL}${routes.compare(ids)}`, about: res.items.map((i) => ({ '@type': 'Thing', name: i.entity.name, url: `${SITE_URL}${routes.entity(i.entity)}` })) } : null; return ( {ld &&