Replace existing Zotero AI notes by default
This commit is contained in:
parent
d1e19e1bb8
commit
dd6e06665c
|
|
@ -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
|
||||
|
|
|
|||
5
SKILL.md
5
SKILL.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue