SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
7.9 KB · 151 lines tsx
Raw Blame History
1import type { Metadata } from "next";2import Link from "next/link";3import { ArrowRight } from "lucide-react";4import { Container, Eyebrow } from "@/components/marketing/section";5import { Button } from "@/components/ui/button";6import { ProviderIcon } from "@/components/brand/provider-icon";7import { PublicCatalogGroup, type CatalogRow } from "@/components/models/public-catalog";8import { PROVIDERS, PROVIDER_ORDER } from "@/lib/client/providers";9import { getPublicModels, popularComparisons } from "@/lib/models/public-data";10import { deriveBadges, lifecycleStatus, sortWeightOf } from "@/lib/models/badges";11import { formatContext, formatPrice } from "@/lib/models/format";12import type { PolyModel } from "@/lib/ai/core/types";13import type { BadgeContext } from "@/lib/models/badges";1415export const dynamic = "force-dynamic";1617export const metadata: Metadata = {18  title: "AI model catalog — pricing, context windows and capabilities",19  description: "Every model PolyLLM supports across OpenAI, Anthropic, Google Gemini, xAI, Mistral, DeepSeek, Kimi, OpenRouter and Cerebras: input/output prices per million tokens, context windows, reasoning, vision, tools and lifecycle status — updated from the providers’ own listings.",20  alternates: { canonical: "/models" },21  openGraph: { title: "AI model catalog — PolyLLM", description: "Prices, context windows and capabilities for every supported model.", url: "/models" },22};2324function toRow(m: PolyModel, ctx: BadgeContext): CatalogRow {25  return {26    key: m.key,27    provider: m.provider,28    name: m.displayName,29    id: m.id,30    inPrice: formatPrice(m.pricing?.inputPerMillion),31    outPrice: formatPrice(m.pricing?.outputPerMillion),32    context: formatContext(m.limits?.contextTokens),33    maxOut: formatContext(m.limits?.maxOutputTokens),34    reasoning: m.capabilities.reasoning,35    vision: m.capabilities.vision,36    tools: m.capabilities.tools,37    json: m.capabilities.structuredOutput,38    lifecycle: lifecycleStatus(m, { ctx }),39    badges: deriveBadges(m, ctx),40  };41}4243export default async function PublicModelsPage() {44  const { models, ctx, ok } = await getPublicModels();45  const visible = models.filter((m) => m.status !== "deprecated");46  const groups = PROVIDER_ORDER.map((p) => ({ provider: p, list: visible.filter((m) => m.provider === p).sort((a, b) => sortWeightOf(b) - sortWeightOf(a) || a.displayName.localeCompare(b.displayName)) })).filter((g) => g.list.length);47  const popular = popularComparisons(models);4849  return (50    <>51      <section className="border-b border-border bg-bg-subtle/60 py-12 sm:py-16">52        <Container>53          <Eyebrow>Model catalog</Eyebrow>54          <h1 className="mt-3 max-w-3xl text-balance text-3xl font-semibold leading-[1.08] tracking-tight sm:text-5xl">Every model. One honest table.</h1>55          <p className="mt-4 max-w-2xl text-pretty text-[15px] leading-7 text-fg-muted sm:text-base">56            {visible.length ? `${visible.length} models from ${groups.length} providers` : "Models from nine providers"} — prices per million tokens, context windows, reasoning, vision, tools and lifecycle status, merged from each provider’s live listing and documented catalogs. Bring your own keys and use any of them in one workspace.57          </p>58          <div className="mt-6 flex flex-col gap-2 sm:flex-row">59            <Button asChild size="lg" className="w-full sm:w-auto">60              <Link href="/signup">61                Start using PolyLLM <ArrowRight />62              </Link>63            </Button>64            {popular[0] ? (65              <Button asChild size="lg" variant="ghost" className="w-full sm:w-auto">66                <Link href={`/compare/${popular[0].slug}`}>67                  Compare {popular[0].a.displayName} vs {popular[0].b.displayName}68                </Link>69              </Button>70            ) : null}71          </div>72        </Container>73      </section>7475      <Container className="py-10 sm:py-14">76        {!ok || visible.length === 0 ? (77          <div className="rounded-xl border border-dashed border-border px-6 py-12 text-center">78            <p className="text-sm font-medium">{ok ? "The catalog is being populated." : "The catalog is temporarily unavailable."}</p>79            <p className="mt-1 text-[13px] text-fg-muted">Please check back in a moment.</p>80          </div>81        ) : (82          <div className="space-y-12">83            {groups.map((g) => (84              <section key={g.provider} id={g.provider} aria-labelledby={`h-${g.provider}`} className="scroll-mt-20">85                <div className="flex items-center gap-3">86                  <span className="flex size-9 items-center justify-center rounded-lg bg-bg-subtle">87                    <ProviderIcon provider={g.provider} size={18} />88                  </span>89                  <div>90                    <h2 id={`h-${g.provider}`} className="text-lg font-semibold tracking-tight">91                      {PROVIDERS[g.provider].name}92                    </h2>93                    <p className="text-[12.5px] text-fg-muted">94                      {g.list.length} model{g.list.length === 1 ? "" : "s"} · {PROVIDERS[g.provider].description}95                    </p>96                  </div>97                </div>9899                <PublicCatalogGroup provider={g.provider} rows={g.list.map((m) => toRow(m, ctx))} />100              </section>101            ))}102          </div>103        )}104105        {popular.length ? (106          <section className="mt-16">107            <Eyebrow>Popular comparisons</Eyebrow>108            <h2 className="mt-3 text-2xl font-semibold tracking-tight">Head to head</h2>109            <ul className="mt-5 grid gap-2 sm:grid-cols-2 lg:grid-cols-3">110              {popular.map((c) => (111                <li key={c.slug}>112                  <Link href={`/compare/${c.slug}`} className="panel flex items-center gap-3 p-3 transition-colors hover:bg-bg-muted">113                    <span className="flex -space-x-1">114                      <span className="flex size-8 items-center justify-center rounded-full bg-bg-elevated ring-2 ring-bg">115                        <ProviderIcon provider={c.a.provider} size={14} />116                      </span>117                      <span className="flex size-8 items-center justify-center rounded-full bg-bg-elevated ring-2 ring-bg">118                        <ProviderIcon provider={c.b.provider} size={14} />119                      </span>120                    </span>121                    <span className="min-w-0 flex-1 truncate text-[13.5px] font-medium">122                      {c.a.displayName} <span className="text-fg-subtle">vs</span> {c.b.displayName}123                    </span>124                    <ArrowRight className="size-4 shrink-0 text-fg-subtle" />125                  </Link>126                </li>127              ))}128            </ul>129          </section>130        ) : null}131132        <section className="mt-16 rounded-2xl border border-border bg-bg-subtle/60 p-6 text-center sm:p-10">133          <h2 className="text-balance text-2xl font-semibold tracking-tight sm:text-3xl">Use any of them with your own keys.</h2>134          <p className="mx-auto mt-3 max-w-md text-[15px] leading-7 text-fg-muted">Free interface, encrypted keys, real streaming, side-by-side Arena and usage analytics.</p>135          <div className="mt-6 flex flex-col justify-center gap-2 sm:flex-row">136            <Button asChild size="lg" className="w-full sm:w-auto">137              <Link href="/signup">138                Create a free account <ArrowRight />139              </Link>140            </Button>141            <Button asChild size="lg" variant="ghost" className="w-full sm:w-auto">142              <Link href="/login">Sign in</Link>143            </Button>144          </div>145          <p className="mt-6 text-[11px] leading-5 text-fg-subtle">Prices are the providers’ published list prices in USD per million tokens at the last catalog audit; verify with the provider before relying on them. Model names are trademarks of their owners.</p>146        </section>147      </Container>148    </>149  );150}151