dr-claw 1.1.2

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 (287) hide show
  1. package/LICENSE +1380 -0
  2. package/NOTICE +17 -0
  3. package/README.md +875 -0
  4. package/README.zh-CN.md +857 -0
  5. package/dist/api-docs.html +878 -0
  6. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  7. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  8. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  10. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  11. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  12. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  13. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  14. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  16. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  17. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  18. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  19. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  20. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  21. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  22. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  23. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  24. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  25. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  26. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  27. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  28. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  29. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  30. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  31. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  32. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  33. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  34. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  35. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  36. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  37. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  38. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  44. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  45. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  46. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  47. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  48. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  49. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  50. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  51. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  52. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  53. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  54. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  55. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  56. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  57. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  58. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  59. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  60. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  61. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  62. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  63. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  64. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  65. package/dist/assets/_basePickBy-C0n1uo4u.js +1 -0
  66. package/dist/assets/_baseUniq-BRNmhFkc.js +1 -0
  67. package/dist/assets/arc-DOMBmC_A.js +1 -0
  68. package/dist/assets/architectureDiagram-VXUJARFQ-CtMWglbD.js +36 -0
  69. package/dist/assets/blockDiagram-VD42YOAC-DK_gTkJy.js +122 -0
  70. package/dist/assets/c4Diagram-YG6GDRKO-DrDAvsn7.js +10 -0
  71. package/dist/assets/channel-BFOE7OyC.js +1 -0
  72. package/dist/assets/chunk-4BX2VUAB-CGbhdZcN.js +1 -0
  73. package/dist/assets/chunk-55IACEB6-sGeFSxen.js +1 -0
  74. package/dist/assets/chunk-B4BG7PRW-RxgHAU_P.js +165 -0
  75. package/dist/assets/chunk-DI55MBZ5-CvHUXZrx.js +220 -0
  76. package/dist/assets/chunk-FMBD7UC4-Sn7cXVNU.js +15 -0
  77. package/dist/assets/chunk-QN33PNHL-bDKOHppN.js +1 -0
  78. package/dist/assets/chunk-QZHKN3VN-B9kN5LGm.js +1 -0
  79. package/dist/assets/chunk-TZMSLE5B-CCBe6len.js +1 -0
  80. package/dist/assets/classDiagram-2ON5EDUG-DQoMNYlY.js +1 -0
  81. package/dist/assets/classDiagram-v2-WZHVMYZB-DQoMNYlY.js +1 -0
  82. package/dist/assets/clone-BulQSblH.js +1 -0
  83. package/dist/assets/cose-bilkent-S5V4N54A-r6sLiHBW.js +1 -0
  84. package/dist/assets/cytoscape.esm-5J0xJHOV.js +321 -0
  85. package/dist/assets/dagre-6UL2VRFP-AbsaR1Oa.js +4 -0
  86. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  87. package/dist/assets/diagram-PSM6KHXK-DjCvdUyy.js +24 -0
  88. package/dist/assets/diagram-QEK2KX5R-CqiocXkH.js +43 -0
  89. package/dist/assets/diagram-S2PKOQOG-D5ALeoII.js +24 -0
  90. package/dist/assets/erDiagram-Q2GNP2WA-C1AeOsiz.js +60 -0
  91. package/dist/assets/flowDiagram-NV44I4VS-C98ve_DO.js +162 -0
  92. package/dist/assets/ganttDiagram-JELNMOA3-3zdLvcxD.js +267 -0
  93. package/dist/assets/gitGraphDiagram-V2S2FVAM-nDuDMsHh.js +65 -0
  94. package/dist/assets/graph-Blm-emkR.js +1 -0
  95. package/dist/assets/index-B8Yutsnm.css +1 -0
  96. package/dist/assets/index-Cf6gyhc3.js +654 -0
  97. package/dist/assets/infoDiagram-HS3SLOUP-CmzE3q0R.js +2 -0
  98. package/dist/assets/init-Gi6I4Gst.js +1 -0
  99. package/dist/assets/journeyDiagram-XKPGCS4Q-CYaW7LrP.js +139 -0
  100. package/dist/assets/kanban-definition-3W4ZIXB7-mkmW7p0j.js +89 -0
  101. package/dist/assets/layout-Ct2kFIId.js +1 -0
  102. package/dist/assets/linear-CKG9EwIJ.js +1 -0
  103. package/dist/assets/mermaid.core-CaKcUnnd.js +250 -0
  104. package/dist/assets/mindmap-definition-VGOIOE7T-Bct-pYin.js +68 -0
  105. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  106. package/dist/assets/pieDiagram-ADFJNKIX-C8wFD4Jr.js +30 -0
  107. package/dist/assets/quadrantDiagram-AYHSOK5B-CuiE_f46.js +7 -0
  108. package/dist/assets/requirementDiagram-UZGBJVZJ-C4v1CqU8.js +64 -0
  109. package/dist/assets/sankeyDiagram-TZEHDZUN-keAqqs-Y.js +10 -0
  110. package/dist/assets/sequenceDiagram-WL72ISMW-ClNbvKTB.js +145 -0
  111. package/dist/assets/stateDiagram-FKZM4ZOC-D8We-2vq.js +1 -0
  112. package/dist/assets/stateDiagram-v2-4FDKWEC3-BUURfjrE.js +1 -0
  113. package/dist/assets/timeline-definition-IT6M3QCI-DsvEMT5T.js +61 -0
  114. package/dist/assets/treemap-GDKQZRPO-TJrzN7Bj.js +162 -0
  115. package/dist/assets/vendor-codemirror-ansM-eig.js +33 -0
  116. package/dist/assets/vendor-react-BWF6YQQ3.js +8 -0
  117. package/dist/assets/vendor-xterm-DfcmCpbH.js +66 -0
  118. package/dist/assets/xychartDiagram-PRI3JC2R-BdATmY5F.js +7 -0
  119. package/dist/clear-cache.html +85 -0
  120. package/dist/convert-icons.md +53 -0
  121. package/dist/dr-claw.png +0 -0
  122. package/dist/favicon.png +0 -0
  123. package/dist/generate-icons.js +49 -0
  124. package/dist/icons/claude-ai-icon.svg +1 -0
  125. package/dist/icons/codex-white.svg +3 -0
  126. package/dist/icons/codex.svg +3 -0
  127. package/dist/icons/cursor-white.svg +12 -0
  128. package/dist/icons/cursor.svg +1 -0
  129. package/dist/icons/file.svg +16997 -0
  130. package/dist/icons/generate-icons.md +19 -0
  131. package/dist/icons/icon-128x128.png +0 -0
  132. package/dist/icons/icon-144x144.png +0 -0
  133. package/dist/icons/icon-152x152.png +0 -0
  134. package/dist/icons/icon-192x192.png +0 -0
  135. package/dist/icons/icon-384x384.png +0 -0
  136. package/dist/icons/icon-512x512.png +0 -0
  137. package/dist/icons/icon-72x72.png +0 -0
  138. package/dist/icons/icon-96x96.png +0 -0
  139. package/dist/icons/news/arxiv.svg +1 -0
  140. package/dist/icons/news/huggingface.svg +8 -0
  141. package/dist/icons/news/x-black.png +0 -0
  142. package/dist/icons/news/x-white.png +0 -0
  143. package/dist/icons/news/xiaohongshu.png +0 -0
  144. package/dist/index.html +51 -0
  145. package/dist/leveraged-cognition.png +0 -0
  146. package/dist/manifest.json +61 -0
  147. package/dist/screenshots/chat.png +0 -0
  148. package/dist/screenshots/new_claw.png +0 -0
  149. package/dist/screenshots/news_dashboard.png +0 -0
  150. package/dist/screenshots/project_dashboard.png +0 -0
  151. package/dist/screenshots/research_lab.png +0 -0
  152. package/dist/screenshots/skill_library.png +0 -0
  153. package/dist/screenshots/task_list.png +0 -0
  154. package/dist/screenshots/terminal_example.png +0 -0
  155. package/dist/screenshots/terminal_example1.png +0 -0
  156. package/dist/sw.js +58 -0
  157. package/dist/wechat-group-qr.jpg +0 -0
  158. package/package.json +133 -0
  159. package/public/api-docs.html +878 -0
  160. package/public/clear-cache.html +85 -0
  161. package/public/convert-icons.md +53 -0
  162. package/public/dr-claw.png +0 -0
  163. package/public/favicon.png +0 -0
  164. package/public/generate-icons.js +49 -0
  165. package/public/icons/claude-ai-icon.svg +1 -0
  166. package/public/icons/codex-white.svg +3 -0
  167. package/public/icons/codex.svg +3 -0
  168. package/public/icons/cursor-white.svg +12 -0
  169. package/public/icons/cursor.svg +1 -0
  170. package/public/icons/file.svg +16997 -0
  171. package/public/icons/generate-icons.md +19 -0
  172. package/public/icons/icon-128x128.png +0 -0
  173. package/public/icons/icon-144x144.png +0 -0
  174. package/public/icons/icon-152x152.png +0 -0
  175. package/public/icons/icon-192x192.png +0 -0
  176. package/public/icons/icon-384x384.png +0 -0
  177. package/public/icons/icon-512x512.png +0 -0
  178. package/public/icons/icon-72x72.png +0 -0
  179. package/public/icons/icon-96x96.png +0 -0
  180. package/public/icons/news/arxiv.svg +1 -0
  181. package/public/icons/news/huggingface.svg +8 -0
  182. package/public/icons/news/x-black.png +0 -0
  183. package/public/icons/news/x-white.png +0 -0
  184. package/public/icons/news/xiaohongshu.png +0 -0
  185. package/public/leveraged-cognition.png +0 -0
  186. package/public/manifest.json +61 -0
  187. package/public/screenshots/chat.png +0 -0
  188. package/public/screenshots/new_claw.png +0 -0
  189. package/public/screenshots/news_dashboard.png +0 -0
  190. package/public/screenshots/project_dashboard.png +0 -0
  191. package/public/screenshots/research_lab.png +0 -0
  192. package/public/screenshots/skill_library.png +0 -0
  193. package/public/screenshots/task_list.png +0 -0
  194. package/public/screenshots/terminal_example.png +0 -0
  195. package/public/screenshots/terminal_example1.png +0 -0
  196. package/public/sw.js +58 -0
  197. package/public/wechat-group-qr.jpg +0 -0
  198. package/scripts/export-skills-catalog-v2.mjs +746 -0
  199. package/scripts/fix-node-pty.js +45 -0
  200. package/scripts/gemini_image_core.py +470 -0
  201. package/scripts/install-python-tools.js +64 -0
  202. package/server/AGENTS.md +73 -0
  203. package/server/__tests__/gemini-legacy-thought.test.mjs +88 -0
  204. package/server/__tests__/runtimePorts.test.mjs +115 -0
  205. package/server/__tests__/session-delete.test.mjs +166 -0
  206. package/server/bin/compute-node +70 -0
  207. package/server/claude-sdk.js +799 -0
  208. package/server/cli-chat.js +488 -0
  209. package/server/cli.js +354 -0
  210. package/server/compute-node.js +439 -0
  211. package/server/constants/config.js +5 -0
  212. package/server/cursor-cli.js +340 -0
  213. package/server/database/auth.db +0 -0
  214. package/server/database/db.js +1852 -0
  215. package/server/database/init.sql +194 -0
  216. package/server/gemini-cli.js +1352 -0
  217. package/server/index.js +3154 -0
  218. package/server/load-env.js +71 -0
  219. package/server/local-gpu.js +860 -0
  220. package/server/middleware/auth.js +100 -0
  221. package/server/openai-codex.js +724 -0
  222. package/server/openrouter.js +890 -0
  223. package/server/project-token-usage.js +407 -0
  224. package/server/projects.js +3973 -0
  225. package/server/routes/agent.js +1272 -0
  226. package/server/routes/auth.js +128 -0
  227. package/server/routes/auto-research.js +558 -0
  228. package/server/routes/cli-auth.js +958 -0
  229. package/server/routes/codex.js +344 -0
  230. package/server/routes/commands.js +601 -0
  231. package/server/routes/compute.js +358 -0
  232. package/server/routes/cursor.js +795 -0
  233. package/server/routes/git.js +1348 -0
  234. package/server/routes/mcp-utils.js +48 -0
  235. package/server/routes/mcp.js +618 -0
  236. package/server/routes/news.js +711 -0
  237. package/server/routes/projects.js +633 -0
  238. package/server/routes/references.js +338 -0
  239. package/server/routes/settings.js +285 -0
  240. package/server/routes/skills.js +914 -0
  241. package/server/routes/taskmaster.js +2566 -0
  242. package/server/routes/telemetry.js +89 -0
  243. package/server/routes/user.js +155 -0
  244. package/server/scripts/fix_session_names.js +57 -0
  245. package/server/scripts/research-news/common_words.py +19 -0
  246. package/server/scripts/research-news/link_keywords.py +298 -0
  247. package/server/scripts/research-news/scan_existing_notes.py +266 -0
  248. package/server/scripts/research-news/scoring_utils.py +227 -0
  249. package/server/scripts/research-news/search_arxiv.py +842 -0
  250. package/server/scripts/research-news/search_huggingface.py +438 -0
  251. package/server/scripts/research-news/search_x.py +490 -0
  252. package/server/scripts/research-news/search_xiaohongshu.py +610 -0
  253. package/server/taskmaster-templates/README.md +150 -0
  254. package/server/taskmaster-templates/README.zh-CN.md +150 -0
  255. package/server/taskmaster-templates/ai-research-dataset.json +230 -0
  256. package/server/taskmaster-templates/ai-research-method-model.json +230 -0
  257. package/server/taskmaster-templates/ai-research-position-paper.json +230 -0
  258. package/server/telemetry.js +121 -0
  259. package/server/templates/AGENTS.md +103 -0
  260. package/server/templates/CLAUDE.md +104 -0
  261. package/server/templates/GEMINI.md +103 -0
  262. package/server/templates/cursor-project.md +108 -0
  263. package/server/templates/index.js +37 -0
  264. package/server/utils/__tests__/sessionTokenUsage.test.js +82 -0
  265. package/server/utils/auto-research-mailer.js +57 -0
  266. package/server/utils/cliResolution.js +170 -0
  267. package/server/utils/commandParser.js +303 -0
  268. package/server/utils/cursorCommand.js +61 -0
  269. package/server/utils/geminiApiKey.js +28 -0
  270. package/server/utils/gitConfig.js +24 -0
  271. package/server/utils/imageAttachmentFiles.js +35 -0
  272. package/server/utils/mcp-detector.js +198 -0
  273. package/server/utils/parsers/bibtex-parser.js +207 -0
  274. package/server/utils/permissions.js +100 -0
  275. package/server/utils/runtimePorts.js +212 -0
  276. package/server/utils/sessionFormatting.js +68 -0
  277. package/server/utils/sessionIndex.js +100 -0
  278. package/server/utils/sessionMode.js +95 -0
  279. package/server/utils/sessionTokenUsage.js +105 -0
  280. package/server/utils/taskmaster-websocket.js +129 -0
  281. package/server/utils/zotero-client.js +136 -0
  282. package/shared/errorClassifier.js +102 -0
  283. package/shared/geminiThinkingSupport.d.ts +22 -0
  284. package/shared/geminiThinkingSupport.js +96 -0
  285. package/shared/geminiThoughtParser.d.ts +8 -0
  286. package/shared/geminiThoughtParser.js +30 -0
  287. package/shared/modelConstants.js +139 -0
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 扫描现有笔记构建索引
4
+ 用于 start-my-day skill,扫描 vault 中的现有笔记并构建关键词到笔记路径的映射表
5
+ """
6
+
7
+ import os
8
+ import re
9
+ import json
10
+ import sys
11
+ import argparse
12
+ import logging
13
+ from pathlib import Path
14
+ from typing import List, Dict, Set, Tuple
15
+ import yaml
16
+
17
+ from common_words import COMMON_WORDS
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def parse_frontmatter(content: str) -> Dict:
23
+ """
24
+ 解析 frontmatter (YAML 格式)
25
+
26
+ Args:
27
+ content: markdown 文件内容
28
+
29
+ Returns:
30
+ frontmatter 字典
31
+ """
32
+ # 查找 frontmatter 开始和结束标记
33
+ frontmatch = re.match(r'^---\s*\n(.*?)^---\s*\n', content, re.MULTILINE | re.DOTALL)
34
+
35
+ if not frontmatch:
36
+ return {}
37
+
38
+ try:
39
+ frontmatter_str = frontmatch.group(1)
40
+ frontmatter_data = yaml.safe_load(frontmatter_str)
41
+ return frontmatter_data or {}
42
+ except Exception as e:
43
+ logger.warning("Error parsing frontmatter: %s", e)
44
+ return {}
45
+
46
+
47
+ def extract_keywords_from_title(title: str) -> List[str]:
48
+ """
49
+ 从标题中提取关键词
50
+
51
+ Args:
52
+ title: 论文标题
53
+
54
+ Returns:
55
+ 关键词列表
56
+ """
57
+ if not title:
58
+ return []
59
+
60
+ keywords = []
61
+
62
+ # 主要策略:提取论文的缩写或专有名词(大写开头的词)
63
+ # 例如:从 "BLIP: Bootstrapping..." 提取 "BLIP"
64
+ main_keyword = re.match(r'^([A-Z]{2,})(?:\s*:|\s+)', title)
65
+ if main_keyword:
66
+ keywords.append(main_keyword.group(1))
67
+
68
+ # 策略2:提取冒号前的完整标题(如果是缩写+冒号格式)
69
+ colon_match = title.split(':')
70
+ if len(colon_match) >= 2 and len(colon_match[0].strip()) > 2:
71
+ before_colon = colon_match[0].strip()
72
+ # 只添加长度在3-20之间的
73
+ if 3 <= len(before_colon) <= 20:
74
+ keywords.append(before_colon)
75
+
76
+ # 策略3:提取带连字符的术语(如 Vision-Language, Fine-Tuning, In-Context)
77
+ # 只匹配明确的技术术语,避免过度分割
78
+ tech_terms = re.findall(r'\b[A-Z][a-z]*(?:-[A-Z][a-z]*)+\b', title)
79
+ for term in tech_terms:
80
+ term_clean = term.strip()
81
+ # 只添加长度在3-20之间的技术术语
82
+ if 3 <= len(term_clean) <= 20:
83
+ # 过滤掉通用词
84
+ if term_clean.lower() not in COMMON_WORDS:
85
+ keywords.append(term_clean)
86
+
87
+ # 去重
88
+ keywords = list(dict.fromkeys(keywords))
89
+
90
+ return keywords
91
+
92
+
93
+ def scan_notes_directory(papers_dir: Path) -> List[Dict]:
94
+ """
95
+ 扫描 Papers 目录下的所有笔记
96
+
97
+ Args:
98
+ papers_dir: Papers 目录路径
99
+
100
+ Returns:
101
+ 笔记列表
102
+ """
103
+ notes = []
104
+
105
+ # 递归查找所有 .md 文件
106
+ for md_file in papers_dir.rglob('*.md'):
107
+ try:
108
+ with open(md_file, 'r', encoding='utf-8') as f:
109
+ content = f.read()
110
+
111
+ # 解析 frontmatter
112
+ frontmatter = parse_frontmatter(content)
113
+
114
+ # 提取信息
115
+ # 计算相对于vault的路径(使用正斜杠)
116
+ rel_path = md_file.relative_to(papers_dir.parent.parent)
117
+ note_info = {
118
+ 'path': str(rel_path).replace('\\', '/'), # 使用正斜杠
119
+ 'filename': md_file.name,
120
+ 'short_name': md_file.stem, # 文件名(不含.md扩展名),用于短链接
121
+ 'path_str': str(rel_path), # 添加路径的字符串表示,用于正确编码
122
+ 'title': frontmatter.get('title', md_file.stem),
123
+ 'tags': frontmatter.get('tags', []),
124
+ }
125
+
126
+ # 从标题提取关键词
127
+ title_keywords = extract_keywords_from_title(note_info['title'])
128
+ note_info['title_keywords'] = title_keywords
129
+
130
+ # 从 tags 提取关键词(保留有意义的tag)
131
+ tag_keywords = []
132
+ for tag in note_info['tags']:
133
+ if isinstance(tag, list):
134
+ for sub_tag in tag:
135
+ if isinstance(sub_tag, str):
136
+ # 只添加长度3-20的tag,过滤通用词
137
+ if 3 <= len(sub_tag) <= 20 and sub_tag.lower() not in COMMON_WORDS:
138
+ tag_keywords.append(sub_tag)
139
+ elif isinstance(tag, str):
140
+ if 3 <= len(tag) <= 20 and tag.lower() not in COMMON_WORDS:
141
+ tag_keywords.append(tag)
142
+
143
+ note_info['tag_keywords'] = tag_keywords
144
+
145
+ notes.append(note_info)
146
+
147
+ except Exception as e:
148
+ logger.warning("Error reading %s: %s", md_file, e)
149
+ continue
150
+
151
+ return notes
152
+
153
+
154
+ def build_keyword_index(notes: List[Dict]) -> Dict[str, List[str]]:
155
+ """
156
+ 构建关键词到笔记路径的映射表
157
+
158
+ Args:
159
+ notes: 笔记列表
160
+
161
+ Returns:
162
+ 关键词映射字典
163
+ """
164
+ keyword_index = {}
165
+
166
+ for note in notes:
167
+ # 优先使用从标题提取的关键词
168
+ for keyword in note['title_keywords']:
169
+ keyword_lower = keyword.lower()
170
+ if len(keyword_lower) >= 3 and len(keyword_lower) <= 30:
171
+ if keyword_lower not in COMMON_WORDS:
172
+ if keyword_lower not in keyword_index:
173
+ keyword_index[keyword_lower] = []
174
+ if note['path'] not in keyword_index[keyword_lower]:
175
+ keyword_index[keyword_lower].append(note['path'])
176
+
177
+ # 添加从 tags 提取的关键词
178
+ for keyword in note['tag_keywords']:
179
+ keyword_lower = keyword.lower()
180
+ if len(keyword_lower) >= 3 and len(keyword_lower) <= 30:
181
+ if keyword_lower not in COMMON_WORDS:
182
+ if keyword_lower not in keyword_index:
183
+ keyword_index[keyword_lower] = []
184
+ if note['path'] not in keyword_index[keyword_lower]:
185
+ keyword_index[keyword_lower].append(note['path'])
186
+
187
+ # 使用短名称(文件名)作为关键词,但只添加主要部分
188
+ if 'short_name' in note:
189
+ short_name = note['short_name']
190
+ # 移除版本号和常见后缀
191
+ clean_short = re.sub(r'-\d{4}\.\d{5}$', '', short_name)
192
+ clean_short = re.sub(r'-v\d+$', '', clean_short)
193
+
194
+ # 如果清理后的短名称长度合适,添加到索引
195
+ if 3 <= len(clean_short) <= 40 and clean_short.lower() not in COMMON_WORDS:
196
+ keyword_index[clean_short.lower()] = [note['path']]
197
+
198
+ return keyword_index
199
+
200
+
201
+ def main():
202
+ """主函数"""
203
+ parser = argparse.ArgumentParser(description='Scan existing notes and build keyword index')
204
+ parser.add_argument('--vault', type=str,
205
+ default=os.environ.get('OBSIDIAN_VAULT_PATH', ''),
206
+ help='Path to Obsidian vault (or set OBSIDIAN_VAULT_PATH env var)')
207
+ parser.add_argument('--output', type=str, default='existing_notes_index.json',
208
+ help='Output JSON file path')
209
+ parser.add_argument('--papers-dir', type=str,
210
+ default='20_Research/Papers',
211
+ help='Relative path to Papers directory')
212
+
213
+ args = parser.parse_args()
214
+
215
+ logging.basicConfig(
216
+ level=logging.INFO,
217
+ format='%(asctime)s [%(levelname)s] %(message)s',
218
+ datefmt='%H:%M:%S',
219
+ stream=sys.stderr,
220
+ )
221
+
222
+ if not args.vault:
223
+ logger.error("未指定 vault 路径。请通过 --vault 参数或 OBSIDIAN_VAULT_PATH 环境变量设置。")
224
+ sys.exit(1)
225
+
226
+ vault_path = Path(args.vault)
227
+ papers_dir = vault_path / args.papers_dir
228
+
229
+ if not papers_dir.exists():
230
+ logger.error("Papers directory not found: %s", papers_dir)
231
+ logger.error("Using vault path: %s", vault_path)
232
+ sys.exit(1)
233
+
234
+ logger.info("Scanning notes in: %s", papers_dir)
235
+
236
+ notes = scan_notes_directory(papers_dir)
237
+ logger.info("Found %d notes", len(notes))
238
+
239
+ keyword_index = build_keyword_index(notes)
240
+ logger.info("Built index with %d keywords", len(keyword_index))
241
+
242
+ # 准备输出
243
+ output = {
244
+ 'notes': notes,
245
+ 'keyword_to_notes': keyword_index
246
+ }
247
+
248
+ # 保存结果
249
+ with open(args.output, 'w', encoding='utf-8') as f:
250
+ json.dump(output, f, ensure_ascii=False, indent=2, default=str)
251
+
252
+ logger.info("Index saved to: %s", args.output)
253
+
254
+ logger.info("=== Keyword Index Statistics ===")
255
+ logger.info("Total notes: %d", len(notes))
256
+ logger.info("Total keywords: %d", len(keyword_index))
257
+
258
+ if len(keyword_index) > 0:
259
+ logger.info("=== Sample Keywords ===")
260
+ sample_keywords = sorted(keyword_index.items())[:10]
261
+ for keyword, paths in sample_keywords:
262
+ logger.info(" %s: %d notes", keyword, len(paths))
263
+
264
+
265
+ if __name__ == '__main__':
266
+ main()
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Shared scoring functions for paper recommendation.
4
+
5
+ Extracted from search_arxiv.py so that other modules (e.g. search_semantic_scholar)
6
+ can reuse the same scoring logic without duplication.
7
+ """
8
+
9
+ from datetime import datetime
10
+ from typing import Dict, List, Optional, Tuple
11
+
12
+ # ---------------------------------------------------------------------------
13
+ # 评分常量 —— 修改权重时只需编辑这里
14
+ # ---------------------------------------------------------------------------
15
+
16
+ # 各维度原始评分的满分值(归一化基准)
17
+ SCORE_MAX = 3.0
18
+
19
+ # 相关性评分:关键词在标题 / 摘要中匹配的加分
20
+ RELEVANCE_TITLE_KEYWORD_BOOST = 0.5
21
+ RELEVANCE_SUMMARY_KEYWORD_BOOST = 0.3
22
+ RELEVANCE_CATEGORY_MATCH_BOOST = 1.0
23
+
24
+ # 新近性阈值(天) -> 对应评分
25
+ RECENCY_THRESHOLDS = [
26
+ (30, 3.0),
27
+ (90, 2.0),
28
+ (180, 1.0),
29
+ ]
30
+ RECENCY_DEFAULT = 0.0
31
+
32
+ # 热门度:高影响力引用数归一化到 0-SCORE_MAX
33
+ # 含义:达到此引用数时视为满分
34
+ POPULARITY_INFLUENTIAL_CITATION_FULL_SCORE = 100
35
+
36
+ # 综合推荐评分权重(普通论文)
37
+ WEIGHTS_NORMAL = {
38
+ 'relevance': 0.40,
39
+ 'recency': 0.20,
40
+ 'popularity': 0.30,
41
+ 'quality': 0.10,
42
+ }
43
+ # 综合推荐评分权重(高影响力论文:提高热门度,降低新近性)
44
+ WEIGHTS_HOT = {
45
+ 'relevance': 0.35,
46
+ 'recency': 0.10,
47
+ 'popularity': 0.45,
48
+ 'quality': 0.10,
49
+ }
50
+
51
+
52
+ def calculate_relevance_score(
53
+ paper: Dict,
54
+ domains: Dict,
55
+ excluded_keywords: List[str]
56
+ ) -> Tuple[float, Optional[str], List[str]]:
57
+ """
58
+ 计算论文与研究兴趣的相关性评分
59
+
60
+ Args:
61
+ paper: 论文信息
62
+ domains: 研究领域配置
63
+ excluded_keywords: 排除关键词
64
+
65
+ Returns:
66
+ (相关性评分, 匹配的领域, 匹配的关键词列表)
67
+ """
68
+ title = paper.get('title', '').lower()
69
+ summary = paper.get('summary', '').lower() if 'summary' in paper else paper.get('abstract', '').lower()
70
+ categories = set(paper.get('categories', []))
71
+
72
+ # 检查排除关键词
73
+ for keyword in excluded_keywords:
74
+ if keyword.lower() in title or keyword.lower() in summary:
75
+ return 0, None, []
76
+
77
+ max_score = 0
78
+ best_domain = None
79
+ matched_keywords = []
80
+
81
+ # 遍历所有领域
82
+ for domain_name, domain_config in domains.items():
83
+ score = 0
84
+ domain_matched_keywords = []
85
+
86
+ # 关键词匹配
87
+ keywords = domain_config.get('keywords', [])
88
+ for keyword in keywords:
89
+ keyword_lower = keyword.lower()
90
+ if keyword_lower in title:
91
+ score += RELEVANCE_TITLE_KEYWORD_BOOST
92
+ domain_matched_keywords.append(keyword)
93
+ elif keyword_lower in summary:
94
+ score += RELEVANCE_SUMMARY_KEYWORD_BOOST
95
+ domain_matched_keywords.append(keyword)
96
+
97
+ # 类别匹配
98
+ domain_categories = domain_config.get('arxiv_categories', [])
99
+ for cat in domain_categories:
100
+ if cat in categories:
101
+ score += RELEVANCE_CATEGORY_MATCH_BOOST
102
+ domain_matched_keywords.append(cat)
103
+
104
+ if score > max_score:
105
+ max_score = score
106
+ best_domain = domain_name
107
+ matched_keywords = domain_matched_keywords
108
+
109
+ return max_score, best_domain, matched_keywords
110
+
111
+
112
+ def calculate_recency_score(published_date: Optional[datetime]) -> float:
113
+ """
114
+ 根据发布日期计算新近性评分
115
+
116
+ Args:
117
+ published_date: 发布日期
118
+
119
+ Returns:
120
+ 新近性评分 (0-3)
121
+ """
122
+ if published_date is None:
123
+ return 0
124
+
125
+ now = datetime.now(published_date.tzinfo) if published_date.tzinfo else datetime.now()
126
+ days_diff = (now - published_date).days
127
+
128
+ for max_days, score in RECENCY_THRESHOLDS:
129
+ if days_diff <= max_days:
130
+ return score
131
+ return RECENCY_DEFAULT
132
+
133
+
134
+ def calculate_quality_score(summary: str) -> float:
135
+ """
136
+ 从摘要推断质量评分
137
+
138
+ 采用更细粒度的指标:强创新词权重高于弱创新词,
139
+ 量化结果和对比实验也加分。
140
+
141
+ Args:
142
+ summary: 论文摘要
143
+
144
+ Returns:
145
+ 质量评分 (0-3)
146
+ """
147
+ score = 0.0
148
+ summary_lower = summary.lower()
149
+
150
+ strong_innovation = [
151
+ 'state-of-the-art', 'sota', 'breakthrough', 'first',
152
+ 'surpass', 'outperform', 'pioneering'
153
+ ]
154
+ weak_innovation = [
155
+ 'novel', 'propose', 'introduce', 'new approach',
156
+ 'new method', 'innovative'
157
+ ]
158
+ method_indicators = [
159
+ 'framework', 'architecture', 'algorithm', 'mechanism',
160
+ 'pipeline', 'end-to-end'
161
+ ]
162
+ quantitative_indicators = [
163
+ 'outperforms', 'improves by', 'achieves', 'accuracy',
164
+ 'f1', 'bleu', 'rouge', 'beats', 'surpasses'
165
+ ]
166
+ experiment_indicators = [
167
+ 'experiment', 'evaluation', 'benchmark', 'ablation',
168
+ 'baseline', 'comparison'
169
+ ]
170
+
171
+ strong_count = sum(1 for ind in strong_innovation if ind in summary_lower)
172
+ if strong_count >= 2:
173
+ score += 1.0
174
+ elif strong_count == 1:
175
+ score += 0.7
176
+ else:
177
+ weak_count = sum(1 for ind in weak_innovation if ind in summary_lower)
178
+ if weak_count > 0:
179
+ score += 0.3
180
+
181
+ if any(ind in summary_lower for ind in method_indicators):
182
+ score += 0.5
183
+
184
+ if any(ind in summary_lower for ind in quantitative_indicators):
185
+ score += 0.8
186
+ elif any(ind in summary_lower for ind in experiment_indicators):
187
+ score += 0.4
188
+
189
+ return min(score, SCORE_MAX)
190
+
191
+
192
+ def calculate_recommendation_score(
193
+ relevance_score: float,
194
+ recency_score: float,
195
+ popularity_score: float,
196
+ quality_score: float,
197
+ is_hot_paper: bool = False
198
+ ) -> float:
199
+ """
200
+ 计算综合推荐评分
201
+
202
+ 权重定义在模块顶部常量 WEIGHTS_NORMAL / WEIGHTS_HOT 中。
203
+ 对于高影响力论文(来自 Semantic Scholar),使用 WEIGHTS_HOT 提高热门度权重。
204
+
205
+ Args:
206
+ relevance_score: 相关性评分 (0-SCORE_MAX)
207
+ recency_score: 新近性评分 (0-SCORE_MAX)
208
+ popularity_score: 热门度评分 (0-SCORE_MAX)
209
+ quality_score: 质量评分 (0-SCORE_MAX)
210
+ is_hot_paper: 是否是高影响力论文
211
+
212
+ Returns:
213
+ 综合推荐评分 (0-10)
214
+ """
215
+ scores = {
216
+ 'relevance': relevance_score,
217
+ 'recency': recency_score,
218
+ 'popularity': popularity_score,
219
+ 'quality': quality_score,
220
+ }
221
+ # 归一化到 0-10 分
222
+ normalized = {k: (v / SCORE_MAX) * 10 for k, v in scores.items()}
223
+
224
+ weights = WEIGHTS_HOT if is_hot_paper else WEIGHTS_NORMAL
225
+ final_score = sum(normalized[k] * weights[k] for k in weights)
226
+
227
+ return round(final_score, 2)