daily-question 1.1.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/LICENSE +21 -21
- package/README.md +251 -114
- package/client.js +804 -361
- package/index.js +2 -2
- package/lib/async.js +17 -0
- package/lib/bank.js +3 -3
- package/lib/exam-dates.js +10 -8
- package/lib/generate.js +124 -104
- package/lib/generation-history.js +67 -0
- package/lib/llm-json.js +17 -8
- package/lib/pdf-worker.js +5 -0
- package/lib/pdf.js +156 -34
- package/lib/reference-reader.js +178 -0
- package/lib/reference-sources.js +83 -0
- package/lib/references.js +159 -0
- package/lib/research.js +166 -0
- package/lib/routes.js +3 -0
- package/lib/service.js +253 -60
- package/lib/store.js +3 -3
- package/lib/subjects.js +19 -0
- package/lib/tutor.js +6 -5
- package/lib/updates.js +49 -0
- package/package.json +4 -2
package/lib/store.js
CHANGED
|
@@ -147,18 +147,18 @@ export class Store {
|
|
|
147
147
|
const draft = structuredClone(data)
|
|
148
148
|
const result = await mutate(draft)
|
|
149
149
|
draft.updatedAt = new Date().toISOString()
|
|
150
|
+
await this.write(draft)
|
|
150
151
|
this.data = draft
|
|
151
|
-
await this.write()
|
|
152
152
|
return result
|
|
153
153
|
}
|
|
154
154
|
this.queue = this.queue.then(run, run)
|
|
155
155
|
return this.queue
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
async write() {
|
|
158
|
+
async write(data = this.data) {
|
|
159
159
|
await mkdir(this.dir, { recursive: true })
|
|
160
160
|
const tmp = `${this.file}.${process.pid}.${Date.now()}.tmp`
|
|
161
|
-
await writeFile(tmp, JSON.stringify(
|
|
161
|
+
await writeFile(tmp, JSON.stringify(data, null, 2), 'utf8')
|
|
162
162
|
await rename(tmp, this.file)
|
|
163
163
|
}
|
|
164
164
|
}
|
package/lib/subjects.js
CHANGED
|
@@ -100,6 +100,22 @@ export const SUBJECT_CATALOG = [
|
|
|
100
100
|
|
|
101
101
|
const CATALOG_BY_ID = new Map(SUBJECT_CATALOG.map((item) => [item.id, item]))
|
|
102
102
|
|
|
103
|
+
/** 参考考试独立于学习科目;不会因此在每日科目列表中新增408。 */
|
|
104
|
+
export const REFERENCE_EXAMS = [
|
|
105
|
+
...SUBJECT_CATALOG.map(({id,name,code}) => ({id,name,code})),
|
|
106
|
+
{id:'cs408',name:'计算机学科专业基础',code:'408'},
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
export function referenceExamFor(subject) {
|
|
110
|
+
if (!subject?.custom) return subject
|
|
111
|
+
if (!subject.referenceSubjectId || subject.referenceSubjectId === 'auto') return {id:'auto_'+subject.id,name:subject.name,code:subject.code || ''}
|
|
112
|
+
if (subject.referenceSubjectId === 'independent') {
|
|
113
|
+
const name = typeof subject.referenceExamName === 'string' ? subject.referenceExamName.trim() : ''
|
|
114
|
+
return name ? {id:'independent_'+subject.id,name,code:subject.referenceExamCode || ''} : undefined
|
|
115
|
+
}
|
|
116
|
+
return REFERENCE_EXAMS.find((exam) => exam.id === subject.referenceSubjectId)
|
|
117
|
+
}
|
|
118
|
+
|
|
103
119
|
/** 全部可选科目 = 内置目录 + 用户自定义。 */
|
|
104
120
|
export function allSubjects(customSubjects = []) {
|
|
105
121
|
const extra = Array.isArray(customSubjects)
|
|
@@ -113,6 +129,9 @@ export function allSubjects(customSubjects = []) {
|
|
|
113
129
|
outline: Array.isArray(item.outline) ? item.outline : [],
|
|
114
130
|
guidance: typeof item.guidance === 'string' ? item.guidance : '',
|
|
115
131
|
custom: true,
|
|
132
|
+
referenceSubjectId: typeof item.referenceSubjectId === 'string' ? item.referenceSubjectId : '',
|
|
133
|
+
referenceExamName: typeof item.referenceExamName === 'string' ? item.referenceExamName : '',
|
|
134
|
+
referenceExamCode: typeof item.referenceExamCode === 'string' ? item.referenceExamCode : '',
|
|
116
135
|
}))
|
|
117
136
|
: []
|
|
118
137
|
return [...SUBJECT_CATALOG, ...extra]
|
package/lib/tutor.js
CHANGED
|
@@ -7,7 +7,7 @@ const TUTOR_SYSTEM = `你是中国大陆硕士研究生招生考试的辅导老
|
|
|
7
7
|
- 只讨论当前这道题:题干、选项、标准答案、解析以及由它们直接延伸的知识点。如果学生的提问明显超出这道题的范围,先说明它与本题的关系,再简短回答,不要把它当成跨题的通用聊天。
|
|
8
8
|
- 用 Markdown 回答;数学公式用 LaTeX(行内 $...$,独立公式 $$...$$)。
|
|
9
9
|
- 解释要一步步来,指出关键依据,不要只给结论。
|
|
10
|
-
-
|
|
10
|
+
- 你给出的解释是对当前题目参考答案的补充说明,不能宣称修改了已保存的答案与解析;模型生成、导入或修订的答案都不能称为“官方答案”。
|
|
11
11
|
- 如果题目资料里标记答案尚未核验或缺少参考答案,必须如实说明,不要把未核验的内容说成确定结论。
|
|
12
12
|
- 不要编造与题目无关的事实。`
|
|
13
13
|
|
|
@@ -29,12 +29,13 @@ export function describeQuestionContext(question, answerDetail) {
|
|
|
29
29
|
for (const option of question.options) lines.push(`${option.key}. ${option.text}`)
|
|
30
30
|
}
|
|
31
31
|
lines.push('')
|
|
32
|
-
if (answerDetail) {
|
|
33
|
-
|
|
32
|
+
if (answerDetail?.answer?.trim()) {
|
|
33
|
+
const status=answerDetail.verified===true?'已通过独立校验,仍非官方认证':'尚未独立核验,可能有误,请先核对'
|
|
34
|
+
lines.push(`--- 参考答案(${status})---`)
|
|
34
35
|
lines.push(answerDetail.answer)
|
|
35
36
|
lines.push('')
|
|
36
|
-
lines.push(
|
|
37
|
-
lines.push(answerDetail.explanation)
|
|
37
|
+
lines.push(`--- 参考解析(${status})---`)
|
|
38
|
+
lines.push(answerDetail.explanation||'(尚未提供解析)')
|
|
38
39
|
} else {
|
|
39
40
|
lines.push('--- 答案状态 ---')
|
|
40
41
|
lines.push('本题的参考答案与解析尚未通过独立校验或尚未生成,请如实说明,不要给出确定的“标准答案”。')
|
package/lib/updates.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
export const PLUGIN_VERSION=JSON.parse(readFileSync(new URL('../package.json',import.meta.url),'utf8')).version
|
|
4
|
+
export const UPDATE_INTERVAL=6*60*60*1000
|
|
5
|
+
const REGISTRY='https://registry.npmjs.org/daily-question/latest'
|
|
6
|
+
|
|
7
|
+
function versionParts(value) {
|
|
8
|
+
if(typeof value!=='string')throw new Error('npm 返回的版本号无效')
|
|
9
|
+
const match=/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*))?(?:\+[\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*)?$/.exec(value)
|
|
10
|
+
if(!match||match[4]?.split('.').some((p)=>/^0\d+$/.test(p)))throw new Error('npm 返回的版本号无效')
|
|
11
|
+
return {numbers:match.slice(1,4).map(BigInt),pre:match[4]?.split('.')}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function newerVersion(latest,current) {
|
|
15
|
+
const a=versionParts(latest),b=versionParts(current)
|
|
16
|
+
for(let i=0;i<3;i++)if(a.numbers[i]!==b.numbers[i])return a.numbers[i]>b.numbers[i]
|
|
17
|
+
if(!a.pre||!b.pre)return Boolean(!a.pre&&b.pre)
|
|
18
|
+
for(let i=0;i<Math.max(a.pre.length,b.pre.length);i++) {
|
|
19
|
+
const x=a.pre[i],y=b.pre[i]
|
|
20
|
+
if(x===y)continue
|
|
21
|
+
if(x===undefined||y===undefined)return y===undefined
|
|
22
|
+
const nx=/^\d+$/.test(x),ny=/^\d+$/.test(y)
|
|
23
|
+
return nx&&ny?BigInt(x)>BigInt(y):nx!==ny?!nx:x>y
|
|
24
|
+
}
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createUpdateChecker({fetchImpl=globalThis.fetch,currentVersion=PLUGIN_VERSION,now=Date.now}={}) {
|
|
29
|
+
let cached,pending
|
|
30
|
+
return async ({force=false}={})=>{
|
|
31
|
+
if(pending)return {...await pending}
|
|
32
|
+
if(force!==true&&cached&&now()-cached.at>=0&&now()-cached.at<UPDATE_INTERVAL)return {...cached.result}
|
|
33
|
+
pending=(async()=>{
|
|
34
|
+
try {
|
|
35
|
+
const response=await fetchImpl(REGISTRY,{signal:AbortSignal.timeout(10000),redirect:'error',headers:{accept:'application/json'}})
|
|
36
|
+
if(!response.ok)throw new Error(`npm 服务返回 ${response.status}`)
|
|
37
|
+
const metadata=await response.json()
|
|
38
|
+
if(metadata?.name!=='daily-question')throw new Error('npm 返回的包信息不匹配')
|
|
39
|
+
const updateAvailable=newerVersion(metadata.version,currentVersion)
|
|
40
|
+
const at=now(),result={ok:true,currentVersion,latestVersion:metadata.version,updateAvailable,checkedAt:new Date(at).toISOString(),packageUrl:'https://www.npmjs.com/package/daily-question'}
|
|
41
|
+
cached={at,result}
|
|
42
|
+
return {...result}
|
|
43
|
+
} catch(error) {
|
|
44
|
+
throw new Error(`检查更新失败:${error.name==='TimeoutError'?'连接 npm 超时,请稍后重试':error.message}`)
|
|
45
|
+
}
|
|
46
|
+
})()
|
|
47
|
+
try{return {...await pending}}finally{pending=null}
|
|
48
|
+
}
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "daily-question",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "考研每日一题 — DSH 插件:每科每天一道独立校验过的考研题,含倒计时、错题本、收藏与按题隔离的追问。",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"node": ">=20.3.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"test": "node test/check.mjs && node test/service-check.mjs && node test/render-check.mjs"
|
|
34
|
+
"test": "node test/check.mjs && node test/service-check.mjs && node test/tutor-status-check.mjs && node test/attempt-binding-check.mjs && node test/list-performance-check.mjs && node test/daily-attempt-check.mjs && node test/render-check.mjs && node test/ui-race-check.mjs && node test/followup-check.mjs && node test/task-check.mjs && node test/generation-check.mjs && node test/generation-history-check.mjs && node test/reference-check.mjs && node test/reference-reader-check.mjs && node test/research-check.mjs && node test/pdf-safety-check.mjs && node test/update-check.mjs",
|
|
35
|
+
"test:live": "node test/live-check.mjs"
|
|
35
36
|
},
|
|
36
37
|
"exports": {
|
|
37
38
|
".": "./index.js",
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"cordis.patch.yml",
|
|
46
47
|
"locale/*.json",
|
|
47
48
|
"icon.svg",
|
|
49
|
+
"CHANGELOG.md",
|
|
48
50
|
"lib/*.js",
|
|
49
51
|
"vendor/mathjax/*"
|
|
50
52
|
],
|