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
@@ -153,19 +153,33 @@ export interface TaskConditions {
153
153
  pageSize: number;
154
154
  }
155
155
  /** 全文获取状态;`unknown` 表示尚未尝试。 */
156
- export type PdfStatus = 'unknown' | 'available' | 'downloaded' | 'html-only' | 'paywalled' | 'blocked' | 'failed';
156
+ export type PdfStatus = 'unknown' | 'available' | 'downloaded' | 'missing' | 'html-only' | 'paywalled' | 'blocked' | 'failed';
157
+ /**
158
+ * PDF 下载失败的根因分类。
159
+ *
160
+ * 除了 `failed`(网络/超时/体积等程序故障)之外,其余都属于「后台拿不到、
161
+ * 但浏览器可能能打开」,界面按此分流,避免把订阅受限误报成功能失败。
162
+ */
163
+ export type PdfFailureReason = 'no-candidate' | 'blocked' | 'paywalled' | 'not-pdf' | 'failed';
164
+ /** 是否需要引导用户到浏览器手动保存。 */
165
+ export declare function isManualPdfFailure(reason: PdfFailureReason | undefined): boolean;
166
+ /** 文献 → Zotero 的同步状态;缺省表示尚未同步。 */
167
+ export type ZoteroSyncStatus = 'synced' | 'failed';
157
168
  /** 论文级人工筛选状态。 */
158
169
  export interface PaperState {
159
170
  screening: Screening;
160
171
  groupId?: string;
161
- shortlistOrder?: number;
162
- note?: string;
163
- tags?: string[];
164
172
  pdfStatus?: PdfStatus;
165
173
  /** 失败原因(面向用户的一句话)。 */
166
174
  pdfError?: string;
167
175
  /** 已下载到用户目录的绝对路径。 */
168
176
  pdfFile?: string;
177
+ /** 最近一次 Zotero 同步结果;缺省为未同步。 */
178
+ zoteroStatus?: ZoteroSyncStatus;
179
+ /** 同步到的 Zotero 目录名,便于列表直接展示。 */
180
+ zoteroCollection?: string;
181
+ /** 同步失败原因(面向用户的一句话)。 */
182
+ zoteroError?: string;
169
183
  }
170
184
  export interface TaskGroup {
171
185
  id: string;
@@ -194,7 +208,7 @@ export interface TaskResult {
194
208
  * 任务内一次检索(一轮)的元数据快照。
195
209
  *
196
210
  * 只留「这一轮用什么条件、什么时候跑、跑出多少、结果如何」,**不保存文献列表**:
197
- * 当前结果永远是最新一轮的 `result.papers`,人工标注按 paperId 跨轮保留。
211
+ * 当前结果永远是最新一轮的 `result.papers`,筛读状态按 paperId 跨轮保留。
198
212
  * 历史轮次用于时间轴回看与「回填条件重跑」。
199
213
  */
200
214
  export interface SearchRound {
@@ -281,6 +295,8 @@ export interface TaskDetail extends TaskSummary {
281
295
  states: Record<string, PaperState>;
282
296
  /** 建立任务时继承筛读状态的来源任务。 */
283
297
  fromTaskId?: string;
298
+ /** PDF 实际保存目录(供面板悬停提示)。 */
299
+ downloadDir?: string;
284
300
  result: {
285
301
  totalLoaded: number;
286
302
  sources: SourceStatus[];
@@ -377,6 +393,7 @@ export interface FullTextCoverage {
377
393
  unknown: number;
378
394
  available: number;
379
395
  downloaded: number;
396
+ missing: number;
380
397
  htmlOnly: number;
381
398
  paywalled: number;
382
399
  blocked: number;
@@ -388,10 +405,6 @@ export interface AnnotationInput {
388
405
  screening?: Screening;
389
406
  /** `null` 表示移出分组。 */
390
407
  groupId?: string | null;
391
- /** `null` 表示清除必读顺序。 */
392
- shortlistOrder?: number | null;
393
- note?: string;
394
- tags?: string[];
395
408
  /** 全文状态由 Host 下载/同步流程写入,面板也可显示与筛选。 */
396
409
  pdfStatus?: PdfStatus;
397
410
  pdfError?: string;
@@ -407,6 +420,8 @@ export interface PdfDownloadResult {
407
420
  path?: string;
408
421
  url?: string;
409
422
  error?: string;
423
+ /** 失败根因;成功时缺省。 */
424
+ reason?: PdfFailureReason;
410
425
  }
411
426
  /** Zotero connector 探测结果。 */
412
427
  export interface ZoteroProbeResult {
@@ -420,6 +435,32 @@ export interface ZoteroProbeResult {
420
435
  };
421
436
  error?: string;
422
437
  }
438
+ /** Zotero 可写入的目标(库或分类),id 为 connector 的 treeViewID。 */
439
+ export interface ZoteroTarget {
440
+ id: string;
441
+ name: string;
442
+ level: number;
443
+ filesEditable?: boolean;
444
+ recent?: boolean;
445
+ }
446
+ /** Zotero 目标目录列表。 */
447
+ export interface ZoteroTargetsResult {
448
+ ok: boolean;
449
+ targets: ZoteroTarget[];
450
+ /** 当前 Zotero 里选中的目标。 */
451
+ current?: {
452
+ id: string;
453
+ name: string;
454
+ };
455
+ error?: string;
456
+ }
457
+ /** 单篇同步结果。 */
458
+ export interface ZoteroSyncItemResult {
459
+ paperId: string;
460
+ ok: boolean;
461
+ attached: boolean;
462
+ error?: string;
463
+ }
423
464
  /** Zotero 批量同步结果。 */
424
465
  export interface ZoteroSyncResult {
425
466
  ok: boolean;
@@ -427,6 +468,7 @@ export interface ZoteroSyncResult {
427
468
  attached: number;
428
469
  failed: number;
429
470
  messages: string[];
471
+ items: ZoteroSyncItemResult[];
430
472
  }
431
473
  /** 下载目录可写性测试结果。 */
432
474
  export interface StorageProbeResult {
@@ -448,6 +490,8 @@ export interface PaperDetail {
448
490
  paper: Paper;
449
491
  state?: PaperState;
450
492
  groups: TaskGroup[];
493
+ /** PDF 实际保存目录(供面板悬停提示)。 */
494
+ downloadDir?: string;
451
495
  }
452
496
  /** PubTator 标注的单个实体(按标识符与原文去重)。 */
453
497
  export interface PaperEntity {
@@ -532,12 +576,113 @@ export interface TaskFocus {
532
576
  /** 发起检索的会话;客户端只在该会话可见时打开,避免抢占其他对话的面板。 */
533
577
  sessionId?: string;
534
578
  }
579
+ /**
580
+ * 结果列表每页篇数。
581
+ *
582
+ * 面板分页与模型侧「第 N 页」共用这一个常量:它是**界面翻页**的粒度,
583
+ * 与任务条件的 `pageSize`(每源条数,可调 1–50)不是一回事,两者不可混用。
584
+ */
585
+ export declare const RESULT_PAGE_SIZE = 20;
586
+ /** 模型侧下载范围;工具把自然语言收敛成这 7 种之一。 */
587
+ export type DownloadScope = 'ids' | 'currentPage' | 'currentView' | 'pages' | 'all' | 'group' | 'filters';
588
+ /**
589
+ * 按页取数时页码的基准列表。
590
+ *
591
+ * - `view`(默认):先按面板视图(筛选 + 排序)切页,再在页内套模型附加条件。
592
+ * 「第二页里有 PDF 的文献」就是这个口径:用户看到的是第 2 页那 20 条。
593
+ * - `filtered`:先把附加条件合并进筛选再切页。
594
+ * 「有 PDF 的文献的第 2 页」用这个。
595
+ */
596
+ export type PageBase = 'view' | 'filtered';
597
+ /**
598
+ * 客户端当前视图快照。
599
+ *
600
+ * 模型工具跑在 Host 侧,看不到右侧栏翻到第几页、用了什么筛选。面板按变化
601
+ * 节流上报这份快照,Host 才能把「当前页」「当前筛选全部」算准。仅存内存,
602
+ * 宿主重启即失效(与事件日志 rev 一致)。
603
+ */
604
+ export interface ViewSnapshot {
605
+ sessionId: string;
606
+ taskId: string;
607
+ /** 上报时间(epoch ms);Host 只接受 30 分钟内的快照。 */
608
+ at: number;
609
+ pageIndex: number;
610
+ pageSize: number;
611
+ /** 客户端看到的结果总数;给出「共 N 页」时用它,缺省则由 Host 按任务重算。 */
612
+ total?: number;
613
+ /** 当前结果排序;快照缺 paperIds 时用它重算当前页。 */
614
+ sort?: SortMode;
615
+ /** 当前生效的筛选;键名沿用 ResultsQuery,值已去除 undefined / 空串。 */
616
+ filters: Record<string, string | number | boolean>;
617
+ /** 当前页按用户所见顺序的 paperId。 */
618
+ pagePaperIds: string[];
619
+ /** 跨页累计勾选的 paperId。 */
620
+ selectedIds: string[];
621
+ /** 客户端看到的任务 rev;每下载一篇都会自增,故只做宽松校验。 */
622
+ rev: number;
623
+ }
624
+ /** 一条待下载文献的展示元数据;由 Host 生成,Client 无需再查详情。 */
625
+ export interface DownloadCommandItem {
626
+ paperId: string;
627
+ title: string;
628
+ source: string;
629
+ /** `期刊 · 年份` 之类的副行,可缺省。 */
630
+ meta?: string;
631
+ }
632
+ /**
633
+ * 模型发起的批量下载指令。
634
+ *
635
+ * 范围在 Host 侧已解析、逐个校验完毕;右侧栏只按 paperIds 执行,不解析
636
+ * 任何条件,也不反查别的任务。弹窗靠 scopeLabel / reason / items 让用户
637
+ * 看清「大模型正在下载哪些文献」。
638
+ */
639
+ export interface DownloadCommand {
640
+ commandId: string;
641
+ taskId: string;
642
+ sessionId?: string;
643
+ at: number;
644
+ origin: 'model';
645
+ /** 用户原话 / 模型对范围的解释,弹窗里展示。 */
646
+ reason: string;
647
+ /** Host 生成的中文范围说明(含篇数),Client 纯展示。 */
648
+ scopeLabel: string;
649
+ paperIds: string[];
650
+ items: DownloadCommandItem[];
651
+ }
652
+ /**
653
+ * 模型发起的批量存入 Zotero 指令。
654
+ *
655
+ * 与下载指令共用同一套范围解析与 `items` 展示元数据;多出来的只有目标目录:
656
+ * 用户在对话里点名了目录且 Host 匹配成功时带上 `target` 并 `autoStart`,
657
+ * 否则弹窗加载好文献后由用户自己选目录再点「同步」。
658
+ */
659
+ export interface ZoteroCommand {
660
+ commandId: string;
661
+ taskId: string;
662
+ sessionId?: string;
663
+ at: number;
664
+ origin: 'model';
665
+ /** 用户原话 / 模型对范围的解释,弹窗里展示。 */
666
+ reason: string;
667
+ /** Host 生成的中文范围说明(含篇数),Client 纯展示。 */
668
+ scopeLabel: string;
669
+ paperIds: string[];
670
+ items: DownloadCommandItem[];
671
+ /** 对话里点名、且已在 Zotero 目录列表里核实过的目标目录。 */
672
+ target?: ZoteroTarget;
673
+ /** true = 目录已确定,弹窗加载完立即自动同步;false = 等用户选目录。 */
674
+ autoStart: boolean;
675
+ }
535
676
  /** `/events` 的响应;`reset` 表示客户端的 sinceRev 比服务端新(宿主重启)。 */
536
677
  export interface EventsPage {
537
678
  rev: number;
538
679
  reset: boolean;
539
680
  tasks: TaskSummary[];
540
681
  focus?: TaskFocus;
682
+ /** 增量下发的模型下载指令;数组而非单值,避免两次轮询间连发时丢指令。 */
683
+ downloads?: DownloadCommand[];
684
+ /** 增量下发的模型 Zotero 同步指令,与 downloads 并列、互不影响。 */
685
+ zotero?: ZoteroCommand[];
541
686
  }
542
687
  /** `/health` 的响应:数据源能力 + 客户端需要的默认设置(不含密钥)。 */
543
688
  export interface HealthInfo {
@@ -573,7 +718,6 @@ export interface HealthInfo {
573
718
  };
574
719
  /** 任务文件存放目录(设置页只读展示)。 */
575
720
  storageDir: string;
576
- version: string;
577
721
  }
578
722
  /** Host HTTP 端点统一信封。 */
579
723
  export type ApiEnvelope<T> = {
@@ -33,3 +33,14 @@ export const TASK_LANGUAGES = [
33
33
  'ita',
34
34
  'por',
35
35
  ];
36
+ /** 是否需要引导用户到浏览器手动保存。 */
37
+ export function isManualPdfFailure(reason) {
38
+ return reason !== undefined && reason !== 'failed';
39
+ }
40
+ /**
41
+ * 结果列表每页篇数。
42
+ *
43
+ * 面板分页与模型侧「第 N 页」共用这一个常量:它是**界面翻页**的粒度,
44
+ * 与任务条件的 `pageSize`(每源条数,可调 1–50)不是一回事,两者不可混用。
45
+ */
46
+ export const RESULT_PAGE_SIZE = 20;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-litsearch",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Search free scholarly literature sources from DSH. The model can search OpenAlex, Crossref and more with native tools, while you can run the same searches in a sidebar panel and send any paper into the conversation.",
5
5
  "keywords": [
6
6
  "dsh",