dsh-plugin-litsearch 0.3.0 → 0.4.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.
Files changed (39) hide show
  1. package/README.md +158 -41
  2. package/client/client.js +2632 -926
  3. package/lib/host/events.d.ts +9 -1
  4. package/lib/host/events.js +50 -3
  5. package/lib/host/export/bibtex.js +1 -4
  6. package/lib/host/export/common.d.ts +0 -2
  7. package/lib/host/export/common.js +0 -7
  8. package/lib/host/export/csv.js +1 -5
  9. package/lib/host/export/markdown.d.ts +1 -1
  10. package/lib/host/export/markdown.js +9 -26
  11. package/lib/host/export/ris.js +1 -4
  12. package/lib/host/paths.d.ts +1 -1
  13. package/lib/host/paths.js +1 -1
  14. package/lib/host/pdf/store.d.ts +5 -3
  15. package/lib/host/pdf/store.js +50 -17
  16. package/lib/host/router.js +146 -79
  17. package/lib/host/tasks/download.d.ts +104 -0
  18. package/lib/host/tasks/download.js +274 -0
  19. package/lib/host/tasks/filter.d.ts +8 -1
  20. package/lib/host/tasks/filter.js +14 -0
  21. package/lib/host/tasks/service.d.ts +44 -11
  22. package/lib/host/tasks/service.js +216 -48
  23. package/lib/host/tasks/states.d.ts +1 -3
  24. package/lib/host/tasks/states.js +2 -66
  25. package/lib/host/tasks/stats.js +3 -0
  26. package/lib/host/tasks/summary.js +1 -1
  27. package/lib/host/tools.d.ts +5 -1
  28. package/lib/host/tools.js +813 -37
  29. package/lib/host/zotero/connector.d.ts +19 -10
  30. package/lib/host/zotero/connector.js +142 -39
  31. package/lib/host/zotero/match.d.ts +27 -0
  32. package/lib/host/zotero/match.js +59 -0
  33. package/lib/search/config.d.ts +0 -2
  34. package/lib/search/config.js +0 -2
  35. package/lib/search/oa.d.ts +8 -0
  36. package/lib/search/oa.js +53 -6
  37. package/lib/search/types.d.ts +154 -10
  38. package/lib/search/types.js +11 -0
  39. package/package.json +1 -1
package/README.md CHANGED
@@ -1,42 +1,124 @@
1
1
  # dsh-plugin-litsearch
2
2
 
3
- **给 DSH 加一个免费的文献检索助手。**
3
+ **给 DSH 装一个免费的文献检索助手。**
4
4
 
5
- 装上这个插件,大模型可以直接检索开放学术文献,你也可以在 DSH 右侧栏用同一套数据源检索,并把感兴趣的论文一键带进对话。无需账号、无需 API Key、无需本地服务。
5
+ 不用注册账号、不用申请密钥、也不用另外启动什么服务。装上以后,AI 可以直接帮你查公开学术文献;你也可以在 DSH 右侧栏用同样的数据源自己查,看到感兴趣的论文,点一下就能交给 AI 接着读。
6
6
 
7
- > 当前为 P4:七个免费数据源聚合,支持多检索式(可命名、启停、按源覆写)、去重、筛选与概览;可预览 / 下载开放获取 PDF、用 Europe PMC 兜底 HTML 全文、同步到本机 Zotero,并对结果做批量筛读、分组、备注、导出与批量插入对话。每篇论文还能打开独立论文页,查看 PubTator 实体标注;检索式带字段语法时会提示哪些源只按自然语言处理。
7
+ > 当前版本 **v0.3.5**:七个免费文献来源,外加可选的谷歌学术;支持一次写多条检索式、反复调整条件重搜、自动去重、筛选与图表概览、边读边筛与分组、跨页选中与批量操作、PDF 预览下载(含开放获取补全与失败归类)、**在对话里说一句话就批量下载或批量存入 Zotero**、选目录同步、导出台账与批量加入对话。
8
+ >
9
+ > 下面的「能做什么」是写给使用者的;再往后的安装、配置、数据目录、架构等章节面向开发者和运维。
8
10
 
9
11
  ## 能做什么
10
12
 
11
- - **模型侧检索**:内置 `literature_search`、`literature_sources`、`literature_task`、`literature_export` 四个工具,可提交多条检索式与年份、开放获取、文献类型等条件;任务可被读取、筛读、分组、对比、查看轮次历史与导出。同一主题的多轮检索复用同一个任务,不会连续弹出新 tab;一个对话只保留一个任务 tab,模型可在执行前用 `literature_task`(`action=current`)确认这个对话当前的任务。
12
- - **人侧检索**:右侧栏「文献检索」面板,可加多条检索式、勾选数据源,结果显示来源、被引与开放获取状态;任务自动保存,可随时回来继续。
13
- - **高级检索式编辑**:每条检索式可改自定义名称、单独启用 / 停用、并针对某个数据源写覆写式(例如 PubMed 用 MeSH、OpenAlex 用概念检索);未覆写的源仍用主检索式。
14
- - **任务条件编辑**:任务完成后仍可在面板内「改条件」调整检索式、数据源、年份、开放获取、语言、类型、排序与每源条数,保存后一键重跑,无需新建任务。每次重跑都会记为任务内新的一轮。
15
- - **轮次历史时间轴**:任务页右上角历史图标打开时间轴,按时间列出每一轮的发起方、检索条件、命中数、完成状态;可一键把某一轮的条件回填后重跑。
16
- - **任务列表**:主页与任务页右上角都有任务列表图标,可打开、重命名、删除任意任务;从列表点开某任务即「进入」该任务,右侧栏原有的任务 tab 会被原位替换(一个对话只保留一个任务 tab),之后的模型检索默认落在这里。
17
- - **数据源语法提示**:检索式含字段标签或布尔语法时,提示 Semantic Scholar / PubTator 会按自然语言处理;数据源按钮悬浮可查看各自支持的语法,必要时用单源覆写保留查询意图。
18
- - **独立论文页**:任意结果可打开独立论文 tab,集中展示完整摘要、标识符、筛读 / 分组 / 备注 / 标签 / 必读序号、PDF 与 HTML 全文预览,并可插入对话。
19
- - **PubTator 实体**:有 PMID 的论文可查看 PubTator3 标注的基因、疾病、化学物等实体,按标准标识符去重并显示出现次数。
20
- - **筛选与概览**:结果可按关键词、来源、年份、类型、语言、人工筛读状态、缺全文、全文获取状态等条件过滤并排序;结果页顶部常驻全任务口径的全文覆盖横幅,可一键筛出缺全文或跳到全文台账;「概览」页用彩色条形 / 堆叠图紧凑展示漏斗、指标、全文覆盖、年份分布(横轴为完整 4 位年份)、来源与检索式贡献及类型、语言、出处、作者分布,多指标用「条 + 数字列」紧凑表呈现;点任意条目(含年份柱、全文状态、筛读三态)即可反向筛选结果。
21
- - **筛读工作流**:每篇论文可标记未读 / 已读 / 纳入 / 排除,写备注、打标签、加入必读顺序;任务内可建「研究分组」,按为什么读归类,并导出分层报告。
22
- - **全文预览与下载**:在结果卡片里直接预览开放获取 PDF,或下载到本地(默认落在当前会话工作目录下的 `dsh-litsearch/`);文件按「作者年份_标题_标识.pdf」命名,支持断点式 Range 读取。没有 PDF 但有 PMCID 时,用 Europe PMC 全文渲染可读 HTML。
23
- - **全文台账**:「全文」页按全任务口径(与概览一致、不受结果页筛选影响)列出覆盖率;状态数字可点击切换清单,缺全文、有全文直链、已下载、需订阅、被出版商拒绝、获取失败等各自成表,可分页加载、多选批量下载 PDF / 存入 Zotero,并可把当前清单带进结果页(会清空其他筛选,保证篇数一致)。
24
- - **Zotero 同步**:通过本机 Zotero 桌面端 connector 保存条目,可选同时附上 PDF;未连接 Zotero、同步失败、部分成功都会给出明确提示,并保留一条带当前条目范围的 RIS 导出入口。
25
- - **批量操作**:结果页与全文页均可多选后批量下载 PDF、存入 Zotero;结果页还支持批量改筛读状态、归入或移出分组、按选中导出。
26
- - **导出与对比**:按当前筛选或选中条目导出 CSV 台账、RIS、BibTeX、Markdown 报告;也可选一个旧任务做新增 / 共有 / 缺失对比,并把「新增」整批标记为纳入。
27
- - **加入对话**:点击「加入对话」,把论文元数据与摘要写入当前会话草稿(不自动发送),模型可据此继续工作;勾选多篇后可一次插入,按序号编号。
28
- - **任务加入对话**:任务页的「加入对话」把任务 id、标题与当前检索条件写入会话草稿(不自动发送),在新会话里也能明确续接同一任务的下一轮,而不是新建任务。
29
- - **免费数据源**:全部无需付费或申请密钥;NCBI 与 Semantic Scholar 可选填 Key 提高额度。限流在本地按源串行排队:冷却期内排队等待而非报错,收到 429 会退避重试并自适应放大该源间隔,连续成功后回落;只有上游要求冷却过久才优雅跳过该源,此时任务不会变「部分完成」、面板也只给中性提示。单个源触发限流只影响该源。超时 / 临时网络故障会重试一次;Europe PMC 的 `resultType=core` 检索单独享有 25s 超时下限。
13
+ 一句话:**你想弄明白一个题目,插件帮你把散在各处的论文找齐、理清、读上,还能随时叫 AI 接着聊。**
14
+
15
+ ### 第一次用,三步就够
16
+
17
+ 1. 在 DSH 右侧栏点开「文献检索」,输入想找的东西(比如「GLP-1 与心血管结局」),选好年份、语言,点检索。
18
+ 2. 结果按来源列出来,每篇能看到标题、作者、年份、被引次数,以及「能不能免费下载全文」。
19
+ 3. 看到有用的,点「加入对话」,AI 就能基于这篇帮你总结、比较或写提纲。
20
+
21
+ 也可以直接在聊天里让 AI 去找,结果同样出现在右侧栏,你随时能接手;想批量下 PDF 也直接说一句就行(见「在对话里批量下载」)。
22
+
23
+ ### 找得全,也找得准
24
+
25
+ - **一次问好几个方向**:可以同时写几条检索式(比如一条查药物、一条查副作用),各起个名字、随时单独开关;重复出现的论文会自动合并,不会看两遍。
26
+ - **想查哪些网站就勾哪些**:年份、语言、文献类型、只看能免费下载的、按相关度 / 被引次数 / 年份排序,都能当场调。
27
+ - **复杂检索式也不怕**:医学检索里常见的字段标签、AND / OR 这类写法,界面会提示哪些网站只认大白话,必要时可以单独给某个网站写一条更合适的检索式。
28
+ - **默认全都免费**:PubMed、Europe PMC、OpenAlex、Crossref、Semantic Scholar、arXiv、PubTator 这七个来源都不用注册、不用付费、不用申请密钥。想要覆盖更广,可以另外填一个 SerpAPI 密钥启用「谷歌学术」(每月有免费额度,默认关闭)。
29
+ - **网站忙的时候不用管**:某个来源限流了会自动排队等一会儿再试,只影响它自己,其他来源照常出结果;网络偶尔抖动也会自动重试一次。设置页里可以逐个开关来源、填可选密钥、点按钮测试通不通。NCBI 和 Semantic Scholar 可以选填密钥换更高额度。
30
+
31
+ ### 每次调整都留得下痕迹
32
+
33
+ - **一个题目 = 一个任务**:做过的题目都自动保存,随时回来接着看;任务列表里能打开、改名、删除。一个聊天窗口只跟一个任务,切换任务会原位替换,不会越开越多窗口。
34
+ - **不满意就改条件重搜**:检索完成后仍可直接改检索式、来源、年份、语言、排序等,一键重跑,不用从头新建。每次重跑记为任务里的新的一轮。
35
+ - **每一轮都查得到**:任务页右上角的历史图标会按时间列出「谁发起的、当时用了什么条件、找到多少篇、有没有跑完」,看中哪一轮可以照它的条件再跑一次。
36
+ - **换新对话也能续上**:点任务页的「加入对话」,把任务名和当前条件放进输入框,新会话里 AI 就知道是接着这个题目继续查,而不是另起炉灶。
37
+ - **AI 在聊天里发起新一轮时,右侧栏会自动刷新并给出提醒**,不用手动去点。
38
+
39
+ ### 结果看得明白
40
+
41
+ - **一眼看清整批文献**:「概览」页用环形图、折线图和双色条展示一共找到多少、筛到哪一步、多少篇有全文,以及年份、来源、类型、语言、作者怎么分布;图上任意一块都能点,直接筛出对应的论文。
42
+ - **想筛就筛**:按关键词、来源、年份、类型、语言、有没有全文、读没读过来过滤,结果一页一页翻,篇数多也不会挤成一团。
43
+ - **想细看就单独打开**:每篇论文有自己的页面,完整摘要、编号、PDF 与网页版全文都集中在一处;有 PMID 的论文还能看到系统标出的基因、疾病、化学物等关键词。
44
+ - **全文有没有,一本账**:「全文」页按整批结果统计,列出哪些能免费下、哪些需要订阅、哪些被出版社拦了、哪些压根没有可用地址、哪些已经下载好;点数字就能看清单,再批量下载或存进 Zotero,还能把这份清单带回结果页继续筛。
45
+
46
+ ### 读过的都记得住
47
+
48
+ - 每篇可以标「没读 / 读过 / 纳入 / 排除」;同一任务里还能建「研究分组」,按「为什么读」归类并导出分层报告。
49
+ - 换一轮检索也不会丢:标过的筛读状态会跟着论文一直保留。
50
+ - 可以拿当前结果和某个旧任务对比,看新增了哪些、共有哪些、少了哪些,并把「新增」一键整批标成纳入。
51
+ - 批量操作都在勾选后完成:批量改筛读状态、归入或移出分组、下载 PDF、存 Zotero、按选中导出。
52
+ - **跨页选中不丢**:排序右侧有「本页全选 / 取消全选」,本页全选是累加而非替换,翻页或改排序都保留已选文献,累计数量一直显示在工具栏上;跨页选中的文献会异步补齐标题再参与批量操作。「取消全选」清空所有页的已选。
53
+ - 批量下载固定 5 个并发,已下载的自动跳过,弹窗里逐篇实时显示状态;存入 Zotero 同样是 5 并发、逐篇进度。
54
+
55
+ ### 全文和参考文献库都接得上
56
+
57
+ - 能免费下载的 PDF 可以直接在结果里预览,或下载到电脑(默认放在当前工作目录的 `dsh-litsearch/` 文件夹,文件名带作者、年份、标题,好认好找)。没有 PDF 但有 PMC 编号时,会给出排版好的网页版全文。
58
+ - 下载前会按「论文自带的 PDF 直链 → 开放获取副本 → 全文渲染页」依次尝试;**在设置页填了联系邮箱还会启用 Unpaywall**,按 DOI 补出更多开放获取地址。补出来的出版商直链仍可能被拒绝,此时会落到下面的失败归类。
59
+ - 批量下载把「需手动获取」和「失败」分开统计:出版商要求订阅、登录或人机验证时,后台程序拿不到 PDF(浏览器带着会话和机构授权反而能打开);403 / 限流、人机验证页、返回的其实是 HTML 登录页、压根没有 PDF 地址,都会分别给出原因,点卡片上的 PDF / DOI 链接在浏览器打开后手动另存即可。
60
+ - 电脑上开着 Zotero 桌面端的话,点「存入 Zotero」会先弹出目录选择框(列出 Zotero 里的目录,标明当前选中和不可写附件的目录),确认后 5 并发逐篇同步、实时显示结果,可随时取消;没连上、存了一半失败或目录只读都会明确告诉你,并保留一条只含本次条目的 RIS 导出作为备用。**也可以直接在对话框里说一句「把这批存到综述目录」**,详见下面两节。
61
+ - 要交给别的软件用,可以导出 CSV 表格、RIS、BibTeX 或 Markdown 报告;按当前筛选或只按勾选的条目都行。
62
+
63
+ ### 和 AI 接着聊
64
+
65
+ - 点「加入对话」,论文会变成输入框里的一个小卡片(胶囊),鼠标停上去就能看到标题、出处和摘要;发送后 AI 拿到的是完整信息。一次勾多篇会合成一个卡片,按顺序编号。
66
+ - 整个任务也能「加入对话」,方便在新会话里明确续接同一个任务。
67
+ - **不会自动发送,也不会自作主张再搜一轮**:每轮结束后 AI 只给「怎么把检索改得更好」的建议,是否继续由你决定。
68
+ - **下载和存 Zotero 都能直接说**:在对话框说「下载当前页的所有 PDF」「把这批存到综述目录」,AI 会把范围算准并让右侧栏弹出对应的批量操作框,详见下面两节。
69
+
70
+ ### 在对话里批量下载
71
+
72
+ 直接在对话框说要下什么,模型会用 `literature_download` 把范围算成一份已校验的文献清单;右侧栏**自动弹出批量下载框并立即开始**,与手动勾选后点批量下载走的是同一套流程:逐篇显示标题、来源与实时状态,已下载的自动跳过,付费墙 / 需手动获取的单独归类。
73
+
74
+ | 你可以这么说 | 实际范围 |
75
+ | --- | --- |
76
+ | 「帮我下载标题带 xxx 的那篇」「下载这几篇」 | 指定文献(用结果里给出的 `paperId`) |
77
+ | 「帮我下载当前页的所有 pdf 文献」 | 右侧栏当前这一页,且仅有 PDF 直链或已下载 |
78
+ | 「把第二页有 pdf 的文献下载下来」「下载第 4–5 页」 | 面板上的第 2 页 / 第 4–5 页(每页 20 条);叠加 `hasPdf` 时先切页、再在页内筛 |
79
+ | 「下载『综述』分组下的文献」 | 指定分组,跨页全量 |
80
+ | 「下载 2023 年以后、开放获取、已纳入的文献」 | 按条件筛选 |
81
+ | 「帮我下载当前全部文献」 | 当前筛选下的全部结果(跨页);明确说「整个任务 / 不管筛选」才下全任务 |
82
+
83
+ - **页码由宿主换算,模型不翻页、也不搬 id**:`scope=pages` + `pages=[2]`(或 `[4,5]`)交给服务端按视图快照的排序、筛选与每页 20 条切片,再与 `hasPdf` 等条件在页内取交集。`pageBase='view'`(默认)是「第 2 页里有 PDF 的」,`pageBase='filtered'` 是「有 PDF 的文献里的第 2 页」。页码越界会直接拒绝下发并给出总页数。
84
+ - **不确定范围时可以先预演**:`literature_scope` 是只读工具,参数与下载 / 同步完全一致,返回命中篇数、总页数、面板当前视图与清单前 10 篇(含 `paperId` 与有无 PDF),核对无误后再执行,不会产生任何指令。
85
+ - 模型跑在后台,看不到界面。「当前页」「第 N 页」「当前全部」由右侧栏定期上报的视图快照 + 服务端筛选精确解析,清单里每个 `paperId` 都会与任务结果逐一校验,模型编造的 id 会被忽略。面板还没打开或快照过期时,工具会提示先打开任务页,不会按错误的基准切页。
86
+ - 面板每页固定 20 条(`RESULT_PAGE_SIZE`),与任务条件的「每源条数」不是一回事:模型侧按页取数只认前者,`literature_task current/get` 会直接回显「面板视图:第 1/30 页|每页 20 条|筛选…」。
87
+ - 弹窗顶部标明「模型发起」、范围说明与篇数,并附上你的原话;每篇显示标题、来源与期刊·年份,方便核对有没有选错。可随时点「取消下载」中止(**关闭弹窗只隐藏,后台仍会继续跑完**);取消只能中止尚未发出的请求,已到达后台的仍可能落盘,刷新任务页会同步真实状态。手动批量下载的交互保持不变。
88
+ - 连续说几条下载指令会**排队串行执行**,不会丢也不会重复;任务 tab 没打开时先自动拉起。
89
+ - `hasPdf`(有 PDF 直链或已下载)与 `openAccessOnly`(上游标记为开放获取)是两套口径,不会混用;付费墙文献不会静默算作成功。
90
+ - 不设单次篇数上限:几千篇会明显耗时并可能触发上游限流,已下载的会跳过、失败会归类,建议大范围下载前先收窄条件。视图快照只存内存,宿主重启后需重新打开任务页才能解析「当前页」。
91
+ - 该功能依赖新增的模型工具,**升级后需重启宿主或重新加载插件**才会注册生效。
92
+
93
+ ### 在对话里批量存入 Zotero
94
+
95
+ `literature_zotero` 与下载工具**共用同一套范围解析**(7 种 `scope` + `ResultsQuery` 筛选字段 + 视图快照 + 逐个校验 `paperId`),同样把范围算成一份已校验清单下发给右侧栏,由右侧栏复用「存入 Zotero」弹窗逐篇执行。区别只有一步:**目录**。
96
+
97
+ | 你可以这么说 | 实际行为 |
98
+ | --- | --- |
99
+ | 「把『综述』分组的文献存到 Zotero」(没提目录) | 弹窗加载好文献和目录,**由你选目录再点「同步」**,与手动操作完全一致 |
100
+ | 「把这批存到『综述』目录」 | 工具先在本机 Zotero 目录列表里核实目录 → 弹窗**自动预选并立即开始同步** |
101
+ | 「存到『综述』」但 Zotero 里有多个匹配 / 找不到 | **不下发任何指令**,工具返回可用目录清单,让模型在对话里向你确认 |
102
+ | 「把当前页的文献存到 Zotero」 | 范围与「当前页」下载一致:右侧栏正在看的这一页,可叠加 `hasPdf` 等条件 |
103
+
104
+ - 目录匹配只认**唯一命中**:id 精确 → 名称精确(忽略大小写与空白)→ 名称包含;任何一步命中多个都算歧义。**绝不猜一个目录就写入数据**,因为同步会真的在 Zotero 里建条目。
105
+ - 弹窗顶部标明「模型发起」、范围说明、篇数与你的原话;每篇显示标题、来源与期刊·年份,以及待同步 / 同步中 / 已同步 / 失败。可随时取消(只停止派发后续文献,已发出的请求仍会完成),失败时同样保留 RIS 兜底导出。
106
+ - Zotero 桌面端没开、目录列表读不到、或设置里关闭了「Zotero 同步」时,工具直接返回错误**不下发指令**,也不会偷偷改存「我的文库」;指定目录只读时仍能同步条目,但不带 PDF 附件,弹窗会标明。
107
+ - **不跳过「已同步」文献**:模型触发沿用现有重跑语义,会在 Zotero 里新建条目、不覆盖已有条目。大范围指令前建议先收窄条件。
108
+ - 指令与下载链路**并列且互不干扰**:走 `/events` 的 `zotero[]` 增量(1.5s 轮询)、按 `commandId` 幂等、10 分钟窗口、任务 tab 没打开时先自动拉起;弹窗开着时后续指令排队,关闭后串行消费。手动同步的交互零变化。
109
+ - 同样**需重启宿主或重新加载插件**,新工具才会注册生效。
30
110
 
31
111
  ## 搜索任务与轮次
32
112
 
33
- **一个研究主题 = 一个任务 = 一个右侧栏 tab;一个对话只保留一个任务 tab。** 一次检索不满意时,在同一个任务里调整条件再搜一轮,而不是新开一个任务:
113
+ > 这一节解释「任务 / 轮次」背后的规则,主要给想了解细节或做调试的人看。日常使用只需记住:**一个研究主题 = 一个任务 = 一个右侧栏 tab;一个对话只保留一个任务 tab。** 一次检索不满意时,在同一个任务里调整条件再搜一轮,而不是新开一个任务。
114
+
115
+ 具体规则:
34
116
 
35
117
  - 模型第一次检索会新建任务并用 `title` 自己命名;后续检索默认复用「当前任务」,在同一任务内开新一轮,任务 id 与 tab 都不变。模型也可以显式传 `taskId` 复用指定任务,或在用户明确要求时传 `newTask: true` 新建。
36
118
  - **每个对话在右侧栏只开一个任务 tab。** 切换任务从任务列表(主页 / 任务页右上角)进行:点开另一个任务时,会原位替换当前任务 tab,而不是并排再开一个。切回某个对话时,面板里打开的那个任务就是该对话当前的任务。
37
119
  - **建议不同的检索任务使用不同对话。** 一个对话跟进一个任务,上下文和任务一一对应,模型不用猜「现在在改哪个任务」;要在一个新对话里续接已有任务,点任务页的「加入对话」把任务 id 与当前条件写进草稿即可。
38
120
  - 模型在执行检索或任务操作前,可先调用 `literature_task`(`action=current`)读取这个对话当前的任务:标题、id、状态、检索式、完整条件、轮次与筛读漏斗。没有任务时会明确返回「当前没有任务」,避免误建新任务。
39
- - 每次执行记为一条 `SearchRound`:时间、检索条件快照、发起方(模型 / 面板)、原始与去重命中数、已加载篇数、结束状态。**只记元数据,不保存文献列表**;当前结果始终是最新一轮,已标过的已读 / 纳入 / 排除 / 备注按 paperId 跨轮保留。每任务最多保留最近 30 轮,落盘时自动裁剪。
121
+ - 每次执行记为一条 `SearchRound`:时间、检索条件快照、发起方(模型 / 面板)、原始与去重命中数、已加载篇数、结束状态。**只记元数据,不保存文献列表**;当前结果始终是最新一轮,已标过的已读 / 纳入 / 排除按 paperId 跨轮保留。每任务最多保留最近 30 轮,落盘时自动裁剪。
40
122
  - 轮次可通过任务页右上角历史图标(时间轴)查看,也可用 `literature_task rounds` 或 `GET /tasks/:id/rounds` 读取。
41
123
  - **每轮结束模型只给「如何完善检索」的建议,不会自行发起下一轮**:工具描述与每次返回尾句都写死这条约束,`literature_task run` 这条旁路同样受限。用户看过结果后,在右侧栏改条件重跑、或在对话里明确要求调整,才进入下一轮。
42
124
 
@@ -58,21 +140,23 @@ dsh plugin --profile <profile> add dsh-plugin-litsearch
58
140
 
59
141
  | 字段 | 说明 | 默认 |
60
142
  |---|---|---|
61
- | `contactEmail` | 联系邮箱,作为 OpenAlex / Crossref / NCBI 的 polite pool 标识,留空也可用 | 空 |
143
+ | `contactEmail` | 联系邮箱,作为 OpenAlex / Crossref / NCBI 的 polite pool 标识;**填写后还会启用 Unpaywall**,按 DOI 补全开放获取 PDF 地址,明显提升批量下载成功率 | 空 |
62
144
  | `ncbiApiKey` | 可选,PubMed E-utilities 额度 3 → 10 次/秒;界面不回显 | 空 |
63
145
  | `s2ApiKey` | 可选,Semantic Scholar 配额;界面不回显 | 空 |
146
+ | `serpApiKey` | 可选,启用「谷歌学术」来源(SerpAPI 每月 250 次免费额度,超出需付费);留空时该来源不可选且默认关闭 | 空 |
64
147
  | `pubmedIntervalMs` / `s2IntervalMs` / `arxivIntervalMs` | 各源最小请求间隔 | 350 / 3000 / 3000 |
65
148
  | `defaultPageSize` | 每个来源默认取多少条 | 20 |
66
149
  | `requestTimeoutMs` | 单次上游请求超时(毫秒);Europe PMC 检索另有 25000 下限 | 20000 |
67
- | `defaultSources` / `defaultSort` / `defaultLanguage` | 新任务默认勾选的数据源、排序与语言 | 全部 / 相关度 / 不限 |
68
- | `autoOpenTaskTab` / `maxTaskTabs` | 模型发起检索后自动打开 / 聚焦任务 tab;每个对话只保留一个任务 tab,打开新任务会替换旧的。`maxTaskTabs` 为自动打开的兜底上限(按最近真实打开过的 tab 计数,30 分钟后释放名额) | 开 / 5 |
150
+ | `defaultSources` / `defaultSort` / `defaultLanguage` | 新任务默认勾选的数据源、排序与语言;设置页按来源分组,可逐个开关(至少保留一个) | 七个免费源 / 相关度 / 不限 |
151
+ | `autoOpenTaskTab` / `maxTaskTabs` | 模型发起检索**或对话里下发批量下载 / 存入 Zotero 指令**后自动打开 / 聚焦任务 tab;每个对话只保留一个任务 tab,打开新任务会替换旧的。`maxTaskTabs` 为自动打开的兜底上限(按最近真实打开过的 tab 计数,30 分钟后释放名额) | 开 / 5 |
69
152
  | `taskRetentionDays` / `taskRetentionCount` / `taskMaxPapers` | 未保留任务保留天数、保留条数、单任务结果上限(在任务列表中可标记「保留」跳过清理) | 90 / 120 / 1500 |
70
153
  | `pdfRoot` / `pdfMaxMb` / `autoDownloadPdf` | PDF 保存目录、单文件上限(MB)、检索完成后自动下载;`pdfRoot` 留空时落在当前会话工作目录下的 `dsh-litsearch/` | 跟随工作目录 / 30 / 关 |
154
+ | `pdfCacheMaxMb` | 内部 PDF 去重缓存总量上限(MB),超出时从最旧文件开始清理;0 表示不自动清理 | 512 |
71
155
  | `enableEpmcFulltext` | 没有 PDF 时用 Europe PMC JATS 全文兜底为可读 HTML | 开 |
72
156
  | `zoteroEnabled` / `zoteroBaseUrl` | 启用 Zotero 同步及 connector 地址(只允许本机回环) | 开 / `http://127.0.0.1:23119` |
73
157
  | `zoteroAttachPdf` / `zoteroTag` | 同步时附带 PDF、附加标签 | 开 / `dsh-litsearch` |
74
158
 
75
- 设置页还提供诊断与清理:逐源查看最近成功 / 失败与限流冷却并做探活;测试 PDF 下载目录可写性;测试 Zotero connector 连接并读取当前选中分类;按任务 / PDF 缓存 / 全部三种范围清理文件。
159
+ 设置页按数据源分成一组组,每组内含开关、可选密钥、限流参数与探活按钮;关闭的来源不再作为新检索的默认来源。另有诊断与清理:逐源查看最近成功 / 失败与限流冷却;测试 PDF 下载目录可写性;测试 Zotero connector 连接并读取当前选中分类;按任务 / PDF 缓存 / 全部三种范围清理文件。
76
160
 
77
161
  ## 数据目录
78
162
 
@@ -81,15 +165,16 @@ dsh plugin --profile <profile> add dsh-plugin-litsearch
81
165
  ```
82
166
  $DSH_HOME/storages/dsh-plugin-litsearch/
83
167
  ├── tasks/index.json # 任务摘要索引(缺失时按任务文件重建)
84
- ├── tasks/<taskId>.json # 单任务全量元数据:条件、轮次、结果、分组、筛读状态、备注
168
+ ├── tasks/<taskId>.json # 单任务全量元数据:条件、轮次、结果、分组、筛读状态
85
169
  ├── current.json # 当前任务绑定
86
170
  ├── out/<taskId>/ # CSV / RIS / BibTeX / Markdown 导出
87
- └── pdfs/<sha256>.pdf # PDF 去重缓存
171
+ └── pdfs/<sha256>.pdf # PDF 去重缓存(受 pdfCacheMaxMb 约束)
88
172
  ```
89
173
 
90
174
  - `DSH_HOME` 由宿主注入,未设置时回落 `~/.dsh`;设置页「存储目录」展示当前实际生效的路径。
91
- - 旧版本的数据写在 `~/.dsh/dsh-litsearch/`。首次启动时若新目录为空而旧目录有任务,会把任务、导出文件与缓存**复制**到新目录并重建索引,**旧目录原样保留作备份**;新目录已有任务时不再迁移,复制失败则暂时回退到旧目录读取,不会丢标注。
175
+ - 旧版本的数据写在 `~/.dsh/dsh-litsearch/`。首次启动时若新目录为空而旧目录有任务,会把任务、导出文件与缓存**复制**到新目录并重建索引,**旧目录原样保留作备份**;新目录已有任务时不再迁移,复制失败则暂时回退到旧目录读取,不会丢筛读状态。
92
176
  - **PDF 可读文件不在这里**:它由 `pdfRoot` 决定;未显式配置时落在当前会话工作目录下的 `dsh-litsearch/`(读不到工作目录时才回落到旧默认 `~/Downloads/dsh-litsearch`)。任务里记录的 `pdfFile` 是该目录下的绝对路径,升级不影响已下载的文件;只有内部去重缓存 `pdfs/` 随任务目录一起搬迁。
177
+ - **视图快照与下载指令不落盘**:「当前页 / 当前筛选」快照(30 分钟过期)和模型下发的下载指令(10 分钟窗口)只留在宿主内存里,不写进上面的目录,也不外发;宿主重启后归零。
93
178
 
94
179
  ## Zotero 同步
95
180
 
@@ -97,15 +182,19 @@ $DSH_HOME/storages/dsh-plugin-litsearch/
97
182
 
98
183
  1. 启动 Zotero 桌面端,保持运行;
99
184
  2. 在「设置 → 高级」中允许其他应用与本机通信(若该版本提供此开关);
100
- 3. 如有需要,先在 Zotero 里选中目标分类——未选中时条目会保存到「我的文库」。
185
+ 3. 勾选文献(或单篇卡片)点「存入 Zotero」,弹窗里**先选目标目录**再点「同步」。目录列表直接来自本机 Zotero,会标出「当前选中」和「不可写附件」的目录,不必事先在 Zotero 里选中分类;没有可写目录时会提示先在 Zotero 中新建分类。
186
+
187
+ 同步以 5 并发逐篇进行,弹窗实时显示每篇的待同步 / 同步中 / 已同步 / 失败,可随时取消(已发出的请求仍会完成)。写入条目元数据、分组名(作为标签)与可选 PDF 附件;连接失败、目录不可写附件、单篇条目或附件失败都会给出具体原因,目录只读时只保存条目、不尝试上传 PDF。重新同步会在 Zotero 里新建条目,**不会覆盖已有条目**,也不会跳过已同步的文献。同步失败或部分失败时,任务页会保留一条 RIS 导出入口(只含本次同步的条目),可先导出备用。
101
188
 
102
- 同步会写入条目元数据、标签与可选 PDF 附件。连接失败、当前分类不可写附件、单篇条目或附件失败都会在界面给出对应提示;分类只读时只保存条目,不尝试上传 PDF。同步失败或部分失败时,任务页会保留一条 RIS 导出入口(只含本次同步的条目),可先导出备用。
189
+ 在对话里说一句也能触发同样的同步,且支持在话术里直接点名目录(点名且唯一匹配时自动预选并开跑);详见「在对话里批量存入 Zotero」。目录名称的匹配在插件后台完成,不需要先在 Zotero 里选中分类。
103
190
 
104
191
  ## 隐私
105
192
 
106
193
  - 检索词会发送给所选数据源(PubMed、Europe PMC、OpenAlex、Crossref、Semantic Scholar、arXiv、PubTator)的公开 API。
107
- - PDF 下载与 Zotero 连接都只在本机进行;Zotero connector 仅允许回环地址。
108
- - API Key 只保存在本机配置文件(明文)中,界面不回显、不写日志。
194
+ - 配置了 `contactEmail` 后,下载 PDF 会用 DOI 和该邮箱查询 Unpaywall(`api.unpaywall.org`)以寻找开放获取副本;不配置则不发这项请求。
195
+ - 启用「谷歌学术」时,检索词会经第三方服务 SerpAPI 转发,并受其使用条款与额度约束;该来源默认关闭。
196
+ - PDF 下载与 Zotero 连接都只在本机进行;Zotero connector 仅允许回环地址。右侧栏的视图快照与模型下载 / 同步指令也只在宿主内存里传递,不落盘、不上传。
197
+ - 各类 API Key(含 `serpApiKey`)只保存在本机配置文件(明文)中,界面不回显、不写日志。
109
198
 
110
199
  ## 本地开发
111
200
 
@@ -117,6 +206,8 @@ pnpm typecheck:client
117
206
  pnpm test # node:test 单元测试(test/**/*.test.ts)
118
207
  ```
119
208
 
209
+ 下载 / Zotero 指令链路另有专项单测:`test/download-scope.test.ts`(范围解析、无效 id 过滤、当前页顺序、按页切分与页内再筛、页码越界)、`download-events.test.ts`(事件增量与裁剪 / 重置)、`download-store.test.ts`(指令幂等与会话隔离)、`zotero-command.test.ts`(目录匹配歧义、指令幂等、事件增量)。
210
+
120
211
  构建后用独立的本地 profile 启动调试:
121
212
 
122
213
  ```bash
@@ -129,12 +220,32 @@ dsh --profile litsearch
129
220
  ## 架构
130
221
 
131
222
  - `src/search/`:数据源无关的检索层(类型、聚合、身份合并、查询适配、限流、健康状态)。
132
- - `src/search/providers/`:每家免费 API 一个 provider,新增数据源只需实现 `Provider` 并注册。
133
- - `src/host/`:`/dsh-litsearch/api/*` 端点、任务执行 / 持久化 / 统计 / 导出、模型侧工具注册。
223
+ - `src/search/providers/`:每家 API 一个 provider,新增数据源只需实现 `Provider` 并注册;其中 `googlescholar` 为需自备密钥的可选源。
224
+ - `src/host/`:`/dsh-litsearch/api/*` 端点、任务执行 / 持久化 / 统计 / 导出,以及七个模型工具(`literature_search` / `literature_sources` / `literature_task` / `literature_scope` / `literature_export` / `literature_download` / `literature_zotero`)的注册。
225
+ - `src/host/tasks/download.ts`:把模型的自然语言范围(7 种 `scope`,含按页取数的 `pages` + 复用 `ResultsQuery` 的筛选字段)解析成已校验的 paperId 清单与中文范围说明;复用 `filterPapers`,不自造筛选 DSL。下载与 Zotero 两个工具共用 `tools.ts` 里的 `resolveModelScope()`,不会出现两套筛选口径。
134
226
  - `src/host/paths.ts`:`$DSH_HOME` 解析、统一存储区路径、旧目录一次性迁移与失败回退。
135
- - `src/host/pdf/`:PDF 候选链、SSRF 防护的抓取与本地缓存、可读文件名、Europe PMC 全文兜底。
136
- - `src/host/zotero/`:本机 Zotero connector 协议封装(探测、保存条目、上传附件)。
137
- - `src/client/`:右侧栏检索面板、任务 tab(结果 / 概览 / 分组 / 笔记 / 对比 / 全文)、独立论文 tab 与设置页。
227
+ - `src/host/pdf/`:PDF 候选链、SSRF 防护的抓取与本地缓存、可读文件名、Europe PMC 全文兜底、下载失败原因归类。
228
+ - `src/host/zotero/`:本机 Zotero connector 协议封装(探测、目录列表、保存条目、上传附件),以及 `match.ts`(把对话里点名的目录唯一匹配到真实目标,歧义返回候选)。
229
+ - `src/client/`:右侧栏检索面板、任务 tab(结果 / 概览 / 分组 / 笔记 / 对比 / 全文)、独立论文 tab 与设置页;`DownloadDialog.tsx` 是批量下载进度弹窗,`ZoteroSyncDialog.tsx` 是选目录 + 逐篇同步弹窗(模型发起时支持预选目录并自动开跑)。
230
+ - `src/client/downloadCommandStore.ts` / `zoteroCommandStore.ts`:两类模型指令各自的幂等去重与待消费队列(按 `commandId`,10 分钟窗口),彼此独立、互不覆盖。
231
+
232
+ ### 模型 → 右侧栏的指令链路
233
+
234
+ Host 没有原生推送,两侧靠 `/events` 增量轮询联动:
235
+
236
+ ```text
237
+ 右侧栏 POST /view(400ms 节流上报当前页 / 筛选 / 选中)
238
+ → Host 按 sessionId 存内存 Map(30 分钟过期)
239
+ → 模型调 literature_download / literature_zotero:读快照 + 服务端筛选算出 paperId 清单
240
+ (Zotero 额外读本机目录列表,点名了目录就唯一匹配,歧义/找不到一律不下发)
241
+ → publishDownload / publishZotero 写入各自日志并推进 rev(各最多保留 50 条)
242
+ → 右侧栏 GET /events?sinceRev=…(1.5s 轮询)拿到 downloads[] / zotero[]
243
+ → downloadCommandStore / zoteroCommandStore 按 commandId 去重、排队
244
+ → TaskPane 消费 → startBatchDownload(5 并发)→ DownloadDialog
245
+ → ZoteroSyncDialog(选目录;已指定目录则自动开跑)
246
+ ```
247
+
248
+ 两个工具都同时 `focusTask` 拉起任务 tab;tab 没挂载时指令在 store 里等待,不会丢失。快照、指令、事件日志都在内存里,宿主重启后归零,不重放历史指令。
138
249
 
139
250
  ## 路线图
140
251
 
@@ -143,7 +254,7 @@ dsh --profile litsearch
143
254
  - [x] Semantic Scholar
144
255
  - [x] 多检索式与跨式去重
145
256
  - [x] 结果筛选、排序与概览统计
146
- - [x] 筛读、分组、备注与任务对比
257
+ - [x] 筛读、分组与任务对比
147
258
  - [x] 续页加载与导出(CSV / RIS / BibTeX / Markdown)
148
259
  - [x] PDF 预览 / 下载、Europe PMC 全文兜底、全文台账
149
260
  - [x] Zotero 同步与批量操作
@@ -151,6 +262,12 @@ dsh --profile litsearch
151
262
  - [x] 独立论文页、PubTator 实体与数据源语法提示
152
263
  - [x] 对比视图整批纳入、Zotero 失败回退 RIS
153
264
  - [x] Unpaywall(按 DOI 补全开放获取全文地址;需配置 `contactEmail`)
265
+ - [x] 谷歌学术(可选源,需自备 SerpAPI Key)与设置页数据源开关
266
+ - [x] 批量下载进度弹窗、跨页选中累加、下载与 Zotero 同步 5 并发
267
+ - [x] PDF 候选补全与失败归类(403 / 限流 / 人机验证 / HTML 页 / 无候选)
268
+ - [x] Zotero 同步前选择目标目录与逐篇结果
269
+ - [x] 对话里自然语言触发批量下载(视图快照 + 已校验 paperId 清单)
270
+ - [x] 对话里自然语言触发批量存入 Zotero(共用范围解析 + 目录唯一匹配 + 自动开跑)
154
271
  - [ ] DOAJ
155
272
 
156
273
  ## License