Replace existing Zotero AI notes by default

This commit is contained in:
qyh15 2026-05-22 16:19:11 +08:00
parent d1e19e1bb8
commit dd6e06665c
3 changed files with 35 additions and 9 deletions

View File

@ -44,7 +44,7 @@ config/config.local.json
```
```text
使用 MYwrite skill为这个 Zotero 条目生成满血版深度阅读笔记SXAIQUJT。不要跳过已有笔记生成一条新的更详细 Zotero 子笔记。
使用 MYwrite skill为这个 Zotero 条目重新生成满血版深度阅读笔记SXAIQUJT。覆盖已有 AI 子笔记,不要新建重复笔记。
```
```text
@ -71,6 +71,8 @@ config/config.local.json
如果需要和手动 AwesomeGPT 生成结果接近的详细笔记,请明确告诉 AI 使用“满血版”或“深度精读”模式。该模式会读取更多全文内容,并要求模型输出更完整的结构化笔记。
重新生成笔记时,默认应覆盖已有 AI 子笔记,不要新建重复笔记。只有在明确需要对比两个版本时,才让 AI 另存为新的 Zotero 子笔记。
## 仓库内容
```text

View File

@ -50,10 +50,10 @@ py "$env:USERPROFILE\.codex\skills\MYwrite\scripts\generate_zotero_ai_note.py" -
### Deep full-detail item
Use this when the user asks for `满血版本`, `详细版`, `深度精读`, or says the batch note is not as detailed as manually generated AwesomeGPT notes. Do not use `--skip-existing` when the user wants to regenerate a fuller note; create a new Zotero child note unless the user explicitly asks to delete old notes.
Use this when the user asks for `满血版本`, `详细版`, `深度精读`, or says the batch note is not as detailed as manually generated AwesomeGPT notes. When regenerating, prefer `--replace-existing` so the existing AI child note is updated instead of creating duplicate Zotero child notes.
```powershell
py "$env:USERPROFILE\.codex\skills\MYwrite\scripts\generate_zotero_ai_note.py" --vault "C:\Users\qyh15\Documents\Obsidian Vault" --item-key SXAIQUJT --mode deep --fulltext-chars 80000 --max-tokens 12000
py "$env:USERPROFILE\.codex\skills\MYwrite\scripts\generate_zotero_ai_note.py" --vault "C:\Users\qyh15\Documents\Obsidian Vault" --item-key SXAIQUJT --mode deep --fulltext-chars 80000 --max-tokens 12000 --replace-existing
```
### Multiple items
@ -99,6 +99,7 @@ Default vault organization:
## Operating Rules
- For live Zotero writes, state that Zotero child notes will be created.
- For regenerating notes, use `--replace-existing` by default; do not create duplicate child notes unless the user explicitly asks for a new comparison copy.
- Use `--dry-run` for first-time validation or template changes.
- Use `--fulltext-chars 4000` for cheap, lightweight notes; use `--mode deep --fulltext-chars 80000 --max-tokens 12000` when the user asks for a full-detail note.
- Do not add visible machine markers to note bodies. Use the Zotero item link for duplicate detection.

View File

@ -275,8 +275,9 @@ def all_top_items(limit: int = 0) -> list[dict[str, Any]]:
return items
def has_existing_ai_note(parent_key: str) -> bool:
def existing_ai_notes(parent_key: str) -> list[dict[str, Any]]:
children = zotero_local(f"/items/{urllib.parse.quote(parent_key)}/children")
matches: list[dict[str, Any]] = []
for child in children or []:
data = child.get("data") or {}
if data.get("itemType") != "note":
@ -286,8 +287,12 @@ def has_existing_ai_note(parent_key: str) -> bool:
full_note = zotero_local_optional(f"/items/{urllib.parse.quote(child['key'])}")
note = ((full_note or {}).get("data") or {}).get("note") or ""
if "AI文献笔记" in note or "AI Literature Note" in note or f"items/{parent_key}" in note:
return True
return False
matches.append(child)
return matches
def has_existing_ai_note(parent_key: str) -> bool:
return bool(existing_ai_notes(parent_key))
def export_bibtex(item_key: str) -> str:
@ -548,6 +553,17 @@ def create_child_note(user_id: str, parent_key: str, markdown: str, dry_run: boo
return zotero_web(f"/users/{user_id}/items", method="POST", payload=payload)
def update_child_note(user_id: str, note_key: str, markdown: str, dry_run: bool) -> Any:
note = zotero_web(f"/users/{user_id}/items/{urllib.parse.quote(note_key)}")
data = note.get("data") if isinstance(note, dict) else None
if not isinstance(data, dict):
fail(f"could not load existing note {note_key}")
data["note"] = markdown_to_zotero_html(markdown)
if dry_run:
return {"dryRun": True, "itemKey": note_key, "payload": data}
return zotero_web(f"/users/{user_id}/items/{urllib.parse.quote(note_key)}", method="PUT", payload=data)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--item-key", action="append", default=[], help="Zotero top-level item key; can be repeated")
@ -559,6 +575,7 @@ def main() -> None:
parser.add_argument("--mode", choices=["quick", "deep"], default="quick", help="quick for batch notes; deep for full-detail notes")
parser.add_argument("--max-tokens", type=int, default=0, help="Optional LLM output token limit; deep mode defaults to 12000")
parser.add_argument("--skip-existing", action="store_true", help="Skip items that already have a generated AI child note")
parser.add_argument("--replace-existing", action="store_true", help="Update the first existing AI child note instead of creating a duplicate")
parser.add_argument("--dry-run", action="store_true", help="generate but do not write Zotero note")
parser.add_argument("--vault", default=str(DEFAULT_VAULT), help="Obsidian vault containing 00 Templater")
parser.add_argument("--env-file", help="Optional .env path; defaults to <vault>/.env")
@ -586,7 +603,8 @@ def main() -> None:
if not key:
results.append({"index": index, "status": "skipped", "reason": "missing key", "title": title})
continue
if args.skip_existing and has_existing_ai_note(key):
existing_notes = existing_ai_notes(key)
if args.skip_existing and existing_notes:
results.append({"index": index, "itemKey": key, "title": title, "status": "skipped", "reason": "existing AI note"})
continue
print(f"[{index}/{len(items)}] generating {key}: {title}", file=sys.stderr)
@ -600,8 +618,13 @@ def main() -> None:
fulltext = local_fulltext(key, fulltext_chars)
prompt = build_prompt(item, bibtex, fulltext, vault, args.mode)
markdown = call_llm(prompt, max_tokens or None)
result = create_child_note(user_id, key, markdown, args.dry_run)
results.append({"index": index, "itemKey": key, "title": title, "status": "ok", "result": result})
if args.replace_existing and existing_notes:
note_key = existing_notes[0].get("key")
result = update_child_note(user_id, str(note_key), markdown, args.dry_run)
results.append({"index": index, "itemKey": key, "title": title, "status": "ok", "action": "updated", "noteKey": note_key, "result": result})
else:
result = create_child_note(user_id, key, markdown, args.dry_run)
results.append({"index": index, "itemKey": key, "title": title, "status": "ok", "action": "created", "result": result})
except SystemExit as exc:
results.append({
"index": index,