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%
21.1 KB · 356 lines tsx
Raw Blame History
1import { ExternalLink, GitFork } from 'lucide-react';2import Link from 'next/link';3import { ChangeRow } from '@/components/changes/change-row';4import { CompareButton } from '@/components/compare/compare-button';5import { Identity, SourcesTable } from '@/components/entity/blocks';6import { DataStrip, SectionNav } from '@/components/layout/terminal';7import { ViewBeacon } from '@/components/layout/view-beacon';8import { BreadcrumbLd, Breadcrumbs } from '@/components/meta/breadcrumb-ld';9import { WatchButton } from '@/components/watchlist/watch-button';10import { Chip, EntityBadge, OpennessBadge, StatusBadge } from '@/components/ui/badges';11import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';12import { EntityLink, QualityMark } from '@/components/ui/entity';13import { Hint } from '@/components/ui/hint';14import { KeyValue } from '@/components/ui/key-value';15import { Container, Note, Section } from '@/components/ui/section';16import { EmptyState } from '@/components/ui/unavailable';17import { api, apiD1, apiD3, safe } from '@/lib/api';18import { fmtAgo, fmtDate, fmtInt, fmtParams, fmtTokens, num } from '@/lib/format';19import { PROSE_KEYS, routes, SITE_NAME, SITE_URL, typeLabel } from '@/lib/site';20import type { ChangeEvent, EntityDetail, EntitySummary, FamilyRow } from '@/lib/types';2122/*23  Organization page 3.0 (server, async): footprint strip → models grouped by family → families → papers → researchers →24  providers operated → repositories/frameworks → timeline split CORPORATE NEWS vs MODEL EVENTS → sources.25  Counts come from the listing APIs (`/models?org=`, `/families?org=`, `/papers?org=`, `/explore/researcher?org=`) and the26  detail relations; what the API does not expose per organization (datasets, benchmarks) is shown as "—" with a definition.27*/2829const ORG_TYPES = ['company', 'organization', 'lab', 'university'];30const CORPORATE = new Set(['ANNOUNCEMENT', 'NEW_COMPANY', 'NEW_ORGANIZATION', 'NEW_LAB', 'NEW_UNIVERSITY', 'PROPERTY_CHANGED']);3132function relItems(d: EntityDetail, types: string[], predicates?: string[]): EntitySummary[] {33  const out: EntitySummary[] = [];34  const seen = new Set<string>();35  for (const g of d.relations ?? []) {36    if (predicates && !predicates.includes(g.predicate)) continue;37    for (const it of g.items) {38      if (!types.includes(it.entity_type) || seen.has(it.id)) continue;39      seen.add(it.id);40      out.push(it);41    }42  }43  return out;44}4546type ModelRow = EntitySummary & { family?: { id: string; slug: string; name: string } | null };4748function ModelsByFamily({ items, total, org }: { items: ModelRow[]; total: number; org: string }) {49  if (!items.length) return <EmptyState title="No canonical model attributed to this organization">Models are attributed through a stated developer relation or an official model id.</EmptyState>;50  const groups = new Map<string, { label: string; href: string | null; items: ModelRow[] }>();51  for (const m of items) {52    const fam = m.family ?? null;53    const label = fam?.name ?? (typeof m.attributes?.family === 'string' ? (m.attributes.family as string) : 'No family');54    const key = fam?.slug ?? `label:${label}`;55    (groups.get(key) ?? groups.set(key, { label, href: fam ? routes.family(fam.slug) : null, items: [] }).get(key)!).items.push(m);56  }57  const ordered = [...groups.values()].sort((a, b) => b.items.length - a.items.length || a.label.localeCompare(b.label));58  return (59    <>60      <DataTable caption="Models by family" compact>61        <thead>62          <tr>63            <Th>Model</Th>64            <Th num>Params</Th>65            <Th num>Context</Th>66            <Th>Openness</Th>67            <Th>Status</Th>68            <Th>Released</Th>69            <Th num>Quality</Th>70          </tr>71        </thead>72        <tbody>73          {ordered.map((g) => (74            <FamilyRows key={g.label} g={g} />75          ))}76        </tbody>77      </DataTable>78      {total > items.length && (79        <Note className="mt-2">80          Showing {fmtInt(items.length)} of {fmtInt(total)} — <Link href={`/models?org=${encodeURIComponent(org)}`} className="link">all models by this organization →</Link>81        </Note>82      )}83    </>84  );85}86function FamilyRows({ g }: { g: { label: string; href: string | null; items: ModelRow[] } }) {87  return (88    <>89      <tr className="bg-surface-2/60">90        <td colSpan={7} className="!py-1.5">91          <span className="eyebrow inline-flex items-center gap-2">92            {g.href ? (93              <Link href={g.href} className="hover:text-ink">94                {g.label}95              </Link>96            ) : (97              g.label98            )}99            <span className="tnum normal-case tracking-normal text-ink-3">{fmtInt(g.items.length)}</span>100          </span>101        </td>102      </tr>103      {g.items.map((m) => {104        const a = m.attributes ?? {};105        return (106          <tr key={m.id}>107            <Td primary>108              <EntityLink e={m} />109            </Td>110            <Td num label="Params" className="tnum">{fmtParams(a.parameter_count)}</Td>111            <Td num label="Context" className="tnum">{num(a.context_length) === null ? '—' : fmtTokens(a.context_length)}</Td>112            <Td label="Openness">{typeof a.openness === 'string' ? <OpennessBadge openness={a.openness} /> : <span className="text-ink-3">—</span>}</Td>113            <Td label="Status">114              <StatusBadge status={typeof a.status === 'string' ? a.status : m.status} />115            </Td>116            <Td label="Released" className="tnum text-ink-2">{typeof a.release_date === 'string' ? fmtDate(a.release_date) : '—'}</Td>117            <Td num label="Quality">118              <QualityMark q={m.quality?.score} />119            </Td>120          </tr>121        );122      })}123    </>124  );125}126127function FamiliesTable({ items }: { items: FamilyRow[] }) {128  if (!items.length) return <p className="text-sm text-ink-3">No family recorded for this organization.</p>;129  return (130    <DataTable caption="Families" compact>131      <thead>132        <tr>133          <Th>Family</Th>134          <Th num>Models</Th>135          <Th>First release</Th>136          <Th>Last release</Th>137          <Th>Modalities</Th>138          <Th>Canonical</Th>139        </tr>140      </thead>141      <tbody>142        {items.map((f) => (143          <tr key={f.slug}>144            <Td primary>145              {f.canonical ? (146                <Link href={routes.family(f.slug)} className="text-ink hover:text-accent hover:underline">147                  {f.name}148                </Link>149              ) : (150                <span>{f.name}</span>151              )}152            </Td>153            <Td num label="Models" className="tnum">{fmtInt(f.model_count)}</Td>154            <Td label="First release" className="tnum text-ink-2">{fmtDate(f.first_release)}</Td>155            <Td label="Last release" className="tnum text-ink-2">{fmtDate(f.last_release)}</Td>156            <Td label="Modalities" className="text-xs text-ink-2">{f.modalities?.length ? f.modalities.join(', ') : '—'}</Td>157            <Td label="Canonical">{f.canonical ? <Chip tone="accent">entity</Chip> : <Chip>legacy label</Chip>}</Td>158          </tr>159        ))}160      </tbody>161    </DataTable>162  );163}164165function List({ items, empty }: { items: EntitySummary[]; empty: React.ReactNode }) {166  if (!items.length) return <p className="text-sm text-ink-3">{empty}</p>;167  return (168    <ul className="divide-y divide-rule border-y border-rule">169      {items.map((e) => (170        <li key={e.id} className="flex items-center gap-2 py-2 text-sm">171          <EntityBadge type={e.entity_type} small />172          <EntityLink e={e} className="truncate" />173          {typeof e.attributes?.published_at === 'string' && <span className="ml-auto shrink-0 text-xs text-ink-3">{fmtDate(e.attributes.published_at as string)}</span>}174        </li>175      ))}176    </ul>177  );178}179180export async function OrganizationPage({ d, canonical }: { d: EntityDetail; canonical: string }) {181  const a = d.attributes ?? {};182  const [models, families, papers, researchers, news, tl] = await Promise.all([183    safe(api.models({ org: d.slug, limit: 200, sort: 'release' })),184    safe(apiD1.families({ org: d.slug, limit: 50, sort: 'models' })),185    safe(api.papers({ org: d.slug, limit: 30, sort: 'published' })),186    safe(api.explore('researcher', { org: d.slug, limit: 30 })),187    safe(apiD3.changes({ entity: d.slug, type: 'ANNOUNCEMENT', include_backfill: 1, limit: 30 })),188    safe(apiD3.entityTimeline(d.slug, { include_backfill: 1, limit: 100 })),189  ]);190  const modelItems = (models?.items ?? []) as ModelRow[];191  const modelTotal = num(models?.total) ?? num(d.models?.total) ?? modelItems.length;192  const famItems = families?.items ?? [];193  const paperItems = papers?.items ?? d.papers ?? [];194  const researcherItems = (researchers?.items ?? []).filter((r) => r.entity_type === 'researcher');195  const withIds = researcherItems.filter((r) => (r.counts?.claims ?? 0) > 0 || (r.organization && r.organization.slug === d.slug));196  const providers = relItems(d, ['provider'], ['operates', 'owns']);197  const repos = [...(d.repositories ?? []), ...relItems(d, ['repository', 'framework', 'library'])].filter((x, i, arr) => arr.findIndex((y) => y.id === x.id) === i);198  const datasets = relItems(d, ['dataset']);199  const benchmarks = relItems(d, ['benchmark']);200  const newsItems: ChangeEvent[] = news?.items ?? [];201  const modelEvents: ChangeEvent[] = (tl?.items ?? []).filter((e) => !CORPORATE.has(e.event_type) && e.event_type !== 'ENTITY_MERGED');202  const corporateFromTimeline: ChangeEvent[] = (tl?.items ?? []).filter((e) => CORPORATE.has(e.event_type) && !newsItems.some((n) => n.id === e.id));203  const corporate = [...newsItems, ...corporateFromTimeline].sort((x, y) => ((y.occurred_at ?? y.observed_at) < (x.occurred_at ?? x.observed_at) ? -1 : 1));204  const link = ['website', 'official_url'].map((k) => a[k]).find((v): v is string => typeof v === 'string' && /^https?:\/\//.test(v)) ?? null;205  const crumbs = [{ name: SITE_NAME, href: '/' }, { name: 'Organizations', href: '/companies' }, { name: d.name, href: canonical }];206  const ld = { '@context': 'https://schema.org', '@type': 'Organization', name: d.name, url: `${SITE_URL}${canonical}`, description: d.description ?? undefined, alternateName: d.aliases?.length ? d.aliases : undefined, foundingDate: a.founded ? String(a.founded) : undefined, sameAs: link ? [link] : undefined, location: typeof a.headquarters === 'string' ? a.headquarters : undefined };207  const specRows = Object.keys(a)208    .filter((k) => !PROSE_KEYS.has(k) && !['founders', 'leadership'].includes(k))209    .map((k) => ({ key: k, raw: a[k] }));210  const sections = [211    { id: 'models', label: 'Models' },212    { id: 'families', label: 'Families' },213    { id: 'papers', label: 'Papers' },214    { id: 'researchers', label: 'Researchers' },215    { id: 'providers', label: 'Providers' },216    { id: 'repositories', label: 'Code' },217    { id: 'news', label: 'Corporate news' },218    { id: 'model-events', label: 'Model events' },219    { id: 'sources', label: 'Sources' },220  ];221222  return (223    <Container wide>224      <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />225      <BreadcrumbLd items={crumbs} />226      <ViewBeacon path={canonical} />227      <Breadcrumbs items={crumbs} />228      <header className="pb-5 pt-4 md:pb-6 md:pt-5">229        <div className="flex flex-wrap items-center gap-2">230          <EntityBadge type={d.entity_type} />231          {typeof a.org_kind === 'string' && a.org_kind.toLowerCase() !== typeLabel(d.entity_type).toLowerCase() && <Chip>{a.org_kind}</Chip>}232          {typeof a.country === 'string' && <Chip className="mono">{a.country}</Chip>}233        </div>234        <div className="mt-3 flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">235          <div className="min-w-0">236            <h1 className="display text-[30px] md:text-[44px]">{d.name}</h1>237            <p className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[15px] text-ink-2">238              {typeof a.headquarters === 'string' && <span>{a.headquarters}</span>}239              {a.founded ? <span>· founded {String(a.founded).slice(0, 4)}</span> : null}240              {link && (241                <a href={link} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-ink-3 hover:text-accent">242                  {link.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, '').slice(0, 48)} <ExternalLink className="size-3.5" aria-hidden />243                </a>244              )}245            </p>246            {d.description && <p className="mt-3 max-w-3xl text-[15px] leading-relaxed text-ink-2">{d.description}</p>}247            <div className="mt-4 flex flex-wrap items-center gap-2" aria-label="Actions">248              <CompareButton e={d} />249              <WatchButton e={d} />250              <Link href={`/graph/${encodeURIComponent(d.slug)}?mode=company`} className="inline-flex h-9 items-center gap-1.5 border border-rule px-2.5 text-sm text-ink-2 hover:border-rule-strong hover:text-ink">251                <GitFork className="size-3.5" aria-hidden /> Company ecosystem graph252              </Link>253              <Link href={routes.timeline({ entity: d.slug })} className="inline-flex h-9 items-center gap-1.5 border border-rule px-2.5 text-sm text-ink-2 hover:border-rule-strong hover:text-ink">254                Timeline255              </Link>256              <Link href={routes.diff({ scope: `org:${d.slug}` })} className="inline-flex h-9 items-center gap-1.5 border border-rule px-2.5 text-sm text-ink-2 hover:border-rule-strong hover:text-ink">257                Diff (last 7 days)258              </Link>259            </div>260          </div>261          <div className="shrink-0 text-xs text-ink-3 lg:text-right">262            <QualityMark q={d.quality?.score} label />263            <p className="mt-1" title={d.updated_at}>264              Updated {fmtAgo(d.updated_at)} · first seen {fmtDate(d.first_seen_at)}265            </p>266            <p className="mono mt-0.5 text-[11px]">{d.id}</p>267          </div>268        </div>269      </header>270271      <DataStrip272        dense273        items={[274          { label: 'Models', value: fmtInt(modelTotal), definition: 'Canonical model releases attributed to this organization (artifacts and folded variants excluded).', href: '#models' },275          { label: 'Families', value: fmtInt(families?.total ?? famItems.length), definition: 'Model families whose official organization is this one.', href: '#families' },276          { label: 'Papers', value: fmtInt(papers?.total ?? paperItems.length), definition: 'Papers with this organization stated as publisher (arXiv metadata carries no affiliation, so this is often zero).', href: '#papers' },277          { label: 'Researchers', value: fmtInt(researchers?.total ?? researcherItems.length), definition: 'Researcher records affiliated with this organization by a stated relation.', href: '#researchers' },278          { label: 'Datasets', value: datasets.length ? fmtInt(datasets.length) : '—', definition: 'Datasets linked to this organization by a stated relation; the API has no per-organization dataset count.' },279          { label: 'Benchmarks', value: benchmarks.length ? fmtInt(benchmarks.length) : '—', definition: 'Benchmarks created by this organization (stated relation). Results by this organization’s models live on each leaderboard (?org=).' },280          { label: 'Providers', value: fmtInt(providers.length), definition: 'Inference providers operated or owned by this organization.', href: '#providers' },281          { label: 'Repositories', value: fmtInt(repos.length), definition: 'Repositories, frameworks and libraries linked by a stated relation.', href: '#repositories' },282          { label: 'Announcements', value: fmtInt(news?.total ?? corporate.length), definition: 'ANNOUNCEMENT events from this organization’s own news pages (including historical backfill).', href: '#news' },283        ]}284      />285      <SectionNav items={sections} className="mt-3" />286287      <div className="grid gap-10 pb-16 lg:grid-cols-[minmax(0,1fr)_22rem]">288        <div className="min-w-0">289          <Section id="models" eyebrow="Models" title={<>Models <span className="tnum text-base font-normal text-ink-3">{fmtInt(modelTotal)}</span></>} lede="Grouped by family (canonical family entity when known, legacy label otherwise)." action={{ href: `/models?org=${encodeURIComponent(d.slug)}`, label: 'Filter in Models' }}>290            <ModelsByFamily items={modelItems} total={modelTotal} org={d.slug} />291          </Section>292          <Section id="families" eyebrow="Families" title={<>Families <span className="tnum text-base font-normal text-ink-3">{fmtInt(families?.total ?? famItems.length)}</span></>}>293            <FamiliesTable items={famItems} />294          </Section>295          <Section id="papers" eyebrow="Research" title={<>Papers <span className="tnum text-base font-normal text-ink-3">{fmtInt(papers?.total ?? paperItems.length)}</span></>} action={{ href: `/papers?org=${encodeURIComponent(d.slug)}`, label: 'All' }}>296            <List items={paperItems} empty={<>No paper is attributed to {d.name}: arXiv metadata carries author names but no affiliation, and a paper is linked to an organization only when an official model card or lab page cites it.</>} />297          </Section>298          <Section id="researchers" eyebrow="People" title={<>Researchers <span className="tnum text-base font-normal text-ink-3">{fmtInt(withIds.length)}</span></>} lede="Only researcher records with an identifier or a stated affiliation are listed.">299            <List items={withIds} empty={<>No researcher is affiliated with {d.name} by a stated relation. The atlas's researcher rows come from arXiv author lists (name only, no identifier, no affiliation) and are not attributed to organizations.</>} />300            {researcherItems.length > withIds.length && <Note className="mt-2">{fmtInt(researcherItems.length - withIds.length)} name-only legacy rows omitted.</Note>}301          </Section>302          <Section id="providers" eyebrow="Serving" title={<>Providers operated <span className="tnum text-base font-normal text-ink-3">{fmtInt(providers.length)}</span></>}>303            <List items={providers} empty="No provider operated by this organization is recorded." />304          </Section>305          <Section id="repositories" eyebrow="Code" title={<>Repositories & frameworks <span className="tnum text-base font-normal text-ink-3">{fmtInt(repos.length)}</span></>}>306            <List items={repos} empty="No repository or framework linked." />307          </Section>308          <Section id="news" eyebrow="Timeline" title={<>Corporate news <span className="tnum text-base font-normal text-ink-3">{fmtInt(news?.total ?? corporate.length)}</span></>} lede="ANNOUNCEMENT events from the organization's own channels, newest first (dates are publication dates when stated).">309            {corporate.length === 0 ? (310              <EmptyState title="No announcement recorded">The news connector for this organization has not produced announcements yet.</EmptyState>311            ) : (312              <ul className="border-t border-rule">313                {corporate.slice(0, 30).map((e) => (314                  <ChangeRow key={e.id} e={e} showDate live={false} />315                ))}316              </ul>317            )}318          </Section>319          <Section id="model-events" eyebrow="Timeline" title={<>Model events <span className="tnum text-base font-normal text-ink-3">{fmtInt(modelEvents.length)}</span></>} lede="New models, releases, deprecations, price and property changes for the models this organization develops." action={{ href: routes.timeline({ entity: d.slug }), label: 'Full timeline' }}>320            {modelEvents.length === 0 ? (321              <EmptyState title="No model event recorded" />322            ) : (323              <ul className="border-t border-rule">324                {modelEvents.slice(0, 40).map((e) => (325                  <ChangeRow key={e.id} e={e} showDate live={false} />326                ))}327              </ul>328            )}329            {modelEvents.length > 40 && <Note className="mt-2">First 40 of {fmtInt(modelEvents.length)} loaded — the full timeline has cursor paging.</Note>}330          </Section>331          <Section id="sources" eyebrow="Sources" title={<>Sources <span className="tnum text-base font-normal text-ink-3">{fmtInt(d.sources?.length ?? 0)}</span></>}>332            <SourcesTable sources={d.sources ?? []} />333          </Section>334        </div>335        <aside className="min-w-0 space-y-8 lg:pt-8">336          <section>337            <p className="eyebrow mb-2 inline-flex items-center gap-1">338              Record <Hint align="right" text="Every value shows its source, tier and observation time; click a value for the claim behind it." />339            </p>340            <KeyValue rows={specRows} provenance={d.provenance} slug={d.slug} entity={{ name: d.name, entity_type: d.entity_type }} dense />341          </section>342          <section>343            <p className="eyebrow mb-2">Identity</p>344            <Identity d={d} />345            <p className="mono mt-2 break-all text-[11px] text-ink-3">slug {d.slug}</p>346          </section>347          <section>348            <p className="eyebrow mb-2">Definition</p>349            <p className="text-xs text-ink-3">Organizations = companies, labs and universities ({ORG_TYPES.join(', ')}). Counts on this page are live from the listing APIs and the stated relations of this record.</p>350          </section>351        </aside>352      </div>353    </Container>354  );355}356