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%
7.2 KB · 142 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { GraphWorkbench } from '@/components/graph/graph-workbench';4import { defaultModeFor, GRAPH_MODES, graphHref, isGraphMode } from '@/components/graph/modes';5import { NodeSearch } from '@/components/graph/node-search-link';6import { BreadcrumbLd } from '@/components/meta/breadcrumb-ld';7import { EntityBadge } from '@/components/ui/badges';8import { Container, Note, PageHeader } from '@/components/ui/section';9import { EmptyState, Unavailable } from '@/components/ui/unavailable';10import { api, apiD3, safe } from '@/lib/api';11import { fmtInt } from '@/lib/format';12import { routes, SITE_NAME, SITE_URL, typeLabel } from '@/lib/site';13import type { GraphExploreMode } from '@/lib/types';1415type SP = { node?: string; mode?: string; depth?: string };16export const revalidate = 600;1718function resolve(sp: SP): { node: string | null; mode: GraphExploreMode | null; depth: 1 | 2 } {19  return { node: sp.node?.trim() || null, mode: isGraphMode(sp.mode) ? sp.mode : null, depth: sp.depth === '2' ? 2 : 1 };20}2122/** Default root when none is asked: the organization with the most canonical models (company ecosystem — the richest landing view), else the most viewed model this week. Never hardcoded. */23async function defaultRoot(): Promise<string | null> {24  const o = await safe(api.companies({ limit: 1, sort: 'models' }));25  if (o?.items?.[0]?.slug) return o.items[0].slug;26  const t = await safe(apiD3.trending('views', { type: 'model', limit: 1, days: 7 }));27  if (t?.items?.[0]?.slug) return t.items[0].slug;28  const m = await safe(api.models({ limit: 1, sort: 'quality' }));29  return m?.items?.[0]?.slug ?? null;30}3132export async function generateMetadata({ searchParams }: { searchParams: Promise<SP> }): Promise<Metadata> {33  const { node, mode, depth } = resolve(await searchParams);34  const d = node ? await safe(api.entity(node)) : null;35  const m = mode ?? defaultModeFor(d?.entity_type);36  const modeLabel = GRAPH_MODES.find((x) => x.mode === m)?.label ?? 'Graph';37  const title = d ? `${d.name} — ${modeLabel.toLowerCase()} graph` : 'Knowledge graph — every entity, every relation';38  const description = d ? `${modeLabel} around ${d.name} (${typeLabel(d.entity_type).toLowerCase()}) on ${SITE_NAME}: ${depth}-hop neighbourhood with the predicate of every stated relation.` : 'Explore the AI ecosystem as a graph: model lineage, research networks, company ecosystems, benchmark, dataset, provider and hardware graphs — every edge is a stated relation with a source.';39  const canonical = d ? graphHref(d.slug, m, depth) : '/graph';40  const og = `${SITE_URL}/graph/og${d ? `?node=${encodeURIComponent(d.slug)}&mode=${m}&depth=${depth}` : ''}`;41  return { title, description, alternates: { canonical }, openGraph: { title: `${title} | ${SITE_NAME}`, description, url: `${SITE_URL}${canonical}`, images: [{ url: og, width: 1200, height: 630 }] }, twitter: { card: 'summary_large_image', images: [og] }, robots: node && !d ? { index: false } : undefined };42}4344export default async function GraphIndexPage({ searchParams }: { searchParams: Promise<SP> }) {45  const sp = await searchParams;46  const { node: asked, mode: askedMode, depth } = resolve(sp);47  const node = asked ?? (await defaultRoot());48  const d = node ? await safe(api.entity(node)) : null;49  const mode = askedMode ?? defaultModeFor(d?.entity_type);50  const graph = d ? await safe(apiD3.graphExplore(d.slug, mode, depth, 150)) : null;51  const modeDef = GRAPH_MODES.find((x) => x.mode === mode)!;5253  return (54    <>55      <BreadcrumbLd items={[{ name: SITE_NAME, href: '/' }, { name: 'Knowledge graph', href: '/graph' }, ...(d ? [{ name: d.name, href: graphHref(d.slug, mode, depth) }] : [])]} />56      <Container wide>57        <PageHeader58          eyebrow={59            <>60              Knowledge graph {d && <EntityBadge type={d.entity_type} small />}61            </>62          }63          title={64            d ? (65              <>66                {modeDef.label} around{' '}67                <Link href={routes.entity(d)} className="hover:text-accent">68                  {d.name}69                </Link>70              </>71            ) : (72              'Every entity, every relation'73            )74          }75          lede={d ? `${modeDef.hint[0]?.toUpperCase()}${modeDef.hint.slice(1)}. Nodes are coloured by type; every edge is a relation stated by a source. Click a node for its facts, double-click to expand its neighbourhood, pick another root or mode in the rail.` : 'Pick a root entity and a mode to draw its neighbourhood.'}76          aside={77            graph ? (78              <p className="tnum text-xs text-ink-3">79                {fmtInt(graph.counts?.nodes ?? graph.nodes.length)} nodes · {fmtInt(graph.counts?.edges ?? graph.edges.length)} edges · depth {graph.depth}80                {graph.truncated ? <span className="text-warning"> · truncated</span> : null}81              </p>82            ) : undefined83          }84          className="pb-3 md:pb-4"85        />86      </Container>87      {!node ? (88        <Container>89          <div className="pb-16">90            <Unavailable what="Graph" reason="No root could be chosen (the API did not answer)." />91          </div>92        </Container>93      ) : !d ? (94        <Container>95          <div className="pb-16">96            <EmptyState title={`No entity called “${node}”`}>97              Pick a root from the atlas:98              <div className="mx-auto mt-3 max-w-md text-left">99                <NodeSearch mode={mode} depth={depth} />100              </div>101            </EmptyState>102          </div>103        </Container>104      ) : !graph ? (105        <Container>106          <div className="pb-16">107            <Unavailable what="Graph" reason="The graph service did not answer." />108          </div>109        </Container>110      ) : graph.nodes.length <= 1 ? (111        <Container>112          <div className="pb-16">113            <EmptyState title={`No ${modeDef.label.toLowerCase()} relations recorded for ${d.name}`}>114              Relations are written only when a source states them. Try another mode:{' '}115              {GRAPH_MODES.filter((m) => m.mode !== mode)116                .slice(0, 4)117                .map((m, i) => (118                  <span key={m.mode}>119                    {i > 0 && ' · '}120                    <Link href={graphHref(d.slug, m.mode, depth)} className="link">121                      {m.label}122                    </Link>123                  </span>124                ))}125              , or <Link href={routes.entity(d)} className="link">open {d.name}</Link>.126            </EmptyState>127          </div>128        </Container>129      ) : (130        <div className="pb-12">131          <GraphWorkbench key={`${d.slug}:${mode}:${depth}`} initial={graph} root={{ slug: d.slug, name: d.name, entity_type: d.entity_type }} mode={mode} depth={depth} />132          <Container wide>133            <Note className="mt-4">134              Modes map to <span className="mono">/graph/explore?mode=</span>: {GRAPH_MODES.map((m) => m.mode).join(' · ')}. The API never returns more than 150 nodes and says so (<span className="mono">truncated</span>). Nothing is inferred: an edge exists only when a source stated the relation. <Link href={routes.methodology()} className="link">Methodology →</Link>135            </Note>136          </Container>137        </div>138      )}139    </>140  );141}142