HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1import { ExternalLink, FileText } from 'lucide-react';2import Link from 'next/link';3import { SourcesTable, TimelineList } from '@/components/entity/blocks';4import { ViewBeacon } from '@/components/layout/view-beacon';5import { BreadcrumbLd, Breadcrumbs } from '@/components/meta/breadcrumb-ld';6import { WatchButton } from '@/components/watchlist/watch-button';7import { Chip, EntityBadge } from '@/components/ui/badges';8import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';9import { EntityLink, QualityMark } from '@/components/ui/entity';10import { KeyValue } from '@/components/ui/key-value';11import { Container, Note, Section } from '@/components/ui/section';12import { EmptyState } from '@/components/ui/unavailable';13import { api, safe } from '@/lib/api';14import { fmtAgo, fmtDate, fmtInt, fmtParams, fmtTokens, num } from '@/lib/format';15import { routes, SITE_NAME, SITE_URL, typeLabel } from '@/lib/site';16import type { EntityDetail, EntitySummary } from '@/lib/types';1718/*19 Paper page (server, async). Sections: header (title, published, venue, arXiv, PDF/code) → abstract → authors (researcher20 pages when the `authored` relation names them) → organizations → models introduced (`described_by` inbound) → datasets →21 benchmarks → repositories → related papers (shared entities) → sources → timeline. Everything comes from the detail22 payload's relations; nothing is inferred beyond grouping by entity type.23*/2425function relItems(d: EntityDetail, types: string[], predicate?: string): EntitySummary[] {26 const out: EntitySummary[] = [];27 const seen = new Set<string>();28 for (const g of d.relations ?? []) {29 if (predicate && g.predicate !== predicate) continue;30 for (const it of g.items) {31 if (!types.includes(it.entity_type) || seen.has(it.id)) continue;32 seen.add(it.id);33 out.push(it);34 }35 }36 return out;37}3839function ModelsIntroduced({ items }: { items: EntitySummary[] }) {40 if (!items.length) return <EmptyState title="No model links this paper yet">Model pages link papers through their model cards and documentation; the relation is written only when a source states it.</EmptyState>;41 return (42 <DataTable caption="Models introduced or described" compact>43 <thead>44 <tr>45 <Th>Model</Th>46 <Th>Type</Th>47 <Th>Organization</Th>48 <Th num>Params</Th>49 <Th num>Context</Th>50 <Th>Released</Th>51 </tr>52 </thead>53 <tbody>54 {items.map((m) => {55 const a = m.attributes ?? {};56 return (57 <tr key={m.id}>58 <Td primary>59 <EntityLink e={m} />60 </Td>61 <Td label="Type">62 <EntityBadge type={m.entity_type} small />63 </Td>64 <Td label="Organization" className="text-ink-2">{m.organization?.name ?? '—'}</Td>65 <Td num label="Params" className="tnum">{fmtParams(a.parameter_count)}</Td>66 <Td num label="Context" className="tnum">{num(a.context_length) === null ? '—' : fmtTokens(a.context_length)}</Td>67 <Td label="Released" className="tnum text-ink-2">{typeof a.release_date === 'string' ? fmtDate(a.release_date) : '—'}</Td>68 </tr>69 );70 })}71 </tbody>72 </DataTable>73 );74}7576function List({ items, empty }: { items: EntitySummary[]; empty: string }) {77 if (!items.length) return <p className="text-sm text-ink-3">{empty}</p>;78 return (79 <ul className="divide-y divide-rule border-y border-rule">80 {items.map((e) => (81 <li key={e.id} className="flex items-center gap-2 py-2 text-sm">82 <EntityBadge type={e.entity_type} small />83 <EntityLink e={e} className="truncate" />84 {e.organization && <span className="ml-auto shrink-0 text-xs text-ink-3">{e.organization.name}</span>}85 </li>86 ))}87 </ul>88 );89}9091export async function PaperPage({ d, canonical }: { d: EntityDetail; canonical: string }) {92 const a = d.attributes ?? {};93 const authors: string[] = Array.isArray(a.authors) ? (a.authors as unknown[]).map(String) : [];94 const researchers = relItems(d, ['researcher'], 'authored');95 const byName = new Map(researchers.map((r) => [r.name.toLowerCase(), r]));96 const models = relItems(d, ['model', 'artifact', 'quantization']);97 const datasets = relItems(d, ['dataset']);98 const benchmarks = relItems(d, ['benchmark']);99 const repos = [...relItems(d, ['repository', 'framework', 'library']), ...(d.repositories ?? [])].filter((x, i, arr) => arr.findIndex((y) => y.id === x.id) === i);100 const orgs = relItems(d, ['company', 'organization', 'lab', 'university']);101 const related = await safe(api.entityRelated(d.slug, 12));102 const relatedPapers = (related?.items ?? []).filter((r) => r.entity_type === 'paper' && r.id !== d.id);103 const relatedOther = (related?.items ?? []).filter((r) => r.entity_type !== 'paper' && r.id !== d.id);104 const arxiv = typeof a.arxiv_id === 'string' ? a.arxiv_id : d.identifiers?.find((i) => i.scheme === 'arxiv')?.value;105 const pdf = typeof a.pdf_url === 'string' ? a.pdf_url : arxiv ? `https://arxiv.org/pdf/${arxiv}` : null;106 const code = typeof a.code_url === 'string' ? a.code_url : null;107 const cats = [...new Set([...(typeof a.primary_category === 'string' ? [a.primary_category] : []), ...(Array.isArray(a.categories) ? (a.categories as unknown[]).map(String) : [])])];108 const crumbs = [{ name: SITE_NAME, href: '/' }, { name: 'Research', href: '/papers' }, { name: d.name, href: canonical }];109 const ld = {110 '@context': 'https://schema.org',111 '@type': 'ScholarlyArticle',112 headline: d.name,113 name: d.name,114 url: `${SITE_URL}${canonical}`,115 datePublished: typeof a.published_at === 'string' ? a.published_at : undefined,116 author: authors.slice(0, 30).map((n) => ({ '@type': 'Person', name: n })),117 abstract: typeof a.abstract === 'string' ? a.abstract : undefined,118 sameAs: [pdf, arxiv ? `https://arxiv.org/abs/${arxiv}` : null].filter(Boolean),119 identifier: arxiv ? { '@type': 'PropertyValue', propertyID: 'arxiv', value: arxiv } : undefined,120 publisher: d.organization ? { '@type': 'Organization', name: d.organization.name } : undefined,121 };122 const specRows = ['published_at', 'venue', 'doi', 'arxiv_id', 'primary_category', 'pdf_url', 'code_url', 'updated_at'].filter((k) => a[k] !== undefined && a[k] !== null && a[k] !== '').map((k) => ({ key: k, raw: a[k] }));123124 return (125 <Container wide>126 <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />127 <BreadcrumbLd items={crumbs} />128 <ViewBeacon path={canonical} />129 <Breadcrumbs items={crumbs} />130 <header className="pb-6 pt-4 md:pb-8 md:pt-5">131 <div className="flex flex-wrap items-center gap-2">132 <EntityBadge type="paper" />133 {cats.map((c) => (134 <Link key={c} href={`/papers?category=${encodeURIComponent(c)}`}>135 <Chip tone={c === a.primary_category ? 'accent' : 'neutral'} className="mono">{c}</Chip>136 </Link>137 ))}138 </div>139 <div className="mt-3 flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">140 <div className="min-w-0">141 <h1 className="display text-[26px] leading-tight md:text-[36px]">{d.name}</h1>142 <p className="tnum mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[15px] text-ink-2">143 {typeof a.published_at === 'string' && <span>Published {fmtDate(a.published_at)}</span>}144 {typeof a.venue === 'string' && <span>· {a.venue}</span>}145 {arxiv && (146 <a href={`https://arxiv.org/abs/${arxiv}`} target="_blank" rel="noopener noreferrer" className="mono inline-flex items-center gap-1 text-ink-3 hover:text-accent">147 arXiv:{arxiv} <ExternalLink className="size-3.5" aria-hidden />148 </a>149 )}150 </p>151 <div className="mt-4 flex flex-wrap items-center gap-2" aria-label="Actions">152 {pdf && (153 <a href={pdf} target="_blank" rel="noopener noreferrer" 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">154 <FileText className="size-3.5" aria-hidden /> PDF155 </a>156 )}157 {code && (158 <a href={code} target="_blank" rel="noopener noreferrer" 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">159 Code <ExternalLink className="size-3.5" aria-hidden />160 </a>161 )}162 <WatchButton e={d} />163 <Link href={`/graph/${encodeURIComponent(d.slug)}?mode=research`} 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">164 Research graph165 </Link>166 </div>167 </div>168 <div className="shrink-0 text-xs text-ink-3 lg:text-right">169 <QualityMark q={d.quality?.score} label />170 <p className="mt-1" title={d.updated_at}>171 Updated {fmtAgo(d.updated_at)} · first seen {fmtDate(d.first_seen_at)}172 </p>173 <p className="mono mt-0.5 text-[11px]">{d.id}</p>174 </div>175 </div>176 </header>177178 <div className="grid gap-10 pb-16 lg:grid-cols-[minmax(0,1fr)_22rem]">179 <div className="min-w-0">180 {typeof a.abstract === 'string' && (181 <Section id="abstract" eyebrow="Abstract" hairline={false} className="pt-0">182 <p className="prose-atlas max-w-3xl text-[15px] leading-relaxed text-ink-2">{a.abstract}</p>183 </Section>184 )}185 <Section id="authors" eyebrow="Authors" title={<>Authors <span className="tnum text-base font-normal text-ink-3">{fmtInt(authors.length || researchers.length)}</span></>}>186 {authors.length === 0 && researchers.length === 0 ? (187 <p className="text-sm text-ink-3">Author list not stated by the source.</p>188 ) : (189 <p className="flex flex-wrap gap-x-3 gap-y-1 text-sm">190 {(authors.length ? authors : researchers.map((r) => r.name)).map((n, i) => {191 const r = byName.get(n.toLowerCase());192 return r ? (193 <Link key={`${n}-${i}`} href={routes.entity(r)} className="text-ink hover:text-accent hover:underline">194 {n}195 </Link>196 ) : (197 <span key={`${n}-${i}`} className="text-ink-2">198 {n}199 </span>200 );201 })}202 </p>203 )}204 <Note className="mt-2">Linked names open researcher pages (created from the paper's author list; name-only, no affiliation unless a source states it). Unlinked names have no researcher record yet.</Note>205 </Section>206 <Section id="organizations" eyebrow="Organizations" title={<>Organizations <span className="tnum text-base font-normal text-ink-3">{fmtInt(orgs.length + (d.organization ? 1 : 0))}</span></>}>207 {d.organization || orgs.length ? (208 <List items={[...(d.organization ? [{ id: d.organization.id, entity_type: 'company', slug: d.organization.slug, name: d.organization.name, description: null, status: 'active', organization: null, attributes: {}, quality: {}, counts: {}, first_seen_at: '', last_seen_at: '', updated_at: '' } as EntitySummary] : []), ...orgs]} empty="" />209 ) : (210 <p className="text-sm text-ink-3">No organization stated. arXiv metadata does not carry affiliations; an organization is linked only when a model card or lab page cites the paper.</p>211 )}212 </Section>213 <Section id="models" eyebrow="Models" title={<>Models introduced or described <span className="tnum text-base font-normal text-ink-3">{fmtInt(models.length)}</span></>} lede="Inbound described_by relations from model cards and documentation.">214 <ModelsIntroduced items={models} />215 </Section>216 <div className="grid gap-8 md:grid-cols-2">217 <Section id="datasets" eyebrow="Datasets" title={<>Datasets used <span className="tnum text-base font-normal text-ink-3">{fmtInt(datasets.length)}</span></>}>218 <List items={datasets} empty="No dataset relation recorded." />219 </Section>220 <Section id="benchmarks" eyebrow="Benchmarks" title={<>Benchmarks used <span className="tnum text-base font-normal text-ink-3">{fmtInt(benchmarks.length)}</span></>}>221 <List items={benchmarks} empty="No benchmark relation recorded." />222 </Section>223 </div>224 <Section id="repositories" eyebrow="Code" title={<>Repositories & frameworks <span className="tnum text-base font-normal text-ink-3">{fmtInt(repos.length)}</span></>}>225 <List items={repos} empty="No repository linked." />226 </Section>227 <Section id="related" eyebrow="Related" title={<>Related papers <span className="tnum text-base font-normal text-ink-3">{fmtInt(relatedPapers.length)}</span></>} lede="Papers sharing an organization, a family or a relation with this one.">228 <List items={relatedPapers} empty="No related paper found through shared entities." />229 {relatedOther.length > 0 && (230 <>231 <p className="eyebrow mb-1.5 mt-5">Other related entities</p>232 <List items={relatedOther} empty="" />233 </>234 )}235 </Section>236 <Section id="timeline" eyebrow="Timeline" title={<>Timeline <span className="tnum text-base font-normal text-ink-3">{fmtInt(d.timeline?.length ?? 0)}</span></>}>237 <TimelineList events={d.timeline ?? []} slug={d.slug} />238 </Section>239 <Section id="sources" eyebrow="Sources" title={<>Sources <span className="tnum text-base font-normal text-ink-3">{fmtInt(d.sources?.length ?? 0)}</span></>}>240 <SourcesTable sources={d.sources ?? []} />241 </Section>242 </div>243 <aside className="min-w-0 space-y-8">244 <section>245 <p className="eyebrow mb-2">Record</p>246 <KeyValue rows={specRows} provenance={d.provenance} slug={d.slug} entity={{ name: d.name, entity_type: d.entity_type }} dense />247 <p className="mono mt-2 break-all text-[11px] text-ink-3">slug {d.slug}</p>248 </section>249 {d.identifiers?.length > 0 && (250 <section>251 <p className="eyebrow mb-2">Identifiers</p>252 <ul className="space-y-0.5 text-xs">253 {d.identifiers.map((i) => (254 <li key={`${i.scheme}:${i.value}`} className="flex gap-2">255 <span className="mono text-ink-3">{i.scheme}</span>256 <span className="mono truncate text-ink-2">{i.value}</span>257 </li>258 ))}259 </ul>260 </section>261 )}262 <section>263 <p className="eyebrow mb-2">Type</p>264 <p className="text-sm text-ink-2">265 {typeLabel(d.entity_type)} · <Link href={routes.methodology()} className="link">how papers are recorded</Link>266 </p>267 </section>268 </aside>269 </div>270 </Container>271 );272}273