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%
8.7 KB · 148 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import { ScrollX } from '@/components/models/scroll-x';3import Link from 'next/link';4import { Tri } from '@/components/models/badges';5import { LICENCE_DIMENSIONS } from '@/components/models/openness-block';6import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';7import { Hint } from '@/components/ui/hint';8import { Container, Note, PageHeader } from '@/components/ui/section';9import { Unavailable } from '@/components/ui/unavailable';10import { apiD1, safe } from '@/lib/api';11import { cn } from '@/lib/cn';12import { fmtInt, num } from '@/lib/format';13import { SITE_NAME, SITE_URL } from '@/lib/site';1415export const metadata: Metadata = {16  title: 'Model licences — commercial use, redistribution, derivatives, hosting restrictions',17  description: 'Canonical licences of AI models (Apache-2.0, MIT, Llama Community, Gemma Terms, RAIL…): category, commercial use, redistribution, derivatives, hosting restrictions, acceptable-use policy and the number of canonical models under each — permissions read from the licence text, unknown when ambiguous.',18  alternates: { canonical: '/licenses' },19  openGraph: { title: `Model licences | ${SITE_NAME}`, url: `${SITE_URL}/licenses`, type: 'website' },20};21export const revalidate = 3600;2223const CATEGORY_HINT: Record<string, string> = {24  permissive: 'OSI-style licences with no field-of-use restriction (Apache, MIT, BSD).',25  copyleft: 'Derivatives must be shared under the same terms (GPL, AGPL, LGPL, MPL).',26  'creative-commons': 'Creative Commons terms; NC variants forbid commercial use, ND forbids derivatives.',27  'responsible-ai': 'RAIL / OpenRAIL licences: permissive with an attached acceptable-use policy.',28  community: 'Vendor community licences (Llama, Gemma, Qwen, NVIDIA…): downloadable weights with user caps, hosting or field-of-use clauses.',29  'research-only': 'Non-commercial / research-only terms.',30  proprietary: 'No weights redistribution; access through an API or product.',31  unknown: 'Stated licence text could not be classified.',32};3334export default async function LicensesPage({ searchParams }: { searchParams: Promise<{ category?: string }> }) {35  const { category } = await searchParams;36  const res = await safe(apiD1.licenses());37  const all = (res?.items ?? []).slice().sort((a, b) => (num(b.models) ?? 0) - (num(a.models) ?? 0) || a.label.localeCompare(b.label));38  const items = category ? all.filter((l) => l.category === category) : all;39  const cats = (res?.categories ?? []).map((c) => ({ c, n: all.filter((l) => l.category === c).length, models: all.filter((l) => l.category === c).reduce((s, l) => s + (num(l.models) ?? 0), 0) })).filter((x) => x.n > 0);40  const chip = (active: boolean) => cn('inline-flex h-8 items-center gap-1.5 border px-2.5 text-xs whitespace-nowrap', active ? 'border-ink bg-ink text-canvas' : 'border-rule text-ink-2 hover:border-rule-strong hover:text-ink');41  const ld = { '@context': 'https://schema.org', '@type': 'CollectionPage', name: 'Model licences', url: `${SITE_URL}/licenses`, description: metadata.description, mainEntity: { '@type': 'ItemList', numberOfItems: all.length, itemListElement: all.slice(0, 12).map((l, i) => ({ '@type': 'ListItem', position: i + 1, name: l.label, url: `${SITE_URL}/licenses/${encodeURIComponent(l.key)}` })) } };4243  return (44    <Container wide>45      <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />46      <PageHeader eyebrow="Licences" title="Model licences" lede="The canonical licence ontology behind the openness classification. Permissions are read from each licence text; a dash means the text is ambiguous — never assumed." aside={res ? <p className="tnum text-sm text-ink-3">{fmtInt(items.length)}{category ? ` of ${fmtInt(all.length)}` : ''} licences</p> : undefined}>47        {cats.length > 0 && (48          <nav aria-label="Category" className="no-scrollbar -mx-4 mt-6 flex gap-1.5 overflow-x-auto px-4 md:mx-0 md:flex-wrap md:px-0">49            <Link href="/licenses" className={chip(!category)} aria-current={!category ? 'true' : undefined}>50              All <span className="tnum opacity-70">{fmtInt(all.length)}</span>51            </Link>52            {cats.map(({ c, n, models }) => (53              <Link key={c} href={`/licenses?category=${encodeURIComponent(c)}`} className={chip(category === c)} aria-current={category === c ? 'true' : undefined} title={`${CATEGORY_HINT[c] ?? c} · ${fmtInt(models)} models`}>54                {c} <span className="tnum opacity-70">{fmtInt(n)}</span>55              </Link>56            ))}57          </nav>58        )}59      </PageHeader>60      <div className="pb-16">61        {!res ? (62          <Unavailable what="Licences" />63        ) : (64          <>65            {category && CATEGORY_HINT[category] && <p className="mb-3 text-sm text-ink-2">{CATEGORY_HINT[category]}</p>}66            <ScrollX><DataTable caption="Licences" compact>67              <thead>68                <tr>69                  <Th>Licence</Th>70                  <Th>Category</Th>71                  {LICENCE_DIMENSIONS.slice(0, 5).map((d) => (72                    <Th key={d.key} title={d.hint} className="cursor-help underline decoration-dotted decoration-rule-strong underline-offset-2">73                      {d.label}74                    </Th>75                  ))}76                  <Th num>Models</Th>77                </tr>78              </thead>79              <tbody>80                {items.length === 0 && <EmptyRow cols={8}>No licence in this category.</EmptyRow>}81                {items.map((l) => (82                  <tr key={l.key}>83                    <Td primary>84                      <Link href={`/licenses/${encodeURIComponent(l.key)}`} className="text-ink hover:text-accent hover:underline">85                        {l.label}86                      </Link>87                      <span className="mono block text-[11px] text-ink-3">88                        {l.key}89                        {l.spdx && l.spdx !== l.key ? ` · SPDX ${l.spdx}` : ''}90                        {l.osi_approved ? ' · OSI' : ''}91                      </span>92                    </Td>93                    <Td label="Category" className="text-ink-2">94                      <Link href={`/licenses?category=${encodeURIComponent(l.category)}`} className="hover:text-accent">95                        {l.category}96                      </Link>97                    </Td>98                    {LICENCE_DIMENSIONS.slice(0, 5).map((d) => (99                      <Td key={d.key} label={d.label}>100                        <Tri v={l[d.key] as boolean | null} yes={d.invert ? 'Yes — restricted' : 'Yes'} no={d.invert ? 'None' : 'No'} />101                      </Td>102                    ))}103                    <Td num label="Models" className="tnum">104                      {num(l.models) ? (105                        <Link href={`/models?license=${encodeURIComponent(l.key)}`} className="hover:text-accent">106                          {fmtInt(l.models)}107                        </Link>108                      ) : (109                        <span className="text-ink-3">0</span>110                      )}111                    </Td>112                  </tr>113                ))}114              </tbody>115            </DataTable></ScrollX>116            {res.unclassified?.length > 0 && (117              <section className="mt-8">118                <p className="eyebrow mb-2">119                  Unclassified licence labels <span className="tnum text-ink-3">{fmtInt(res.unclassified.length)}</span>120                </p>121                <ul className="flex flex-wrap gap-1.5 text-xs">122                  {res.unclassified.map((u) => (123                    <li key={u.raw} className="border border-dashed border-rule-strong px-2 py-1 text-ink-2">124                      “{u.raw}” <span className="tnum text-ink-3">{fmtInt(u.models)}</span>125                    </li>126                  ))}127                </ul>128              </section>129            )}130            <Note className="mt-3">{res.note ?? 'Permissions are read from the licence text (null = the text is ambiguous). Counts cover canonical models only.'} Hosting restrictions / acceptable-use: “Yes — restricted” means the clause exists.</Note>131            <dl className="mt-3 grid gap-x-6 gap-y-1 text-[11px] text-ink-3 sm:grid-cols-2 lg:grid-cols-3" data-licence-definitions>132              {LICENCE_DIMENSIONS.map((d) => (133                <div key={d.key} className="flex gap-1.5">134                  <dt className="shrink-0 font-medium text-ink-2">{d.label}</dt>135                  <dd className="flex items-center gap-1">136                    {d.hint}137                    <Hint text={d.hint} align="right" className="hidden" />138                  </dd>139                </div>140              ))}141            </dl>142          </>143        )}144      </div>145    </Container>146  );147}148