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%
13.4 KB · 228 lines tsx
Raw Blame History
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 { QUANTS } from '@/components/hardware/fit-form';6import { FINDER_USE_CASES, FinderForm, type FinderInputs, MODALITIES } from '@/components/intelligence/finder-form';7import { FitCell, Methodology, RankChips } from '@/components/intelligence/bits';8import { TerminalLayout } from '@/components/layout/terminal';9import { Chip, Estimated, OpennessBadge } from '@/components/ui/badges';10import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';11import { EntityLink } from '@/components/ui/entity';12import { Note, PageHeader } from '@/components/ui/section';13import { EmptyState, Unavailable } from '@/components/ui/unavailable';14import { WatchButton } from '@/components/watchlist/watch-button';15import { ApiError, intel, safe } from '@/lib/api';16import { fmtDate, fmtInt, fmtParams, fmtTokens, fmtUsdPerM, num } from '@/lib/format';17import { routes, SITE_NAME, SITE_URL } from '@/lib/site';18import type { FinderPayload } from '@/lib/types';1920export const revalidate = 300;21type SP = Record<string, string | string[] | undefined>;22const one = (v: string | string[] | undefined) => (Array.isArray(v) ? v[0] : v);2324function parse(sp: SP): FinderInputs {25  const custom = num(one(sp.memory_custom));26  const preset = num(one(sp.memory_gb));27  const mods = (Array.isArray(sp.mod) ? sp.mod : sp.mod ? [sp.mod] : one(sp.modalities)?.split(',') ?? []).filter((m) => MODALITIES.includes(m));28  const dep = one(sp.deployment);29  const lic = one(sp.license);30  return {31    useCase: FINDER_USE_CASES.some((u) => u.value === one(sp.use_case)) ? (one(sp.use_case) as string) : '',32    deployment: dep === 'local' || dep === 'api' ? dep : 'any',33    memory: custom !== null && custom > 0 ? custom : preset !== null && preset > 0 ? preset : null,34    quant: QUANTS.some((q) => q.value === one(sp.quant)) ? (one(sp.quant) as string) : '4bit',35    contextMin: num(one(sp.context_min)),36    license: lic === 'commercial' ? 'commercial' : 'any',37    openness: ['open-source', 'open-weights', 'restricted-weights', 'proprietary'].includes(one(sp.openness) ?? '') ? (one(sp.openness) as string) : '',38    maxIn: num(one(sp.max_input_price)),39    maxOut: num(one(sp.max_output_price)),40    modalities: mods,41  };42}43const active = (v: FinderInputs) => [v.useCase, v.deployment !== 'any', v.memory, v.contextMin, v.license !== 'any', v.openness, v.maxIn, v.maxOut, v.modalities.length].filter(Boolean).length;4445const TITLE = 'Find a model — guided search over observed dimensions';46const DESC = 'Tell the atlas what you need — use case, local or API deployment, memory, licence, openness, context, price ceiling, modalities — and it lists the canonical models that satisfy the criteria, with the reasons, their observed dimensions, an estimated local fit and current deployments. Deterministic rules, no composite score, no single winner.';47export async function generateMetadata({ searchParams }: { searchParams: Promise<SP> }): Promise<Metadata> {48  const v = parse(await searchParams);49  const n = active(v);50  const title = n ? `Models for ${[FINDER_USE_CASES.find((u) => u.value === v.useCase)?.label, v.deployment !== 'any' ? v.deployment : null, v.memory ? `${v.memory} GB` : null].filter(Boolean).join(' · ') || 'your criteria'}` : TITLE;51  return { title, description: DESC, alternates: { canonical: routes.findAModel() }, openGraph: { title: `${title} | ${SITE_NAME}`, description: DESC, url: `${SITE_URL}${routes.findAModel()}`, siteName: SITE_NAME }, robots: n ? { index: false, follow: true } : undefined };52}5354export default async function FindAModelPage({ searchParams }: { searchParams: Promise<SP> }) {55  const sp = await searchParams;56  const v = parse(sp);57  const n = active(v);58  let res: FinderPayload | null = null;59  let error: string | null = null;60  if (n > 0) {61    try {62      res = await intel.findAModel({ use_case: v.useCase || undefined, deployment: v.deployment, memory_gb: v.memory ?? undefined, quant: v.deployment === 'local' || v.memory ? v.quant : undefined, context_min: v.contextMin ?? undefined, license: v.license, openness: v.openness || undefined, max_input_price: v.maxIn ?? undefined, max_output_price: v.maxOut ?? undefined, modalities: v.modalities.length ? v.modalities.join(',') : undefined, limit: 40 });63    } catch (e) {64      error = e instanceof ApiError ? e.detail ?? e.message : 'API unreachable';65    }66  }67  const rules = res?.rules ?? (await safe(intel.methodology()))?.find_a_model ?? null;68  const rulesObj = rules && typeof rules === 'object' ? (rules as Record<string, string>) : null;69  const apiMode = v.deployment === 'api';70  const localMode = v.deployment === 'local' || v.memory !== null;7172  const inspector = (73    <div className="space-y-3 text-xs leading-relaxed text-ink-2">74      <p className="font-medium text-ink">Rules (deterministic)</p>75      {rulesObj ? (76        <dl className="space-y-1.5">77          {Object.entries(rulesObj).map(([k, r]) => (78            <div key={k}>79              <dt className="mono text-[11px] text-ink-3">{k}</dt>80              <dd>{r}</dd>81            </div>82          ))}83        </dl>84      ) : (85        <p className="text-ink-3">Rules unavailable from the API.</p>86      )}87      <p>88        <Link href="/methodology" className="link">/methodology</Link> · <Link href="/developers" className="link">GET /find-a-model</Link>89      </p>90    </div>91  );9293  return (94    <TerminalLayout filters={<FinderForm v={v} />} inspector={inspector} filtersTitle="What do you need?" inspectorTitle="Rules" storageKey="aia-inspector-finder" filterCount={n}>95      <PageHeader eyebrow="Model finder" title="Find a model" lede="Describe the need; the atlas applies deterministic rules to observed attributes, current prices and current benchmark results and returns the models that satisfy them — sorted by how many criteria they meet, then best benchmark rank, then release date. No composite score, no single winner." className="pt-4 md:pt-6" />96      <div className="pb-16">97        {n === 0 ? (98          <EmptyState title="Pick at least one criterion">99            Examples: <Link href="/find-a-model?use_case=coding&deployment=local&memory_gb=64" className="link">coding · local · 64 GB</Link>, <Link href="/find-a-model?use_case=vision&deployment=api&max_output_price=5" className="link">vision · API · output ≤ $5</Link>, <Link href="/find-a-model?use_case=long_context&context_min=1000000&license=commercial" className="link">1M context · commercial licence</Link>.100          </EmptyState>101        ) : !res ? (102          <Unavailable what="Model finder" reason={error ?? undefined} />103        ) : (104          <>105            <div className="border-y border-rule py-3">106              <p className="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm">107                <span className="font-semibold text-ink">108                  {fmtInt(res.total)} model{res.total === 1 ? '' : 's'} satisfy {res.matches[0]?.why.length ? 'at least one of' : ''} the criteria109                </span>110                <span className="text-ink-3">No single winner — these are the observed dimensions; the order is criteria met → best rank → release date.</span>111              </p>112              <p className="mt-1.5 flex flex-wrap gap-1.5">113                {Object.entries(res.filters_applied).map(([k, val]) => (114                  <Chip key={k}>115                    <span className="text-ink-3">{k.replace(/_/g, ' ')}</span> {String(val)}116                  </Chip>117                ))}118              </p>119            </div>120            {res.matches.length === 0 ? (121              <EmptyState title="No model satisfies these criteria" className="mt-4">122                Relax a bound (price, memory, context) or drop a modality. Every rule is listed in the Rules pane.123              </EmptyState>124            ) : (125              <DataTable scroll compact className="mt-4">126                <thead>127                  <tr>128                    <Th>Model</Th>129                    <Th>Why</Th>130                    <Th>Observed dimensions</Th>131                    {localMode && (132                      <Th>133                        Fit <Estimated className="ml-1 align-middle" />134                      </Th>135                    )}136                    {apiMode && <Th>Deployments</Th>}137                    <Th>Ranks</Th>138                    <Th className="w-40" aria-label="Actions" />139                  </tr>140                </thead>141                <tbody>142                  {res.matches.length === 0 && <EmptyRow cols={7} />}143                  {res.matches.map((m) => {144                    const o = m.observed ?? {};145                    const mods = Array.isArray(o.modalities) ? (o.modalities as string[]) : [];146                    return (147                      <tr key={m.model.id} data-finder-row>148                        <Td primary>149                          <span className="flex flex-wrap items-center gap-x-2 gap-y-0.5">150                            <EntityLink e={m.model} />151                            {typeof o.openness === 'string' && <OpennessBadge openness={o.openness} />}152                          </span>153                          {m.model.organization && <span className="block text-xs text-ink-3">{m.model.organization.name}</span>}154                        </Td>155                        <Td label="Why" wide>156                          <ul className="flex flex-wrap gap-1">157                            {m.why.map((w) => (158                              <li key={w} className={`rounded-[3px] px-1.5 py-[1px] text-[11px] leading-4 ${/estimate/i.test(w) ? 'border border-dashed border-warning/60 text-warning' : 'bg-positive-soft text-positive'}`}>159                                {w}160                              </li>161                            ))}162                            {m.why.length === 0 && <li className="text-xs text-ink-3">—</li>}163                          </ul>164                        </Td>165                        <Td label="Observed" className="tnum text-xs text-ink-2" wide>166                          {[167                            num(o.parameter_count) !== null ? `${fmtParams(o.parameter_count)} params` : null,168                            num(o.context_length) !== null ? `${fmtTokens(o.context_length)} context` : null,169                            typeof o.license === 'string' ? o.license : null,170                            mods.length ? mods.join(' · ') : null,171                            o.reasoning === true ? 'reasoning' : null,172                            o.tool_calling === true ? 'tool calling' : null,173                            typeof o.release_date === 'string' ? `released ${fmtDate(o.release_date)}` : null,174                            num(o.cheapest_input_per_mtok) !== null ? `from ${fmtUsdPerM(o.cheapest_input_per_mtok)} in / ${fmtUsdPerM(o.cheapest_output_per_mtok)} out` : null,175                            num(o.providers) !== null ? `${fmtInt(o.providers)} provider${num(o.providers) === 1 ? '' : 's'}` : null,176                          ]177                            .filter(Boolean)178                            .join(' · ') || '—'}179                        </Td>180                        {localMode && (181                          <Td label="Fit (estimated)">182                            <FitCell fit={m.estimated_fit} />183                          </Td>184                        )}185                        {apiMode && (186                          <Td label="Deployments" className="text-xs">187                            {m.deployments && m.deployments.length ? (188                              <ul className="space-y-0.5">189                                {m.deployments.slice(0, 4).map((d) => (190                                  <li key={d.id} className="tnum flex flex-wrap gap-x-2">191                                    <EntityLink e={d.provider} className="text-ink-2" />192                                    <span className="text-accent-2">193                                      {fmtUsdPerM(d.prices.input)} / {fmtUsdPerM(d.prices.output)}194                                    </span>195                                  </li>196                                ))}197                                {m.deployments.length > 4 && <li className="text-ink-3">+{m.deployments.length - 4} more</li>}198                              </ul>199                            ) : (200                              <span className="text-ink-3">—</span>201                            )}202                          </Td>203                        )}204                        <Td label="Ranks">205                          <RankChips ranks={o.benchmark_ranks ?? null} max={3} />206                        </Td>207                        <Td className="text-right">208                          <span className="inline-flex flex-wrap justify-end gap-1">209                            <CompareButton e={m.model} size="sm" />210                            <WatchButton e={m.model} size="sm" />211                          </span>212                        </Td>213                      </tr>214                    );215                  })}216                </tbody>217              </DataTable>218            )}219            <Note className="mt-3">Green chips = criteria satisfied by an observed fact; dashed amber = satisfied by an estimate (hardware fit). Ranks are positions inside each benchmark&apos;s primary comparability group.</Note>220            <Methodology text={res.note} />221          </>222        )}223      </div>224      <CompareTrayBar />225    </TerminalLayout>226  );227}228