1818 "training" : ["finetune" , "sft" , "rlhf" , "训练" , "蒸馏" ],
1919 "evaluation" : ["benchmark" , "eval" , "评测" , "幻觉" , "hallucination" ],
2020 "deployment" : ["部署" , "serving" , "latency" , "吞吐" , "inference" ],
21+ "paper-reading" : [
22+ "paper" ,
23+ "论文" ,
24+ "arxiv" ,
25+ "method" ,
26+ "实验" ,
27+ "ablation" ,
28+ "baseline" ,
29+ "sota" ,
30+ ],
31+ "productivity" : ["复盘" , "学习计划" , "todo" , "行动项" , "习惯" , "时间管理" ],
2132}
2233
2334VALID_SUBCATEGORIES = set (SUBCATEGORY_KEYWORDS ) | {"general" }
3849 "inference" ,
3950 "latency" ,
4051 "serving" ,
52+ "paper" ,
53+ "arxiv" ,
54+ "method" ,
55+ "ablation" ,
56+ "baseline" ,
57+ "sota" ,
58+ "todo" ,
4159}
4260
61+ METADATA_PREFIXES = (
62+ "title:" ,
63+ "title:" ,
64+ "paper title:" ,
65+ "paper title:" ,
66+ "论文标题:" ,
67+ "论文标题:" ,
68+ "链接:" ,
69+ "链接:" ,
70+ "link:" ,
71+ "link:" ,
72+ "arxiv:" ,
73+ "arxiv:" ,
74+ )
75+
4376
4477def slugify (text : str ) -> str :
4578 slug = re .sub (r"[^a-zA-Z0-9\u4e00-\u9fff\s-]" , "" , text ).strip ().lower ()
@@ -78,11 +111,24 @@ def classify_subcategory(text: str) -> str:
78111 return best if scores [best ] > 0 else "general"
79112
80113
114+ def _is_metadata_line (line : str ) -> bool :
115+ lower = line .strip ().lower ()
116+ if lower .startswith (METADATA_PREFIXES ):
117+ return True
118+
119+ # Skip pure arXiv URL lines, but keep explanatory sentences that merely contain a URL.
120+ pure_arxiv_url = re .fullmatch (
121+ r"https?://arxiv\.org/(?:abs|pdf)/\d{4}\.\d{4,5}(?:v\d+)?/?" ,
122+ lower ,
123+ )
124+ return pure_arxiv_url is not None
125+
126+
81127def summarize_points (content : str , max_points : int = 6 ) -> list [str ]:
82128 lines = [ln .strip (" -\t " ) for ln in content .splitlines () if ln .strip ()]
83129 points = []
84130 for line in lines :
85- if len (line ) < 8 :
131+ if len (line ) < 8 or _is_metadata_line ( line ) :
86132 continue
87133 points .append (line )
88134 if len (points ) >= max_points :
@@ -121,6 +167,8 @@ def _collect_tags(subcategory: str, source_text: str) -> list[str]:
121167 "evaluation" : ["eval" , "benchmark" , "评测" , "hallucination" ],
122168 "training" : ["sft" , "rlhf" , "finetune" , "训练" , "蒸馏" ],
123169 "deployment" : ["inference" , "serving" , "部署" , "latency" ],
170+ "paper-reading" : ["paper" , "arxiv" , "method" , "实验" , "ablation" ],
171+ "productivity" : ["复盘" , "行动项" , "计划" , "时间管理" , "学习方法" ],
124172 }
125173
126174 for tag , markers in topic_tags .items ():
@@ -129,7 +177,58 @@ def _collect_tags(subcategory: str, source_text: str) -> list[str]:
129177 if any (_keyword_hit (normalized_text , marker ) for marker in markers ):
130178 tags .append (tag )
131179
132- return tags [:5 ]
180+ # Deduplicate while preserving order.
181+ deduped_tags = list (dict .fromkeys (tags ))
182+
183+ # Add stable fallback tags to keep tags informative even on sparse inputs.
184+ fallback_tags = {
185+ "paper-reading" : ["paper" , "reading-notes" ],
186+ "productivity" : ["learning-method" , "action-items" ],
187+ }
188+ for fallback in fallback_tags .get (subcategory , ["learning-notes" ]):
189+ if fallback not in deduped_tags :
190+ deduped_tags .append (fallback )
191+ if len (deduped_tags ) >= 3 :
192+ break
193+
194+ return deduped_tags [:5 ]
195+
196+
197+ def _extract_field_value (line : str , field : str ) -> str | None :
198+ pattern = rf"^\s*{ re .escape (field )} \s*[::]\s*(.+?)\s*$"
199+ match = re .match (pattern , line , flags = re .IGNORECASE )
200+ if match :
201+ value = match .group (1 ).strip ()
202+ return value or "待补充"
203+ return None
204+
205+
206+ def _extract_paper_title (source_text : str ) -> str :
207+ lines = [ln .strip () for ln in source_text .splitlines () if ln .strip ()]
208+ title_fields = ("title" , "paper title" , "论文标题" )
209+ for line in lines :
210+ for field in title_fields :
211+ value = _extract_field_value (line , field )
212+ if value is not None :
213+ return value
214+
215+ quoted = re .search (r"[《\"]([^》\"]{8,160})[》\"]" , source_text )
216+ if quoted :
217+ return quoted .group (1 ).strip ()
218+
219+ return "待补充"
220+
221+
222+ def _extract_arxiv_link (source_text : str ) -> str :
223+ url_match = re .search (r"https?://arxiv\.org/(?:abs|pdf)/\d{4}\.\d{4,5}(?:v\d+)?" , source_text , re .IGNORECASE )
224+ if url_match :
225+ return url_match .group (0 )
226+
227+ id_match = re .search (r"arxiv\s*[::]\s*(\d{4}\.\d{4,5}(?:v\d+)?)" , source_text , re .IGNORECASE )
228+ if id_match :
229+ return f"https://arxiv.org/abs/{ id_match .group (1 )} "
230+
231+ return "待补充"
133232
134233
135234def build_post (
@@ -147,9 +246,29 @@ def build_post(
147246 "" ,
148247 "本篇为学习对话自动沉淀的笔记,保留结论和可复用要点。" ,
149248 "" ,
249+ ]
250+
251+ if subcategory == "paper-reading" :
252+ paper_title = _extract_paper_title (source_text )
253+ arxiv_link = _extract_arxiv_link (source_text )
254+ body .extend (
255+ [
256+ "## 论文信息" ,
257+ "" ,
258+ f"- 论文标题:{ paper_title } " ,
259+ "- 核心任务:待补充" ,
260+ "- 主要贡献:待补充" ,
261+ f"- 链接:{ arxiv_link } " ,
262+ "" ,
263+ "## 方法与实验要点" ,
264+ "" ,
265+ ]
266+ )
267+
268+ body .extend ([
150269 "## 结论速记" ,
151270 "" ,
152- ]
271+ ])
153272 body .extend ([f"- { point } " for point in bullets ])
154273 body .extend (
155274 [
@@ -167,7 +286,7 @@ def build_post(
167286 f'title: "{ _yaml_escaped (title )} "' ,
168287 f"date: { date .isoformat ()} 09:00:00 +0800" ,
169288 f'categories: ["{ CATEGORY } ", "{ subcategory } "]' ,
170- "tags: [" + ", " .join (f'"{ tag } "' for tag in tags ) + "]" ,
289+ "tags: [" + ", " .join (f'\ "{ tag } \ " ' for tag in tags ) + "]" ,
171290 "---" ,
172291 "" ,
173292 ]
@@ -197,7 +316,7 @@ def main() -> None:
197316 parser .add_argument (
198317 "--subcategory" ,
199318 default = None ,
200- help = "Force specific subcategory (e.g. rag/agents/general)" ,
319+ help = "Force specific subcategory (e.g. rag/agents/paper-reading/productivity/ general)" ,
201320 )
202321
203322 args = parser .parse_args ()
0 commit comments