THE OPEN RESEARCH READER 01 — INDEX
好研究,
值得被读到。
从一个问题,到一篇论文。
发现研究、阅读摘要,回到每一项工作的原始出处。
浏览论文
{data.mode === "sample" && 真实数据库样本 · 当前展示 2026-11-06 导出的前 20 条记录,搜索与筛选仅限此样本。
数据说明
}
- {f.topic || "论文索引"}{data.total.toString().padStart(2, "0")}
+
{f.topic || "论文索引"}{data.total !== null && {data.total.toLocaleString("zh-CN")}}
{(f.q || f.topic) &&
{f.q ? `搜索 “${f.q}”` : f.topic}清除筛选
}
- {data.items.length ?
{data.items.map((p, i) => - {String((data.page - 1) * data.pageSize + i + 1).padStart(2, "0")}
{p.topics[0] || "Research"}{paperDate(p.publishedAt)}
{p.title}
{p.abstract || "来源未提供摘要。"}
{p.authors.slice(0, 3).join(", ")}{p.authors.length > 3 ? ` +${p.authors.length - 3}` : ""}阅读简介
)}
:
没有找到符合条件的论文
{data.mode === "sample" ? "当前只有 20 条样本,试试更短的关键词,或清除领域筛选。" : "试试其他关键词,或清除筛选。"}
}
-
;
+ {issue ?
: data.items.length ?
{data.items.map((p, i) => - {String((data.page - 1) * data.pageSize + i + 1).padStart(2, "0")}
{f.topic || p.topics[0] || "Research"}{paperDate(p.publishedAt)}
{p.title}
{p.abstract || "来源未提供摘要。"}
{p.authors.slice(0, 3).join(", ")}{p.authors.length > 3 ? ` +${p.authors.length - 3}` : ""}阅读简介
)}
:
没有找到符合条件的论文
{data.mode === "sample" ? "当前只有 20 条样本,试试更短的关键词,或清除领域筛选。" : "试试其他关键词,或清除筛选。"}
}
+ {!issue &&
}{data.total !== null && data.total > MAX_PAPER_PAGE * data.pageSize &&
当前最多浏览前 {(MAX_PAPER_PAGE * data.pageSize).toLocaleString("zh-CN")} 条结果。搜索或筛选研究领域,可进一步缩小范围。
}
;
}
diff --git a/src/lib/paper-api.mjs b/src/lib/paper-api.mjs
new file mode 100644
index 0000000..603c679
--- /dev/null
+++ b/src/lib/paper-api.mjs
@@ -0,0 +1,17 @@
+// Anonymous server-side transport. Expected HTTP/network failures are values;
+// callers can keep the search form usable without falling back to sample data.
+export function createPaperTransport(origin, requestUpstream = fetch) {
+ return async function request(path) {
+ let response;
+ try {
+ response = await requestUpstream(new URL(path, origin.replace(/\/$/, "") + "/"), {
+ cache: "no-store", signal: AbortSignal.timeout(12000), redirect: "error",
+ });
+ } catch (error) {
+ return { ok: false, status: error?.name === "TimeoutError" || error?.name === "AbortError" ? 504 : 503 };
+ }
+ if (!response.ok) return { ok: false, status: response.status };
+ try { return { ok: true, data: await response.json() }; }
+ catch { return { ok: false, status: 502 }; }
+ };
+}
diff --git a/src/lib/papers.ts b/src/lib/papers.ts
index ae88a09..ef93462 100644
--- a/src/lib/papers.ts
+++ b/src/lib/papers.ts
@@ -1,13 +1,16 @@
import snapshot from "@/data/papers.json";
+import { createPaperTransport } from "./paper-api.mjs";
export type Paper = { id: string; title: string; abstract: string | null; authors: string[]; publishedAt: string | null; topics: string[]; sources: { label: string; url: string; externalId: string }[]; doi?: string | null; journal?: string | null; comments?: string | null; publications?: { venue: string; year: number; track: string; status: string; evidenceUrl?: string }[] };
export type PaperPage = { items: Paper[]; total: number; page: number; pageSize: number; topics: string[]; mode: "sample" | "live"; capturedAt?: string };
+export type PaperFailure = { mode: "error"; reason: "timeout" | "busy" | "invalid_query" | "unavailable"; items: Paper[]; total: null; page: number; pageSize: number; topics: string[] };
export type Filters = { q?: string; topic?: string; sort?: string; page?: string; searchIn?: string };
export function normalizeFilters(input: Record