import { Search, X } from 'lucide-react'; import type { Metadata } from 'next'; import Link from 'next/link'; import { Chip, EntityBadge, OpennessBadge } from '@/components/ui/badges'; import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table'; import { cheapestPrice, EntityLink, EntityRow, QualityMark } from '@/components/ui/entity'; import { Hint } from '@/components/ui/hint'; import { Pagination, withParams } from '@/components/ui/pagination'; import { Container, Note, PageHeader } from '@/components/ui/section'; import { EmptyState, Unavailable } from '@/components/ui/unavailable'; import { apiD3, safe } from '@/lib/api'; import { fmtDate, fmtInt, fmtParams, fmtTokens, num } from '@/lib/format'; import { EXAMPLE_QUERIES, exploreNav, PALETTE_PREFIXES, routes, typeLabel } from '@/lib/site'; import type { EntitySummary } from '@/lib/types'; import { compiledType, impliedColumns, toModelsParams, withoutSpan } from './compiled'; type SP = { q?: string; type?: string; offset?: string }; const LIMIT = 30; export async function generateMetadata({ searchParams }: { searchParams: Promise }): Promise { const { q } = await searchParams; return { title: q ? `“${q}” — search` : 'Search', description: 'Plain-English search over the AI Atlas graph: the compiler turns your words into filters (organization, parameters, context, openness, licence, dates, price) and shows them back as removable chips.', robots: { index: false, follow: true }, alternates: { canonical: '/search' } }; } export default async function SearchPage({ searchParams }: { searchParams: Promise }) { const sp = await searchParams; const q = (sp.q ?? '').trim(); const type = sp.type ?? ''; const offset = Math.max(0, Number(sp.offset) || 0); const res = q ? await safe(apiD3.search(q, { type: type || undefined, limit: LIMIT, offset })) : null; const query = res?.query; const compiled = query?.compiled ?? []; const cType = compiledType(query); const isModelTable = (type || cType) === 'model' && compiled.length > 0; const mapped = isModelTable ? toModelsParams(compiled, query?.residual) : null; const cols = impliedColumns(compiled, query?.sort); const current = { q, type }; const unrec = query?.unrecognised ?? []; const exploreHref = mapped ? `/explore?type=model&${mapped.params.toString()}` : `/explore${cType ? `?type=${encodeURIComponent(cType)}` : ''}`; return ( Results for “{q}” : 'Search the atlas'} lede={!q ? 'Names, ids, providers, benchmarks — or a plain-English question. The compiler turns your words into filters and shows them back as chips you can remove.' : undefined}>
{type && }
{q && res && (

Compiled as {query?.semantic && · semantic ranking on}

{compiled.length === 0 ? (

No structured filter recognised — full-text search for “{q}”{cType ? ` among ${typeLabel(cType, true).toLowerCase()}` : ''}.

) : (
    {compiled.map((f, i) => { const nextText = withoutSpan(q, f.source_span); const removable = !!f.source_span && nextText !== q; return (
  • {f.filter} {f.label} {removable ? ( ) : ( · )}
  • ); })}
)}

{query?.sort && ( Sort: {query.sort} )} {query?.residual?.trim() && ( Free text: “{query.residual.trim()}” )} {unrec.length > 0 && ( Not understood: {unrec.join(' · ')} )} {query?.note && {query.note}}

{mapped && ( Open in Models → )} Open in Explore builder → {mapped?.links.map((l) => ( {l.label} ))}
{mapped && mapped.unmapped.length > 0 && ( Not expressible in /models: {mapped.unmapped.map((u, i) => ( {i > 0 && ' · '} {u.filter.label} ({u.why}) ))} . )}
)} {q && (
    {[{ label: 'All', type: '' }, ...exploreNav.map((n) => ({ label: n.label.replace(' & Pricing', ''), type: n.type }))].map((t) => { const on = t.type === type; return (
  • {t.label}
  • ); })}
)}
{!q ? (

Try

    {[...EXAMPLE_QUERIES, 'open reasoning models over 30B released in 2026', 'cheapest models under $1/M output with 1M context', 'papers by DeepSeek'].map((ex) => (
  • {ex}
  • ))}

Prefixes (also in the ⌘K palette)

    {Object.entries(PALETTE_PREFIXES).map(([k, v]) => (
  • {k}: {v.label}
  • ))}

Prefer a form? Open the Explore builder →

) : !res ? ( ) : res.items.length === 0 ? ( Try fewer words, a provider's API id, or the builder. {compiled.length > 0 && ( <> {' '} Remove a chip above to widen the search. )} ) : (

{fmtInt(res.total)} result{res.total === 1 ? '' : 's'} {isModelTable ? ' · dense model table (columns implied by the query)' : ''}

{isModelTable ? : (
    {res.items.map((e) => ( } /> ))}
)} withParams('/search', current, { offset: o || undefined })} className="mt-4" />
)}
); } function ModelResults({ items, cols }: { items: (EntitySummary & { rank: number })[]; cols: Set }) { return ( Model Organization {cols.has('params') && Params} {cols.has('context') && Context} {cols.has('price') && Cheapest input} {cols.has('openness') && Openness · licence} {cols.has('reasoning') && Reasoning} {cols.has('modalities') && Modalities} {cols.has('released') && Released} Quality {items.length === 0 && No rows.} {items.map((e) => { const a = e.attributes ?? {}; const ap = num(a.active_parameter_count); const p = num(a.parameter_count); return ( {e.entity_type !== 'model' && } {e.organization ? {e.organization.name} : —} {cols.has('params') && ( {p === null ? — : ap !== null && ap !== p ? `${fmtParams(p)} · ${fmtParams(ap)} active` : fmtParams(p)} )} {cols.has('context') && {num(a.context_length) === null ? — : fmtTokens(a.context_length)}} {cols.has('price') && {cheapestPrice(e) ?? —}} {cols.has('openness') && ( {typeof a.openness === 'string' ? : —} {typeof a.license === 'string' && {a.license}} )} {cols.has('reasoning') && {a.reasoning === true ? 'yes' : a.reasoning === false ? 'no' : —}} {cols.has('modalities') && {Array.isArray(a.modalities) && a.modalities.length ? (a.modalities as unknown[]).map(String).join(', ') : —}} {cols.has('released') && {typeof a.release_date === 'string' ? fmtDate(a.release_date) : —}} ); })} ); }