SPB Git

spb/autoscholar

Public
3.6 KB · 94 lines tsx
1import { listSeries, searchPapers } from "@autoscholar/core";2import { PublicSearchQuery } from "@autoscholar/schemas";3import type { Metadata } from "next";4import { Disclosure } from "@/components/Disclosure";5import { EmptyResearchState, EmptyState } from "@/components/EmptyState";6import { FilterSidebar, LibraryFilters } from "@/components/LibraryFilters";7import { Pagination } from "@/components/Pagination";8import { PaperCard } from "@/components/PaperCard";9import { t } from "@/lib/i18n";10import { db } from "@/lib/server/db";1112export const dynamic = "force-dynamic";13export const metadata: Metadata = {14  title: "Research library",15  description: "Searchable library of AutoScholar working papers.",16  alternates: { canonical: "/research" },17};1819export default async function ResearchPage({20  searchParams,21}: {22  searchParams: Promise<Record<string, string | string[] | undefined>>;23}) {24  const sp = await searchParams;25  const raw: Record<string, string | undefined> = {};26  for (const [k, v] of Object.entries(sp)) raw[k] = Array.isArray(v) ? v[0] : v;27  for (const k of Object.keys(raw)) if (raw[k] === "") delete raw[k];28  const parsed = PublicSearchQuery.safeParse(raw);29  const q = parsed.success ? parsed.data : PublicSearchQuery.parse({});30  const d = db();31  const res = await searchPapers(d, q);32  const series = await listSeries(d);33  const hrefFor = (p: number) => {34    const u = new URLSearchParams();35    for (const [k, v] of Object.entries(raw)) if (v && k !== "page") u.set(k, v);36    u.set("page", String(p));37    return `/research?${u.toString()}`;38  };39  const anyFilter = Object.keys(raw).some((k) => !["page", "sort", "pageSize"].includes(k));40  return (41    <div className="container-x section-tight">42      <header className="max-w-2xl">43        <p className="eyebrow">Library</p>44        <h1 className="title-1 mt-2">Working papers</h1>45        <p className="muted mt-2 text-sm leading-relaxed">46          Search titles, abstracts, keywords and verified metadata. Every entry is an AI-generated working47          paper with its evidence attached.48        </p>49      </header>50      {!parsed.success && (51        <p role="alert" className="mt-4 text-sm" style={{ color: "var(--color-danger-500)" }}>52          Some filters were invalid and have been ignored.53        </p>54      )}55      <div className="mt-6 grid gap-8 lg:grid-cols-12">56        <div className="lg:col-span-9 lg:col-start-4 lg:row-start-1">57          <LibraryFilters q={raw} series={series} total={res.total} />58        </div>59        <aside60          className="hidden lg:col-span-3 lg:col-start-1 lg:row-span-2 lg:row-start-1 lg:block"61          aria-label="Filters"62        >63          <FilterSidebar q={raw} series={series} />64        </aside>65        <div className="lg:col-span-9 lg:col-start-4">66          {res.items.length === 0 ? (67            anyFilter ? (68              <EmptyState title={t("search.empty")}>69                <a href="/research">Clear all filters</a>70              </EmptyState>71            ) : (72              <EmptyResearchState />73            )74          ) : (75            <ul className="border-t hairline">76              {res.items.map((p, i) => (77                <li key={p.publicId}>78                  <PaperCard p={p} variant="row" index={i} />79                </li>80              ))}81            </ul>82          )}83          <div className="mt-6 flex items-center justify-between gap-4">84            <Pagination page={res.page} pageSize={res.pageSize} total={res.total} hrefFor={hrefFor} />85          </div>86          <div className="mt-8">87            <Disclosure compact />88          </div>89        </div>90      </div>91    </div>92  );93}94
Files · cbf31e64fd1db62a1819edf166ff2d903b6d3510
Menu

Theme