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.
- package/LICENSE +1380 -0
- package/NOTICE +17 -0
- package/README.md +875 -0
- package/README.zh-CN.md +857 -0
- package/dist/api-docs.html +878 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/_basePickBy-C0n1uo4u.js +1 -0
- package/dist/assets/_baseUniq-BRNmhFkc.js +1 -0
- package/dist/assets/arc-DOMBmC_A.js +1 -0
- package/dist/assets/architectureDiagram-VXUJARFQ-CtMWglbD.js +36 -0
- package/dist/assets/blockDiagram-VD42YOAC-DK_gTkJy.js +122 -0
- package/dist/assets/c4Diagram-YG6GDRKO-DrDAvsn7.js +10 -0
- package/dist/assets/channel-BFOE7OyC.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-CGbhdZcN.js +1 -0
- package/dist/assets/chunk-55IACEB6-sGeFSxen.js +1 -0
- package/dist/assets/chunk-B4BG7PRW-RxgHAU_P.js +165 -0
- package/dist/assets/chunk-DI55MBZ5-CvHUXZrx.js +220 -0
- package/dist/assets/chunk-FMBD7UC4-Sn7cXVNU.js +15 -0
- package/dist/assets/chunk-QN33PNHL-bDKOHppN.js +1 -0
- package/dist/assets/chunk-QZHKN3VN-B9kN5LGm.js +1 -0
- package/dist/assets/chunk-TZMSLE5B-CCBe6len.js +1 -0
- package/dist/assets/classDiagram-2ON5EDUG-DQoMNYlY.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-DQoMNYlY.js +1 -0
- package/dist/assets/clone-BulQSblH.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-r6sLiHBW.js +1 -0
- package/dist/assets/cytoscape.esm-5J0xJHOV.js +321 -0
- package/dist/assets/dagre-6UL2VRFP-AbsaR1Oa.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-PSM6KHXK-DjCvdUyy.js +24 -0
- package/dist/assets/diagram-QEK2KX5R-CqiocXkH.js +43 -0
- package/dist/assets/diagram-S2PKOQOG-D5ALeoII.js +24 -0
- package/dist/assets/erDiagram-Q2GNP2WA-C1AeOsiz.js +60 -0
- package/dist/assets/flowDiagram-NV44I4VS-C98ve_DO.js +162 -0
- package/dist/assets/ganttDiagram-JELNMOA3-3zdLvcxD.js +267 -0
- package/dist/assets/gitGraphDiagram-V2S2FVAM-nDuDMsHh.js +65 -0
- package/dist/assets/graph-Blm-emkR.js +1 -0
- package/dist/assets/index-B8Yutsnm.css +1 -0
- package/dist/assets/index-Cf6gyhc3.js +654 -0
- package/dist/assets/infoDiagram-HS3SLOUP-CmzE3q0R.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/journeyDiagram-XKPGCS4Q-CYaW7LrP.js +139 -0
- package/dist/assets/kanban-definition-3W4ZIXB7-mkmW7p0j.js +89 -0
- package/dist/assets/layout-Ct2kFIId.js +1 -0
- package/dist/assets/linear-CKG9EwIJ.js +1 -0
- package/dist/assets/mermaid.core-CaKcUnnd.js +250 -0
- package/dist/assets/mindmap-definition-VGOIOE7T-Bct-pYin.js +68 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pieDiagram-ADFJNKIX-C8wFD4Jr.js +30 -0
- package/dist/assets/quadrantDiagram-AYHSOK5B-CuiE_f46.js +7 -0
- package/dist/assets/requirementDiagram-UZGBJVZJ-C4v1CqU8.js +64 -0
- package/dist/assets/sankeyDiagram-TZEHDZUN-keAqqs-Y.js +10 -0
- package/dist/assets/sequenceDiagram-WL72ISMW-ClNbvKTB.js +145 -0
- package/dist/assets/stateDiagram-FKZM4ZOC-D8We-2vq.js +1 -0
- package/dist/assets/stateDiagram-v2-4FDKWEC3-BUURfjrE.js +1 -0
- package/dist/assets/timeline-definition-IT6M3QCI-DsvEMT5T.js +61 -0
- package/dist/assets/treemap-GDKQZRPO-TJrzN7Bj.js +162 -0
- package/dist/assets/vendor-codemirror-ansM-eig.js +33 -0
- package/dist/assets/vendor-react-BWF6YQQ3.js +8 -0
- package/dist/assets/vendor-xterm-DfcmCpbH.js +66 -0
- package/dist/assets/xychartDiagram-PRI3JC2R-BdATmY5F.js +7 -0
- package/dist/clear-cache.html +85 -0
- package/dist/convert-icons.md +53 -0
- package/dist/dr-claw.png +0 -0
- package/dist/favicon.png +0 -0
- package/dist/generate-icons.js +49 -0
- package/dist/icons/claude-ai-icon.svg +1 -0
- package/dist/icons/codex-white.svg +3 -0
- package/dist/icons/codex.svg +3 -0
- package/dist/icons/cursor-white.svg +12 -0
- package/dist/icons/cursor.svg +1 -0
- package/dist/icons/file.svg +16997 -0
- package/dist/icons/generate-icons.md +19 -0
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/news/arxiv.svg +1 -0
- package/dist/icons/news/huggingface.svg +8 -0
- package/dist/icons/news/x-black.png +0 -0
- package/dist/icons/news/x-white.png +0 -0
- package/dist/icons/news/xiaohongshu.png +0 -0
- package/dist/index.html +51 -0
- package/dist/leveraged-cognition.png +0 -0
- package/dist/manifest.json +61 -0
- package/dist/screenshots/chat.png +0 -0
- package/dist/screenshots/new_claw.png +0 -0
- package/dist/screenshots/news_dashboard.png +0 -0
- package/dist/screenshots/project_dashboard.png +0 -0
- package/dist/screenshots/research_lab.png +0 -0
- package/dist/screenshots/skill_library.png +0 -0
- package/dist/screenshots/task_list.png +0 -0
- package/dist/screenshots/terminal_example.png +0 -0
- package/dist/screenshots/terminal_example1.png +0 -0
- package/dist/sw.js +58 -0
- package/dist/wechat-group-qr.jpg +0 -0
- package/package.json +133 -0
- package/public/api-docs.html +878 -0
- package/public/clear-cache.html +85 -0
- package/public/convert-icons.md +53 -0
- package/public/dr-claw.png +0 -0
- package/public/favicon.png +0 -0
- package/public/generate-icons.js +49 -0
- package/public/icons/claude-ai-icon.svg +1 -0
- package/public/icons/codex-white.svg +3 -0
- package/public/icons/codex.svg +3 -0
- package/public/icons/cursor-white.svg +12 -0
- package/public/icons/cursor.svg +1 -0
- package/public/icons/file.svg +16997 -0
- package/public/icons/generate-icons.md +19 -0
- package/public/icons/icon-128x128.png +0 -0
- package/public/icons/icon-144x144.png +0 -0
- package/public/icons/icon-152x152.png +0 -0
- package/public/icons/icon-192x192.png +0 -0
- package/public/icons/icon-384x384.png +0 -0
- package/public/icons/icon-512x512.png +0 -0
- package/public/icons/icon-72x72.png +0 -0
- package/public/icons/icon-96x96.png +0 -0
- package/public/icons/news/arxiv.svg +1 -0
- package/public/icons/news/huggingface.svg +8 -0
- package/public/icons/news/x-black.png +0 -0
- package/public/icons/news/x-white.png +0 -0
- package/public/icons/news/xiaohongshu.png +0 -0
- package/public/leveraged-cognition.png +0 -0
- package/public/manifest.json +61 -0
- package/public/screenshots/chat.png +0 -0
- package/public/screenshots/new_claw.png +0 -0
- package/public/screenshots/news_dashboard.png +0 -0
- package/public/screenshots/project_dashboard.png +0 -0
- package/public/screenshots/research_lab.png +0 -0
- package/public/screenshots/skill_library.png +0 -0
- package/public/screenshots/task_list.png +0 -0
- package/public/screenshots/terminal_example.png +0 -0
- package/public/screenshots/terminal_example1.png +0 -0
- package/public/sw.js +58 -0
- package/public/wechat-group-qr.jpg +0 -0
- package/scripts/export-skills-catalog-v2.mjs +746 -0
- package/scripts/fix-node-pty.js +45 -0
- package/scripts/gemini_image_core.py +470 -0
- package/scripts/install-python-tools.js +64 -0
- package/server/AGENTS.md +73 -0
- package/server/__tests__/gemini-legacy-thought.test.mjs +88 -0
- package/server/__tests__/runtimePorts.test.mjs +115 -0
- package/server/__tests__/session-delete.test.mjs +166 -0
- package/server/bin/compute-node +70 -0
- package/server/claude-sdk.js +799 -0
- package/server/cli-chat.js +488 -0
- package/server/cli.js +354 -0
- package/server/compute-node.js +439 -0
- package/server/constants/config.js +5 -0
- package/server/cursor-cli.js +340 -0
- package/server/database/auth.db +0 -0
- package/server/database/db.js +1852 -0
- package/server/database/init.sql +194 -0
- package/server/gemini-cli.js +1352 -0
- package/server/index.js +3154 -0
- package/server/load-env.js +71 -0
- package/server/local-gpu.js +860 -0
- package/server/middleware/auth.js +100 -0
- package/server/openai-codex.js +724 -0
- package/server/openrouter.js +890 -0
- package/server/project-token-usage.js +407 -0
- package/server/projects.js +3973 -0
- package/server/routes/agent.js +1272 -0
- package/server/routes/auth.js +128 -0
- package/server/routes/auto-research.js +558 -0
- package/server/routes/cli-auth.js +958 -0
- package/server/routes/codex.js +344 -0
- package/server/routes/commands.js +601 -0
- package/server/routes/compute.js +358 -0
- package/server/routes/cursor.js +795 -0
- package/server/routes/git.js +1348 -0
- package/server/routes/mcp-utils.js +48 -0
- package/server/routes/mcp.js +618 -0
- package/server/routes/news.js +711 -0
- package/server/routes/projects.js +633 -0
- package/server/routes/references.js +338 -0
- package/server/routes/settings.js +285 -0
- package/server/routes/skills.js +914 -0
- package/server/routes/taskmaster.js +2566 -0
- package/server/routes/telemetry.js +89 -0
- package/server/routes/user.js +155 -0
- package/server/scripts/fix_session_names.js +57 -0
- package/server/scripts/research-news/common_words.py +19 -0
- package/server/scripts/research-news/link_keywords.py +298 -0
- package/server/scripts/research-news/scan_existing_notes.py +266 -0
- package/server/scripts/research-news/scoring_utils.py +227 -0
- package/server/scripts/research-news/search_arxiv.py +842 -0
- package/server/scripts/research-news/search_huggingface.py +438 -0
- package/server/scripts/research-news/search_x.py +490 -0
- package/server/scripts/research-news/search_xiaohongshu.py +610 -0
- package/server/taskmaster-templates/README.md +150 -0
- package/server/taskmaster-templates/README.zh-CN.md +150 -0
- package/server/taskmaster-templates/ai-research-dataset.json +230 -0
- package/server/taskmaster-templates/ai-research-method-model.json +230 -0
- package/server/taskmaster-templates/ai-research-position-paper.json +230 -0
- package/server/telemetry.js +121 -0
- package/server/templates/AGENTS.md +103 -0
- package/server/templates/CLAUDE.md +104 -0
- package/server/templates/GEMINI.md +103 -0
- package/server/templates/cursor-project.md +108 -0
- package/server/templates/index.js +37 -0
- package/server/utils/__tests__/sessionTokenUsage.test.js +82 -0
- package/server/utils/auto-research-mailer.js +57 -0
- package/server/utils/cliResolution.js +170 -0
- package/server/utils/commandParser.js +303 -0
- package/server/utils/cursorCommand.js +61 -0
- package/server/utils/geminiApiKey.js +28 -0
- package/server/utils/gitConfig.js +24 -0
- package/server/utils/imageAttachmentFiles.js +35 -0
- package/server/utils/mcp-detector.js +198 -0
- package/server/utils/parsers/bibtex-parser.js +207 -0
- package/server/utils/permissions.js +100 -0
- package/server/utils/runtimePorts.js +212 -0
- package/server/utils/sessionFormatting.js +68 -0
- package/server/utils/sessionIndex.js +100 -0
- package/server/utils/sessionMode.js +95 -0
- package/server/utils/sessionTokenUsage.js +105 -0
- package/server/utils/taskmaster-websocket.js +129 -0
- package/server/utils/zotero-client.js +136 -0
- package/shared/errorClassifier.js +102 -0
- package/shared/geminiThinkingSupport.d.ts +22 -0
- package/shared/geminiThinkingSupport.js +96 -0
- package/shared/geminiThoughtParser.d.ts +8 -0
- package/shared/geminiThoughtParser.js +30 -0
- package/shared/modelConstants.js +139 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
HuggingFace Daily Papers search script.
|
|
4
|
+
|
|
5
|
+
Fetches papers from the HuggingFace Daily Papers API, scores them against
|
|
6
|
+
research interest configuration, and outputs filtered/ranked results in the
|
|
7
|
+
same JSON format used by search_arxiv.py.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import logging
|
|
14
|
+
import ssl
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from typing import Dict, List, Optional, Tuple
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import requests
|
|
22
|
+
HAS_REQUESTS = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
HAS_REQUESTS = False
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
import certifi
|
|
28
|
+
CERTIFI_CA_BUNDLE = certifi.where()
|
|
29
|
+
except ImportError:
|
|
30
|
+
CERTIFI_CA_BUNDLE = None
|
|
31
|
+
|
|
32
|
+
import urllib.request
|
|
33
|
+
import urllib.parse
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# Import shared scoring utilities
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
39
|
+
from scoring_utils import (
|
|
40
|
+
SCORE_MAX,
|
|
41
|
+
calculate_relevance_score,
|
|
42
|
+
calculate_recency_score,
|
|
43
|
+
calculate_quality_score,
|
|
44
|
+
calculate_recommendation_score,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# HuggingFace API configuration
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
HF_DAILY_PAPERS_URL = "https://huggingface.co/api/daily_papers"
|
|
51
|
+
|
|
52
|
+
# Popularity: 50+ upvotes = max score (SCORE_MAX)
|
|
53
|
+
HF_UPVOTES_FULL_SCORE = 50
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build_ssl_context() -> ssl.SSLContext:
|
|
57
|
+
if CERTIFI_CA_BUNDLE and os.path.exists(CERTIFI_CA_BUNDLE):
|
|
58
|
+
return ssl.create_default_context(cafile=CERTIFI_CA_BUNDLE)
|
|
59
|
+
return ssl.create_default_context()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def http_get_json(url: str, headers: Optional[Dict[str, str]] = None, timeout: int = 30) -> List[Dict]:
|
|
63
|
+
headers = headers or {}
|
|
64
|
+
|
|
65
|
+
if HAS_REQUESTS:
|
|
66
|
+
request_kwargs = {
|
|
67
|
+
"headers": headers,
|
|
68
|
+
"timeout": timeout,
|
|
69
|
+
}
|
|
70
|
+
if CERTIFI_CA_BUNDLE and os.path.exists(CERTIFI_CA_BUNDLE):
|
|
71
|
+
request_kwargs["verify"] = CERTIFI_CA_BUNDLE
|
|
72
|
+
response = requests.get(url, **request_kwargs)
|
|
73
|
+
response.raise_for_status()
|
|
74
|
+
return response.json()
|
|
75
|
+
|
|
76
|
+
req = urllib.request.Request(url, headers=headers)
|
|
77
|
+
with urllib.request.urlopen(req, timeout=timeout, context=build_ssl_context()) as resp:
|
|
78
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def load_research_config(config_path: str) -> Dict:
|
|
82
|
+
"""
|
|
83
|
+
Load research interest configuration from a YAML file.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
config_path: Path to the YAML config file.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Research configuration dictionary.
|
|
90
|
+
"""
|
|
91
|
+
import json
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
with open(config_path, "r", encoding="utf-8-sig") as f:
|
|
95
|
+
if config_path.endswith(".json"):
|
|
96
|
+
config = json.load(f)
|
|
97
|
+
else:
|
|
98
|
+
try:
|
|
99
|
+
import yaml
|
|
100
|
+
config = yaml.safe_load(f)
|
|
101
|
+
except ImportError:
|
|
102
|
+
config = json.load(f)
|
|
103
|
+
return config
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.error("Error loading config: %s", e)
|
|
106
|
+
return {
|
|
107
|
+
"research_domains": {
|
|
108
|
+
"LLM": {
|
|
109
|
+
"keywords": [
|
|
110
|
+
"pre-training", "foundation model", "model architecture",
|
|
111
|
+
"large language model", "LLM", "transformer",
|
|
112
|
+
],
|
|
113
|
+
"arxiv_categories": ["cs.AI", "cs.LG", "cs.CL"],
|
|
114
|
+
"priority": 5,
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"excluded_keywords": ["3D", "review", "workshop", "survey"],
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def fetch_daily_papers(max_retries: int = 3) -> List[Dict]:
|
|
122
|
+
"""
|
|
123
|
+
Fetch papers from the HuggingFace Daily Papers API.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
max_retries: Maximum number of retry attempts.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Raw list of paper entries from the API.
|
|
130
|
+
"""
|
|
131
|
+
headers = {"User-Agent": "ResearchNews-HFPaperFetcher/1.0"}
|
|
132
|
+
|
|
133
|
+
for attempt in range(max_retries):
|
|
134
|
+
try:
|
|
135
|
+
data = http_get_json(HF_DAILY_PAPERS_URL, headers=headers, timeout=30)
|
|
136
|
+
|
|
137
|
+
logger.info("[HF] Fetched %d daily paper entries", len(data))
|
|
138
|
+
return data
|
|
139
|
+
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.warning("[HF] Error (attempt %d/%d): %s", attempt + 1, max_retries, e)
|
|
142
|
+
if attempt < max_retries - 1:
|
|
143
|
+
import time
|
|
144
|
+
wait_time = (2 ** attempt) * 2
|
|
145
|
+
logger.info("[HF] Retrying in %d seconds...", wait_time)
|
|
146
|
+
time.sleep(wait_time)
|
|
147
|
+
else:
|
|
148
|
+
logger.error("[HF] Failed after %d attempts", max_retries)
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
return []
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def normalize_paper(entry: Dict) -> Optional[Dict]:
|
|
155
|
+
"""
|
|
156
|
+
Normalize a single HuggingFace daily-papers API entry into the internal
|
|
157
|
+
paper dict format used by scoring functions.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
entry: A single entry from the HF daily papers response.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Normalized paper dict, or None if essential fields are missing.
|
|
164
|
+
"""
|
|
165
|
+
paper_data = entry.get("paper", {})
|
|
166
|
+
|
|
167
|
+
arxiv_id = paper_data.get("id")
|
|
168
|
+
title = paper_data.get("title")
|
|
169
|
+
summary = paper_data.get("summary")
|
|
170
|
+
|
|
171
|
+
if not title or not arxiv_id:
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
# Authors: list of dicts with "name" key -> comma-separated string
|
|
175
|
+
raw_authors = paper_data.get("authors") or []
|
|
176
|
+
if isinstance(raw_authors, list):
|
|
177
|
+
author_names = [a.get("name", "") if isinstance(a, dict) else str(a) for a in raw_authors]
|
|
178
|
+
authors_str = ", ".join(n for n in author_names if n)
|
|
179
|
+
else:
|
|
180
|
+
authors_str = str(raw_authors)
|
|
181
|
+
|
|
182
|
+
# Published date
|
|
183
|
+
published_at = paper_data.get("publishedAt", "")
|
|
184
|
+
published_date = None
|
|
185
|
+
if published_at:
|
|
186
|
+
try:
|
|
187
|
+
published_date = datetime.fromisoformat(published_at.replace("Z", "+00:00"))
|
|
188
|
+
except (ValueError, TypeError):
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
upvotes = paper_data.get("upvotes", 0) or 0
|
|
192
|
+
|
|
193
|
+
# Extra metadata from the envelope (not inside paper_data)
|
|
194
|
+
thumbnail = entry.get("thumbnail", "")
|
|
195
|
+
num_comments = entry.get("numComments", 0) or 0
|
|
196
|
+
submitted_by = entry.get("submittedBy", {}) or {}
|
|
197
|
+
organization = entry.get("organization") or paper_data.get("organization")
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
"id": arxiv_id,
|
|
201
|
+
"title": title,
|
|
202
|
+
"summary": summary or "",
|
|
203
|
+
"authors_str": authors_str,
|
|
204
|
+
"published": published_at,
|
|
205
|
+
"published_date": published_date,
|
|
206
|
+
"upvotes": upvotes,
|
|
207
|
+
"thumbnail": thumbnail,
|
|
208
|
+
"num_comments": num_comments,
|
|
209
|
+
"submitted_by_name": submitted_by.get("fullname") or submitted_by.get("name", ""),
|
|
210
|
+
"submitted_by_avatar": submitted_by.get("avatarUrl", ""),
|
|
211
|
+
"organization": organization.get("name", "") if isinstance(organization, dict) else (organization or ""),
|
|
212
|
+
"categories": [],
|
|
213
|
+
"source": "huggingface",
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def calculate_popularity_score(upvotes: int) -> float:
|
|
218
|
+
"""
|
|
219
|
+
Calculate popularity score based on HuggingFace upvotes.
|
|
220
|
+
|
|
221
|
+
50+ upvotes maps to the maximum score (SCORE_MAX).
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
upvotes: Number of upvotes on HuggingFace.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Popularity score in [0, SCORE_MAX].
|
|
228
|
+
"""
|
|
229
|
+
if upvotes <= 0:
|
|
230
|
+
return 0.0
|
|
231
|
+
return min(upvotes / HF_UPVOTES_FULL_SCORE * SCORE_MAX, SCORE_MAX)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def score_papers(
|
|
235
|
+
papers: List[Dict],
|
|
236
|
+
config: Optional[Dict] = None,
|
|
237
|
+
) -> Tuple[List[Dict], int]:
|
|
238
|
+
"""
|
|
239
|
+
Score papers, optionally filtering by research configuration.
|
|
240
|
+
|
|
241
|
+
If config has research_domains, papers are filtered by relevance (unmatched
|
|
242
|
+
papers are excluded). If config is None or has no domains, all papers are
|
|
243
|
+
kept and scored by recency, popularity, and quality only.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
papers: Normalized paper dicts.
|
|
247
|
+
config: Research interest configuration (optional).
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
(scored_papers sorted by final_score descending, total_filtered count)
|
|
251
|
+
"""
|
|
252
|
+
domains = (config or {}).get("research_domains", {})
|
|
253
|
+
excluded_keywords = (config or {}).get("excluded_keywords", [])
|
|
254
|
+
has_domains = bool(domains)
|
|
255
|
+
|
|
256
|
+
scored: List[Dict] = []
|
|
257
|
+
total_filtered = 0
|
|
258
|
+
|
|
259
|
+
for paper in papers:
|
|
260
|
+
# Relevance
|
|
261
|
+
if has_domains:
|
|
262
|
+
relevance, matched_domain, matched_keywords = calculate_relevance_score(
|
|
263
|
+
paper, domains, excluded_keywords
|
|
264
|
+
)
|
|
265
|
+
if relevance == 0:
|
|
266
|
+
total_filtered += 1
|
|
267
|
+
continue
|
|
268
|
+
else:
|
|
269
|
+
# No filtering — give all papers a baseline relevance
|
|
270
|
+
relevance = 1.0
|
|
271
|
+
matched_domain = "daily_papers"
|
|
272
|
+
matched_keywords = []
|
|
273
|
+
|
|
274
|
+
# Recency
|
|
275
|
+
recency = calculate_recency_score(paper.get("published_date"))
|
|
276
|
+
|
|
277
|
+
# Popularity (HF upvotes)
|
|
278
|
+
popularity = calculate_popularity_score(paper.get("upvotes", 0))
|
|
279
|
+
|
|
280
|
+
# Quality (abstract-based heuristics)
|
|
281
|
+
summary = paper.get("summary", "")
|
|
282
|
+
quality = calculate_quality_score(summary)
|
|
283
|
+
|
|
284
|
+
# Final composite score
|
|
285
|
+
final_score = calculate_recommendation_score(
|
|
286
|
+
relevance, recency, popularity, quality
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
arxiv_id = paper["id"]
|
|
290
|
+
|
|
291
|
+
scored.append({
|
|
292
|
+
"id": arxiv_id,
|
|
293
|
+
"title": paper["title"],
|
|
294
|
+
"authors": paper.get("authors_str", ""),
|
|
295
|
+
"abstract": paper.get("summary", ""),
|
|
296
|
+
"published": paper.get("published", ""),
|
|
297
|
+
"categories": paper.get("categories", []),
|
|
298
|
+
"relevance_score": round(relevance, 2),
|
|
299
|
+
"recency_score": round(recency, 2),
|
|
300
|
+
"popularity_score": round(popularity, 2),
|
|
301
|
+
"quality_score": round(quality, 2),
|
|
302
|
+
"final_score": final_score,
|
|
303
|
+
"matched_domain": matched_domain,
|
|
304
|
+
"matched_keywords": matched_keywords,
|
|
305
|
+
"link": f"https://huggingface.co/papers/{arxiv_id}",
|
|
306
|
+
"pdf_link": f"https://arxiv.org/pdf/{arxiv_id}.pdf",
|
|
307
|
+
"source": "huggingface",
|
|
308
|
+
"media_urls": [paper["thumbnail"]] if paper.get("thumbnail") else [],
|
|
309
|
+
"engagement": {
|
|
310
|
+
"likes": paper.get("upvotes", 0),
|
|
311
|
+
"comments": paper.get("num_comments", 0),
|
|
312
|
+
},
|
|
313
|
+
"submitted_by": paper.get("submitted_by_name", ""),
|
|
314
|
+
"organization": paper.get("organization", ""),
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
scored.sort(key=lambda x: x["final_score"], reverse=True)
|
|
318
|
+
return scored, total_filtered
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def main():
|
|
322
|
+
"""Main entry point."""
|
|
323
|
+
import argparse
|
|
324
|
+
|
|
325
|
+
default_config = os.environ.get("OBSIDIAN_VAULT_PATH", "")
|
|
326
|
+
if default_config:
|
|
327
|
+
default_config = os.path.join(
|
|
328
|
+
default_config, "99_System", "Config", "research_interests.yaml"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
parser = argparse.ArgumentParser(
|
|
332
|
+
description="Fetch and score HuggingFace Daily Papers"
|
|
333
|
+
)
|
|
334
|
+
parser.add_argument(
|
|
335
|
+
"--config",
|
|
336
|
+
type=str,
|
|
337
|
+
default=default_config or None,
|
|
338
|
+
help="Path to research interests YAML config file",
|
|
339
|
+
)
|
|
340
|
+
parser.add_argument(
|
|
341
|
+
"--output",
|
|
342
|
+
type=str,
|
|
343
|
+
default="hf_daily_papers.json",
|
|
344
|
+
help="Output JSON file path",
|
|
345
|
+
)
|
|
346
|
+
parser.add_argument(
|
|
347
|
+
"--top-n",
|
|
348
|
+
type=int,
|
|
349
|
+
default=10,
|
|
350
|
+
help="Number of top papers to return",
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
args = parser.parse_args()
|
|
354
|
+
|
|
355
|
+
logging.basicConfig(
|
|
356
|
+
level=logging.INFO,
|
|
357
|
+
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
358
|
+
datefmt="%H:%M:%S",
|
|
359
|
+
stream=sys.stderr,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Config is optional — without it we show all daily papers
|
|
363
|
+
config = None
|
|
364
|
+
if args.config:
|
|
365
|
+
logger.info("Loading config from: %s", args.config)
|
|
366
|
+
config = load_research_config(args.config)
|
|
367
|
+
else:
|
|
368
|
+
logger.info("No config provided — showing all HuggingFace Daily Papers")
|
|
369
|
+
|
|
370
|
+
# Fetch daily papers from HuggingFace
|
|
371
|
+
logger.info("Fetching HuggingFace Daily Papers...")
|
|
372
|
+
raw_entries = fetch_daily_papers()
|
|
373
|
+
|
|
374
|
+
if not raw_entries:
|
|
375
|
+
logger.warning("No papers returned from HuggingFace API")
|
|
376
|
+
output = {
|
|
377
|
+
"top_papers": [],
|
|
378
|
+
"total_found": 0,
|
|
379
|
+
"total_filtered": 0,
|
|
380
|
+
"search_date": datetime.now().strftime("%Y-%m-%d"),
|
|
381
|
+
}
|
|
382
|
+
with open(args.output, "w", encoding="utf-8") as f:
|
|
383
|
+
json.dump(output, f, ensure_ascii=False, indent=2)
|
|
384
|
+
print(json.dumps(output, ensure_ascii=True, indent=2))
|
|
385
|
+
return 0
|
|
386
|
+
|
|
387
|
+
# Normalize entries
|
|
388
|
+
papers = []
|
|
389
|
+
for entry in raw_entries:
|
|
390
|
+
normalized = normalize_paper(entry)
|
|
391
|
+
if normalized:
|
|
392
|
+
papers.append(normalized)
|
|
393
|
+
|
|
394
|
+
logger.info("Normalized %d papers from %d raw entries", len(papers), len(raw_entries))
|
|
395
|
+
|
|
396
|
+
# Score (and optionally filter if config has domains)
|
|
397
|
+
scored_papers, total_filtered = score_papers(papers, config)
|
|
398
|
+
|
|
399
|
+
logger.info(
|
|
400
|
+
"Scored %d papers (%d filtered out by relevance/exclusion)",
|
|
401
|
+
len(scored_papers),
|
|
402
|
+
total_filtered,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Take top N
|
|
406
|
+
top_papers = scored_papers[: args.top_n]
|
|
407
|
+
|
|
408
|
+
# Build output
|
|
409
|
+
output = {
|
|
410
|
+
"top_papers": top_papers,
|
|
411
|
+
"total_found": len(papers),
|
|
412
|
+
"total_filtered": total_filtered,
|
|
413
|
+
"search_date": datetime.now().strftime("%Y-%m-%d"),
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
# Save to file
|
|
417
|
+
with open(args.output, "w", encoding="utf-8") as f:
|
|
418
|
+
json.dump(output, f, ensure_ascii=False, indent=2, default=str)
|
|
419
|
+
|
|
420
|
+
logger.info("Results saved to: %s", args.output)
|
|
421
|
+
logger.info("Top %d papers:", len(top_papers))
|
|
422
|
+
for i, p in enumerate(top_papers, 1):
|
|
423
|
+
logger.info(
|
|
424
|
+
" %d. %s... (Score: %s, Upvotes-based popularity: %s)",
|
|
425
|
+
i,
|
|
426
|
+
p["title"][:60],
|
|
427
|
+
p["final_score"],
|
|
428
|
+
p["popularity_score"],
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# Also output to stdout
|
|
432
|
+
print(json.dumps(output, ensure_ascii=True, indent=2, default=str))
|
|
433
|
+
|
|
434
|
+
return 0
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
if __name__ == "__main__":
|
|
438
|
+
sys.exit(main())
|