-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwikipedia.mjs
More file actions
136 lines (121 loc) · 4.95 KB
/
Copy pathwikipedia.mjs
File metadata and controls
136 lines (121 loc) · 4.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
const endpoint = "https://zh.wikipedia.org/w/api.php";
const userAgent = "QuickLearn/1.0 (https://github.com/fly1d/quicklearn-agent)";
function cleanText(value = "") {
return String(value).replace(/\s+/g, " ").trim();
}
function sentenceList(value, limit = 6) {
return cleanText(value)
.split(/(?<=[。!?.!?])\s*/)
.map((item) => item.trim())
.filter((item) => item.length >= 12)
.slice(0, limit);
}
function normalizeTerm(value = "") {
return value.toLowerCase().replace(/[\s_()()·.-]+/g, "");
}
function isDisambiguation(page) {
return Object.hasOwn(page.pageprops || {}, "disambiguation") || /消歧[义義][页頁]/.test(page.description || "");
}
function sourceUrl(title) {
return `https://zh.wikipedia.org/wiki/${encodeURIComponent(title.replace(/\s+/g, "_"))}`;
}
function clarificationResult(input, pages) {
const top = pages[0];
const disambiguation = pages.find(isDisambiguation);
if (!top || !disambiguation) return null;
const exactDisambiguation = normalizeTerm(disambiguation.title) === normalizeTerm(input);
const topIsExact = normalizeTerm(top.title) === normalizeTerm(input);
const likelyAmbiguous = top === disambiguation || exactDisambiguation || (!topIsExact && disambiguation.index <= 2);
if (!likelyAmbiguous) return null;
const options = [...new Set(pages
.filter((page) => !isDisambiguation(page) && !/列表条目|列表條目/.test(page.description || ""))
.map((page) => page.title))]
.slice(0, 3);
if (options.length < 2) return null;
return {
needsClarification: true,
question: `“${input}”可能有几种意思,你想了解哪一个?`,
options
};
}
function learningResult(page) {
const facts = sentenceList(page.extract);
const description = cleanText(page.description);
const summary = (facts.slice(0, 2).join(" ") || description).slice(0, 360);
const details = [
facts[0] || description || `这是关于 ${page.title} 的百科条目。`,
facts[1] || `${page.title} 的背景和适用语境值得结合原文继续确认。`,
facts[2] || `可以通过相近概念和实际例子进一步理解 ${page.title}。`
];
const takeaways = facts.slice(0, 3).map((item) => item.slice(0, 92));
while (takeaways.length < 3) {
takeaways.push(["先掌握准确定义", "再确认背景与边界", "用例子验证理解"][takeaways.length]);
}
return {
title: page.title,
category: "维基百科速览",
summary,
definition: details[0],
why: description
? `${page.title}通常被概括为“${description}”。理解它有助于建立相关主题的基础背景和概念边界。`
: `理解 ${page.title} 的定义、背景与边界,可以为继续阅读专业资料建立稳定起点。`,
concepts: [["基本定义", details[0]], ["关键背景", details[1]], ["延伸理解", details[2]]],
takeaways,
misconception: "百科摘要适合建立第一层认识,但条目可能持续更新;涉及专业判断时仍应核对原始资料。",
steps: [`用一句话复述 ${page.title}`, `区分 ${page.title} 与相近概念`, "打开来源,重点查看定义、背景和示例"],
source: {
url: sourceUrl(page.title),
host: "zh.wikipedia.org",
title: page.title,
provider: "维基百科",
license: "CC BY-SA 4.0",
excerpts: facts.map((item) => item.slice(0, 280)).slice(0, 6)
}
};
}
export async function lookupWikipedia(input, options = {}) {
const query = input.trim();
if (!query || query.length > 120) return null;
const fetchImpl = options.fetchImpl || fetch;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3500);
timeout.unref?.();
try {
const url = new URL(endpoint);
url.search = new URLSearchParams({
action: "query",
generator: "search",
gsrsearch: query,
gsrnamespace: "0",
gsrlimit: "6",
prop: "extracts|description|pageprops",
exintro: "1",
explaintext: "1",
exsentences: "6",
ppprop: "disambiguation",
redirects: "1",
format: "json",
formatversion: "2"
});
const response = await fetchImpl(url, {
signal: controller.signal,
headers: { Accept: "application/json", "User-Agent": userAgent }
});
if (!response.ok) return null;
const data = await response.json();
const pages = Array.isArray(data?.query?.pages)
? data.query.pages
.filter((page) => page?.ns === 0 && typeof page.title === "string")
.sort((a, b) => (a.index ?? Number.MAX_SAFE_INTEGER) - (b.index ?? Number.MAX_SAFE_INTEGER))
: [];
if (!pages.length) return null;
const clarification = clarificationResult(query, pages);
if (clarification) return clarification;
const page = pages.find((item) => !isDisambiguation(item) && cleanText(item.extract).length >= 30);
return page ? learningResult(page) : null;
} catch {
return null;
} finally {
clearTimeout(timeout);
}
}