diff --git a/paperforge/commands/ocr.py b/paperforge/commands/ocr.py index 84046e78..a3349a26 100644 --- a/paperforge/commands/ocr.py +++ b/paperforge/commands/ocr.py @@ -2,6 +2,8 @@ import argparse import json +import signal +from collections.abc import Callable import logging from pathlib import Path @@ -225,19 +227,77 @@ def _get_run_ocr(): return run_ocr +def _make_cooperative_stop() -> tuple[Callable[[], bool], Callable[[], None]]: + """Install cooperative stop mechanisms: SIGINT handler + stdin reader. + + SIGINT (POSIX/terminal): sets flag. + Stdin line "PAPERFORGE_STOP": daemon reader thread sets flag + (reliable on Windows where SIGINT can't be caught in subprocesses). + + Returns (is_stopped, restore): + is_stopped(): returns True if stop was requested + restore(): restores original SIGINT handler + """ + import threading as _threading + + _flag: list[bool] = [False] + _reader_active: list[bool] = [True] + + # ── SIGINT handler (POSIX/terminal) ── + def _handler(_signum: int, _frame: object) -> None: + _flag[0] = True + + _old = signal.signal(signal.SIGINT, _handler) + + # ── Stdin reader daemon thread (Windows-reliable) ── + def _stdin_reader() -> None: + import sys as _sys + try: + while _reader_active[0]: + line = _sys.stdin.readline() + if not line: # EOF (pipe closed) + break + if line.strip() == "PAPERFORGE_STOP": + _flag[0] = True + break + except (OSError, ValueError, AttributeError, RuntimeError): + pass # stdin unavailable in testing/headless + + _reader_thread = _threading.Thread(target=_stdin_reader, daemon=True) + _reader_thread.start() + + def _restore() -> None: + _reader_active[0] = False + signal.signal(signal.SIGINT, _old) + + return (lambda: _flag[0], _restore) + + + def _run_ocr_redo(vault: Path, keys: list[str] | None = None, dry_run: bool = False, verbose: bool = False, no_progress: bool = False) -> int: """Re-run OCR for papers. - If keys provided, only redo those papers. - If no keys, scan for ocr_redo: true papers (old behavior). - """ - from paperforge.worker.ocr import ocr_redo_papers - if keys: - # Keyed redo: process specific papers - from paperforge.worker.ocr import ensure_ocr_meta, write_json + If keys provided, delegate to redo_papers_for_keys which handles the + full artifact-delete + OCR run + post-check cycle per paper, supporting + per-paper progress tokens and cooperative stop. + + If no keys, scan for ocr_redo: true papers (legacy behavior). + + Progress tokens (multi-key non-dry-run only): + OCR_REDO_START:{total} + OCR_REDO_PROGRESS:{current}:{total}:{key} + OCR_REDO_DONE + + Cooperative stop: SIGINT sets flag checked between papers. + """ + from paperforge.worker.ocr import ocr_redo_papers, redo_papers_for_keys + + if not keys: + return ocr_redo_papers(vault, dry_run=dry_run, verbose=verbose, no_progress=no_progress) + + if not dry_run: from paperforge.worker._utils import pipeline_paths - from pathlib import Path paths = pipeline_paths(vault) ocr_root = paths.get("ocr") @@ -245,34 +305,65 @@ def _run_ocr_redo(vault: Path, keys: list[str] | None = None, dry_run: bool = Fa print("No OCR root directory.") return 1 - for key in keys: - meta_path = ocr_root / key / "meta.json" - if not meta_path.exists(): - print(f"{key}: no meta.json, skipping") - continue - meta = __import__("json").loads(meta_path.read_text(encoding="utf-8")) - current = str(meta.get("ocr_status", "") or "").strip().lower() - if current == "nopdf": - print(f"{key}: nopdf — cannot redo (missing PDF)") - continue - # Reset meta to pending - meta["ocr_status"] = "pending" - meta["ocr_job_id"] = "" - meta["ocr_started_at"] = "" - meta["ocr_finished_at"] = "" - meta["error"] = "" - meta["retry_count"] = 0 - if dry_run: - print(f"{key}: would reset to pending (dry-run)") - else: - write_json(meta_path, meta) - print(f"{key}: reset to pending") + total = len(keys) + batch = total > 1 and not dry_run - if not dry_run: - print(f"Triggering OCR run for {len(keys)} paper(s)...") + if batch: + print(f"OCR_REDO_START:{total}", flush=True) + _is_stopped, _restore_signal = _make_cooperative_stop() + else: + _is_stopped = lambda: False + _restore_signal = lambda: None + + if dry_run: + print(f"Would redo {total} paper(s):") + for k in keys: + print(f" - {k}: would delete artifacts and re-run OCR") + print("Dry-run: no changes made. Run without --dry-run to execute.") + if batch: + print(f"OCR_REDO_DONE", flush=True) return 0 - return ocr_redo_papers(vault, dry_run=dry_run, verbose=verbose, no_progress=no_progress) + # Track current for progress token + _current = [0] + + def _progress_callback(key: str) -> None: + _current[0] += 1 + print(f"OCR_REDO_PROGRESS:{_current[0]}:{total}:{key}", flush=True) + + def _stop_check() -> bool: + return _is_stopped() + + try: + result = redo_papers_for_keys( + vault, keys, + verbose=verbose, + progress_callback=_progress_callback if batch else None, + stop_check=_stop_check if batch else None, + ) + + success_keys = result.get("success_keys", []) + failed_keys = result.get("failed_keys", []) + worker_exit_code = result.get("exit_code", 0) + if success_keys: + print(f"Redo OCR done={len(success_keys)}: {', '.join(success_keys)}", flush=True) + if failed_keys: + print(f"Redo OCR pending/failed={len(failed_keys)}: {', '.join(failed_keys)}", flush=True) + + if batch and _is_stopped(): + print(f"Batch stopped (SIGINT) after {_current[0]} paper(s).") + + if batch: + print(f"OCR_REDO_DONE", flush=True) + finally: + _restore_signal() + + if batch and _is_stopped(): + return 130 + return worker_exit_code + + + def _run_ocr_list(vault: Path, json_output: bool = False, output_file: str | None = None, @@ -438,7 +529,13 @@ def _run_ocr_rebuild( resume: bool = False, parallel_workers: int = 4, ) -> int: - """Rebuild OCR-derived artifacts from existing raw blocks.""" + """Rebuild OCR-derived artifacts from existing raw blocks. + + Progress tokens (multi-key non-dry-run only): + OCR_REBUILD_START:{total} + OCR_REBUILD_PROGRESS:{current}:{total}:{key} + OCR_REBUILD_DONE + """ from paperforge.worker.ocr_maintenance import collect_maintenance_rows from paperforge.worker.ocr_rebuild import run_derived_rebuild_for_keys @@ -452,22 +549,52 @@ def _run_ocr_rebuild( if resume: print("Note: OCR rebuild resume is now version/artifact based; .done markers are ignored.") + total = len(selected) + batch = total > 1 and not dry_run + if dry_run: - print(f"Would rebuild {len(selected)} paper(s):") + print(f"Would rebuild {total} paper(s):") for k in selected: reason = reasons.get(k, "manual_override") print(f" - {k}: {reason}") return 0 from paperforge.worker._progress import progress_bar - result = run_derived_rebuild_for_keys( - vault, selected, - progress_bar=progress_bar, - parallel=parallel_workers, - ) - count = result.get("rebuild_count", 0) - print(f"Done. Rebuilt {count} paper(s).") - return 0 + + if batch: + print(f"OCR_REBUILD_START:{total}", flush=True) + _count = 0 + def _on_progress(key: str) -> None: + nonlocal _count + _count += 1 + print(f"OCR_REBUILD_PROGRESS:{_count}:{total}:{key}", flush=True) + # Force sequential for cooperative stop; parallel pool can't stop mid-batch + parallel_workers = 0 + _is_stopped, _restore_signal = _make_cooperative_stop() + def _stop_check() -> bool: + return _is_stopped() + else: + _on_progress = None # type: ignore[assignment] + _is_stopped = lambda: False + _restore_signal = lambda: None + _stop_check = None + + try: + result = run_derived_rebuild_for_keys( + vault, selected, + progress_bar=progress_bar, + parallel=parallel_workers, + on_progress=_on_progress, + stop_check=_stop_check, + ) + count = result.get("rebuild_count", 0) + print(f"Done. Rebuilt {count} paper(s).") + if batch: + print(f"OCR_REBUILD_DONE", flush=True) + finally: + _restore_signal() + return 0 if not _is_stopped() else 130 + def run(args: argparse.Namespace) -> int: diff --git a/paperforge/plugin/main.js b/paperforge/plugin/main.js index 5a64fdba..537798a5 100644 --- a/paperforge/plugin/main.js +++ b/paperforge/plugin/main.js @@ -1,37 +1,37 @@ "use strict"; -var Dt = Object.create; -var Ie = Object.defineProperty; -var Bt = Object.getOwnPropertyDescriptor; -var At = Object.getOwnPropertyNames; -var Ot = Object.getPrototypeOf, - Lt = Object.prototype.hasOwnProperty; -var It = (u, h) => () => (h || u((h = { exports: {} }).exports, h), h.exports), - Mt = (u, h) => { - for (var e in h) Ie(u, e, { get: h[e], enumerable: !0 }); +var Ot = Object.create; +var ze = Object.defineProperty; +var Lt = Object.getOwnPropertyDescriptor; +var It = Object.getOwnPropertyNames; +var Mt = Object.getPrototypeOf, + Nt = Object.prototype.hasOwnProperty; +var jt = (u, h) => () => (h || u((h = { exports: {} }).exports, h), h.exports), + zt = (u, h) => { + for (var e in h) ze(u, e, { get: h[e], enumerable: !0 }); }, - at = (u, h, e, t) => { + ct = (u, h, e, t) => { if ((h && typeof h == "object") || typeof h == "function") - for (let r of At(h)) - !Lt.call(u, r) && + for (let r of It(h)) + !Nt.call(u, r) && r !== e && - Ie(u, r, { + ze(u, r, { get: () => h[r], - enumerable: !(t = Bt(h, r)) || t.enumerable, + enumerable: !(t = Lt(h, r)) || t.enumerable, }); return u; }; -var V = (u, h, e) => ( - (e = u != null ? Dt(Ot(u)) : {}), - at( +var K = (u, h, e) => ( + (e = u != null ? Ot(Mt(u)) : {}), + ct( h || !u || !u.__esModule - ? Ie(e, "default", { value: u, enumerable: !0 }) + ? ze(e, "default", { value: u, enumerable: !0 }) : e, u ) ), - Nt = (u) => at(Ie({}, "__esModule", { value: !0 }), u); -var Je = It((Jt, zt) => { - zt.exports = { + $t = (u) => ct(ze({}, "__esModule", { value: !0 }), u); +var Xe = jt((Qt, Ht) => { + Ht.exports = { versions: [ { version: "1.5.15", @@ -63,18 +63,18 @@ var Je = It((Jt, zt) => { ], }; }); -var Wt = {}; -Mt(Wt, { default: () => Ke }); -module.exports = Nt(Wt); -var U = require("obsidian"), - W = V(require("fs")), - Se = V(require("path")), - pe = require("child_process"); -var ve = "paperforge-status", - Fe = "paperforge", - it = +var Jt = {}; +zt(Jt, { default: () => Ue }); +module.exports = $t(Jt); +var X = require("obsidian"), + Z = K(require("fs")), + Te = K(require("path")), + fe = require("child_process"); +var we = "paperforge-status", + De = "paperforge", + pt = '', - se = [ + oe = [ { id: "paperforge-sync", title: "Sync Library", @@ -118,7 +118,7 @@ var ve = "paperforge-status", okMsg: "OCR redo started", }, ], - Re = { + Be = { vault_path: "", setup_complete: !1, auto_update: !0, @@ -140,14 +140,14 @@ var ve = "paperforge-status", base_dir: "", last_seen_version: "", }; -function ot(u, h) { +function dt(u, h) { if (!h || !h.note_path) return h; let e = u.vault.getAbstractFileByPath(h.note_path); if (!e) return h; let t = u.metadataCache.getFileCache(e), r = t && t.frontmatter; if (!r) return h; - let n = { ...h }; + let s = { ...h }; for (let a of [ "do_ocr", "analyze", @@ -155,13 +155,13 @@ function ot(u, h) { "ocr_redo", "deep_reading_status", ]) - Object.prototype.hasOwnProperty.call(r, a) && (n[a] = r[a]); - return n; + Object.prototype.hasOwnProperty.call(r, a) && (s[a] = r[a]); + return s; } -function Ze(u, h) { +function qe(u, h) { return u && { ...u, ...h }; } -var qe = { +var Je = { en: { action_running: "Running ", api_key_missing: "Missing", @@ -432,6 +432,14 @@ var qe = { maintenance_refresh_spinning: "Updating\u2026", maintenance_all_good: "\u2705 All good \u2014 no action needed", maintenance_n_pending: "{n} need attention", + maintenance_filter_all: "All", + maintenance_filter_recommended: "Recommended", + maintenance_batch_rebuild: "\u25B6 Rebuild selected", + maintenance_batch_redo: "\u25B6 Full OCR redo selected", + maintenance_stop: "Stop", + maintenance_batch_complete: + "Batch operation complete \u2014 {n} papers processed.", + maintenance_progress_label: "{current}/{total} papers", version_panel_title: "Version History", version_panel_back: "Back", version_filter_placeholder: "Filter papers...", @@ -448,47 +456,45 @@ var qe = { version_compare_paragraphs: "{n} paragraphs changed", version_error_read: "Cannot read version data", retrieval_search_placeholder: "Search papers... (@ for deep search)", - retrieval_search_placeholder_deep: "Search papers via deep retrieval...", - retrieval_search_idle_hint: - "Type to search metadata, or start with @ for deep semantic search", + retrieval_search_placeholder_deep: "Search paper content...", + retrieval_search_idle_hint: "Type a keyword or @ to search paper content", retrieval_searching_metadata: "Searching metadata...", - retrieval_searching_deep: "Running deep search...", + retrieval_searching_deep: "Deep searching...", retrieval_search_cancel: "Cancel", - retrieval_results_count: "{n} result{s}", + retrieval_results_count: "{n} result(s)", retrieval_empty: "No matching papers found.", - retrieval_empty_tips: "Try broader terms or @ deep search.", - retrieval_vectors_not_built: "Vectors Not Built", + retrieval_empty_tips: "Try broader terms or use @ deep search.", + retrieval_vectors_not_built: "Vector index not built", retrieval_vectors_not_built_desc: - "Vector index has not been built. Search requires a built vector index.", + "Build vectors to enable @ deep search with semantic matching.", retrieval_open_vector_settings: "Open Vector Settings", - retrieval_backend_unavailable: "Backend Unavailable", + retrieval_backend_unavailable: "Search backend unavailable", retrieval_backend_unavailable_desc: - "The Python CLI search backend is not responding.", + "The Python CLI search backend is not responding correctly.", retrieval_run_doctor: "Run Doctor", retrieval_retry: "Retry", - retrieval_timeout_title: "Search Timed Out", + retrieval_timeout_title: "Search timed out", retrieval_timeout_desc: - "The search took too long. You can retry or try a simpler query.", - retrieval_model_changed: "Model Changed", + "The search took too long. Try a more specific query.", + retrieval_model_changed: "Model changed", retrieval_model_changed_desc: - "The embedding model has changed since vectors were built. Rebuilding is required.", + "The embedding model has changed since vectors were built. Rebuild to use deep search.", retrieval_rebuild_vectors: "Rebuild Vectors", - retrieval_internal_error: - "An internal error occurred. Check the console for details.", - retrieval_build_idle: "Vectors not yet built", - retrieval_build_ready: "Vectors ready", + retrieval_build_idle: "Vector database ready", + retrieval_build_ready: "{n} vector(s) built", retrieval_build_stopping: "Stopping...", - retrieval_build_stopped: "Build stopped. Resume to continue.", + retrieval_build_stopped: "Build stopped ({n}/{t} papers)", retrieval_build_failed: "Build failed", - retrieval_build_stale: - "Previous build was interrupted. Resume to continue.", - retrieval_build_deps_missing: "Dependencies not installed", - retrieval_build_runtime_mismatch: "Plugin and CLI versions differ", + retrieval_build_stale: "Vectors are stale \u2014 rebuild recommended", + retrieval_build_deps_missing: + "Dependencies missing. Install chromadb and openai.", + retrieval_build_runtime_mismatch: "Python runtime version mismatch.", retrieval_stop: "Stop", + retrieval_no_python: "Python not found", + retrieval_internal_error: "An internal error occurred", retrieval_force_rebuild: "Force Rebuild", retrieval_rebuild_warning: "Rebuild will replace {n} existing chunk(s). Continue?", - retrieval_no_python: "No Python found. Check Installation tab.", }, zh: { action_running: "\u6B63\u5728\u6267\u884C ", @@ -765,6 +771,7 @@ var qe = { "\u8FD9\u7C7B\u8BBA\u6587\u901A\u5E38\u8868\u793A\u7248\u5F0F\u590D\u6742\u6216\u4FE1\u53F7\u504F\u5F31\uFF0CPaperForge \u76EE\u524D\u6CA1\u6709\u9AD8\u7F6E\u4FE1\u5EA6\u7684\u7EF4\u62A4\u5EFA\u8BAE\u3002", ocr_maint_all_papers: "\u5168\u90E8\u8BBA\u6587", ocr_maint_rebuild_btn: "\u91CD\u5EFA\u7ED3\u679C", + ocr_maint_redo_btn: "\u91CD\u65B0 OCR", maintenance_group_retry: "\u9700\u8981\u91CD\u8BD5", maintenance_group_rebuild: "\u53EF\u91CD\u5EFA\u7ED3\u679C", maintenance_group_legacy: @@ -775,6 +782,14 @@ var qe = { maintenance_refresh_spinning: "\u6B63\u5728\u66F4\u65B0\u2026", maintenance_all_good: "\u2705 \u5168\u90E8\u6B63\u5E38", maintenance_n_pending: "{n} \u7BC7\u9700\u8981\u5904\u7406", + maintenance_filter_all: "\u5168\u90E8", + maintenance_filter_recommended: "\u5EFA\u8BAE\u5904\u7406", + maintenance_batch_rebuild: "\u25B6 \u91CD\u5EFA\u5DF2\u9009", + maintenance_batch_redo: "\u25B6 \u5168\u90E8\u91CD\u8DD1 OCR", + maintenance_stop: "\u505C\u6B62", + maintenance_batch_complete: + "\u6279\u91CF\u64CD\u4F5C\u5B8C\u6210 \u2014 \u5904\u7406\u4E86 {n} \u7BC7\u8BBA\u6587\u3002", + maintenance_progress_label: "{current}/{total} \u7BC7", version_panel_title: "\u7248\u672C\u5386\u53F2", version_panel_back: "\u8FD4\u56DE", version_filter_placeholder: "\u641C\u7D22\u8BBA\u6587...", @@ -793,59 +808,57 @@ var qe = { version_compare_paragraphs: "{n} \u6BB5\u6709\u53D8\u5316", version_error_read: "\u65E0\u6CD5\u8BFB\u53D6\u7248\u672C\u6570\u636E", retrieval_search_placeholder: - "\u641C\u7D22\u8BBA\u6587... (@ \u8FDB\u884C\u6DF1\u5EA6\u641C\u7D22)", + "\u641C\u7D22\u8BBA\u6587...\uFF08@ \u542F\u52A8\u6DF1\u5EA6\u641C\u7D22\uFF09", retrieval_search_placeholder_deep: - "\u901A\u8FC7\u6DF1\u5EA6\u68C0\u7D22\u641C\u7D22\u8BBA\u6587...", + "\u641C\u7D22\u8BBA\u6587\u5185\u5BB9...", retrieval_search_idle_hint: - "\u8F93\u5165\u5173\u952E\u5B57\u641C\u7D22\u5143\u6570\u636E\uFF0C\u6216\u4EE5 @ \u5F00\u5934\u8FDB\u884C\u6DF1\u5EA6\u8BED\u4E49\u641C\u7D22", - retrieval_searching_metadata: - "\u6B63\u5728\u641C\u7D22\u5143\u6570\u636E...", - retrieval_searching_deep: - "\u6B63\u5728\u8FD0\u884C\u6DF1\u5EA6\u641C\u7D22...", + "\u8F93\u5165\u5173\u952E\u8BCD\u641C\u7D22\u8BBA\u6587\uFF0C\u6216\u4EE5 @ \u5F00\u5934\u641C\u7D22\u8BBA\u6587\u5185\u5BB9", + retrieval_searching_metadata: "\u641C\u7D22\u5143\u6570\u636E\u4E2D...", + retrieval_searching_deep: "\u6DF1\u5EA6\u641C\u7D22\u4E2D...", retrieval_search_cancel: "\u53D6\u6D88", - retrieval_results_count: "\u627E\u5230 {n} \u6761\u7ED3\u679C", + retrieval_results_count: "{n} \u4E2A\u7ED3\u679C", retrieval_empty: "\u672A\u627E\u5230\u5339\u914D\u7684\u8BBA\u6587\u3002", retrieval_empty_tips: - "\u5C1D\u8BD5\u66F4\u5BBD\u6CDB\u7684\u5173\u952E\u8BCD\uFF0C\u6216\u4F7F\u7528 @ \u8FDB\u884C\u6DF1\u5EA6\u641C\u7D22\u3002", + "\u5C1D\u8BD5\u66F4\u5BBD\u6CDB\u7684\u5173\u952E\u8BCD\uFF0C\u6216\u4F7F\u7528 @ \u6DF1\u5EA6\u641C\u7D22\u8BBA\u6587\u5185\u5BB9\u3002", retrieval_vectors_not_built: "\u5411\u91CF\u7D22\u5F15\u672A\u6784\u5EFA", retrieval_vectors_not_built_desc: - "\u5411\u91CF\u7D22\u5F15\u5C1A\u672A\u6784\u5EFA\uFF0C\u641C\u7D22\u9700\u8981\u5148\u6784\u5EFA\u5411\u91CF\u7D22\u5F15\u3002", + "\u6784\u5EFA\u5411\u91CF\u7D22\u5F15\u4EE5\u542F\u7528 @ \u6DF1\u5EA6\u8BED\u4E49\u641C\u7D22\u3002", retrieval_open_vector_settings: "\u6253\u5F00\u5411\u91CF\u8BBE\u7F6E", - retrieval_backend_unavailable: "\u540E\u7AEF\u4E0D\u53EF\u7528", + retrieval_backend_unavailable: + "\u641C\u7D22\u540E\u7AEF\u4E0D\u53EF\u7528", retrieval_backend_unavailable_desc: - "Python CLI \u641C\u7D22\u540E\u7AEF\u65E0\u54CD\u5E94\u3002", + "Python CLI \u641C\u7D22\u540E\u7AEF\u672A\u6B63\u5E38\u54CD\u5E94\u3002", retrieval_run_doctor: "\u8FD0\u884C\u8BCA\u65AD", retrieval_retry: "\u91CD\u8BD5", retrieval_timeout_title: "\u641C\u7D22\u8D85\u65F6", retrieval_timeout_desc: - "\u641C\u7D22\u8017\u65F6\u8FC7\u957F\u3002\u4F60\u53EF\u4EE5\u91CD\u8BD5\uFF0C\u6216\u5C1D\u8BD5\u7B80\u5316\u67E5\u8BE2\u3002", + "\u641C\u7D22\u8017\u65F6\u8FC7\u957F\uFF0C\u8BF7\u5C1D\u8BD5\u66F4\u7CBE\u786E\u7684\u67E5\u8BE2\u3002", retrieval_model_changed: "\u6A21\u578B\u5DF2\u66F4\u6362", retrieval_model_changed_desc: - "\u5D4C\u5165\u6A21\u578B\u5DF2\u66F4\u6362\uFF0C\u9700\u8981\u91CD\u5EFA\u5411\u91CF\u3002", + "\u5D4C\u5165\u6A21\u578B\u5DF2\u66F4\u6362\uFF0C\u9700\u91CD\u5EFA\u5411\u91CF\u540E\u624D\u80FD\u4F7F\u7528\u6DF1\u5EA6\u641C\u7D22\u3002", retrieval_rebuild_vectors: "\u91CD\u5EFA\u5411\u91CF", - retrieval_internal_error: - "\u53D1\u751F\u5185\u90E8\u9519\u8BEF\u3002\u8BF7\u67E5\u770B\u63A7\u5236\u53F0\u4E86\u89E3\u8BE6\u60C5\u3002", - retrieval_build_idle: "\u5411\u91CF\u7D22\u5F15\u5C1A\u672A\u6784\u5EFA", - retrieval_build_ready: "\u5411\u91CF\u7D22\u5F15\u5C31\u7EEA", - retrieval_build_stopping: "\u6B63\u5728\u505C\u6B62\u2026", + retrieval_build_idle: "\u5411\u91CF\u6570\u636E\u5E93\u5C31\u7EEA", + retrieval_build_ready: "\u5DF2\u6784\u5EFA {n} \u4E2A\u5411\u91CF", + retrieval_build_stopping: "\u6B63\u5728\u505C\u6B62...", retrieval_build_stopped: - "\u6784\u5EFA\u5DF2\u505C\u6B62\u3002\u70B9\u51FB\u7EE7\u7EED\u4EE5\u6062\u590D\u3002", + "\u6784\u5EFA\u5DF2\u505C\u6B62\uFF08{n}/{t} \u7BC7\uFF09", retrieval_build_failed: "\u6784\u5EFA\u5931\u8D25", retrieval_build_stale: - "\u4E0A\u6B21\u6784\u5EFA\u88AB\u4E2D\u65AD\u3002\u70B9\u51FB\u7EE7\u7EED\u4EE5\u6062\u590D\u3002", - retrieval_build_deps_missing: "\u4F9D\u8D56\u672A\u5B89\u88C5", + "\u5411\u91CF\u5DF2\u8FC7\u671F \u2014 \u5EFA\u8BAE\u91CD\u5EFA", + retrieval_build_deps_missing: + "\u4F9D\u8D56\u7F3A\u5931\u3002\u8BF7\u5B89\u88C5 chromadb \u548C openai\u3002", retrieval_build_runtime_mismatch: - "\u63D2\u4EF6\u4E0E CLI \u7248\u672C\u4E0D\u5339\u914D", + "Python \u8FD0\u884C\u65F6\u7248\u672C\u4E0D\u5339\u914D\u3002", retrieval_stop: "\u505C\u6B62", + retrieval_no_python: "\u672A\u627E\u5230 Python", + retrieval_internal_error: "\u53D1\u751F\u5185\u90E8\u9519\u8BEF", retrieval_force_rebuild: "\u5F3A\u5236\u91CD\u5EFA", retrieval_rebuild_warning: - "\u91CD\u5EFA\u5C06\u66FF\u6362 {n} \u4E2A\u5DF2\u6709\u7684 chunk\u3002\u662F\u5426\u7EE7\u7EED\uFF1F", - retrieval_no_python: - "\u672A\u627E\u5230 Python\u3002\u8BF7\u67E5\u770B\u5B89\u88C5\u6807\u7B7E\u9875\u3002", + "\u91CD\u5EFA\u5C06\u66FF\u6362 {n} \u4E2A\u73B0\u6709\u6587\u672C\u5757\uFF0C\u662F\u5426\u7EE7\u7EED\uFF1F", }, }, - Ue = null; -function jt(u) { + Ge = null; +function Vt(u) { try { let h = u.vault; if (typeof h.getConfig == "function") { @@ -861,87 +874,87 @@ function jt(u) { } catch (h) {} return "en"; } -function lt(u) { - Ue = jt(u) === "zh" ? qe.zh : qe.en; +function ut(u) { + Ge = Vt(u) === "zh" ? Je.zh : Je.en; } function o(u) { - return (Ue && Ue[u]) || qe.en[u] || u; + return (Ge && Ge[u]) || Je.en[u] || u; } -var R = require("obsidian"), - $ = V(require("fs")), - X = V(require("path")), - St = V(require("os")), - K = require("child_process"); -var Pt = V(Je()); -var le = V(require("fs")), - oe = V(require("path")), - pt = V(require("os")), - de = require("child_process"), - Ge = null, - ct = !1; -function N(u, h, e, t) { - let r = e || le, - n = t || de.execFileSync; +var C = require("obsidian"), + V = K(require("fs")), + te = K(require("path")), + Rt = K(require("os")), + G = require("child_process"); +var Ft = K(Xe()); +var he = K(require("fs")), + ue = K(require("path")), + _t = K(require("os")), + ge = require("child_process"), + Ye = null, + ht = !1; +function z(u, h, e, t) { + let r = e || he, + s = t || ge.execFileSync; if (h && h.python_path && h.python_path.trim()) { let i = h.python_path.trim(); if (r.existsSync(i)) return { path: i, source: "manual", extraArgs: [] }; } let a = [ - oe.join(u, ".paperforge-test-venv", "Scripts", "python.exe"), - oe.join(u, ".venv", "Scripts", "python.exe"), - oe.join(u, "venv", "Scripts", "python.exe"), + ue.join(u, ".paperforge-test-venv", "Scripts", "python.exe"), + ue.join(u, ".venv", "Scripts", "python.exe"), + ue.join(u, "venv", "Scripts", "python.exe"), ]; for (let i of a) try { if (r.existsSync(i)) return { path: i, source: "auto-detected", extraArgs: [] }; - } catch (l) {} - let s = [ + } catch (c) {} + let n = [ { path: "py", extraArgs: ["-3"] }, { path: "python", extraArgs: [] }, { path: "python3", extraArgs: [] }, ]; - for (let i of s) + for (let i of n) try { - let l = n(i.path, [...i.extraArgs, "--version"], { + let c = s(i.path, [...i.extraArgs, "--version"], { encoding: "utf-8", timeout: 5e3, windowsHide: !0, }); - if (l && l.toLowerCase().includes("python")) + if (c && c.toLowerCase().includes("python")) return { path: i.path, source: "auto-detected", extraArgs: i.extraArgs, }; - } catch (l) {} + } catch (c) {} return { path: "python", source: "auto-detected", extraArgs: [] }; } -function dt(u, h, e, t, r) { +function ft(u, h, e, t, r) { t === void 0 && (t = 1e4); - let n = r || de.execFile; + let s = r || ge.execFile; return new Promise((a) => { - n( + s( u, ["-c", "import paperforge; print(paperforge.__version__)"], { cwd: e, timeout: t }, - (s, i) => { - if (s) { + (n, i) => { + if (n) { a({ status: "not-installed", pyVersion: null, pluginVersion: h, - error: s.message, + error: n.message, }); return; } - let l = (i && i.trim()) || null; + let c = (i && i.trim()) || null; a( - l === h - ? { status: "match", pyVersion: l, pluginVersion: h, error: null } + c === h + ? { status: "match", pyVersion: c, pluginVersion: h, error: null } : { status: "mismatch", - pyVersion: l, + pyVersion: c, pluginVersion: h, error: null, } @@ -950,7 +963,7 @@ function dt(u, h, e, t, r) { ); }); } -function Xe(u) { +function Qe(u) { let h = String(u), t = { ENOENT: { @@ -1042,42 +1055,42 @@ function Xe(u) { ? { ...t } : { type: "unknown", message: String(u), recoverable: !1 }; } -function ut(u, h, e) { +function gt(u, h, e) { e === void 0 && (e = []); let t = `paperforge==${h}`, r = `git+https://github.com/LLLin000/PaperForge.git@${h}`, - n = [...e, "-m", "pip", "install", "--upgrade", t], + s = [...e, "-m", "pip", "install", "--upgrade", t], a = [...e, "-m", "pip", "install", "--upgrade", r]; - return { cmd: u, url: r, args: a, pypiArgs: n, gitArgs: a, timeout: 12e4 }; + return { cmd: u, url: r, args: a, pypiArgs: s, gitArgs: a, timeout: 12e4 }; } -function ht(u, h, e, t, r, n) { - let a = r || de.spawn; - return new Promise((s) => { +function mt(u, h, e, t, r, s) { + let a = r || ge.spawn; + return new Promise((n) => { let i = Date.now(), - l = { cwd: e, timeout: t, windowsHide: !0 }; - n && (l.env = n); - let c = a(u, h, l), + c = { cwd: e, timeout: t, windowsHide: !0 }; + s && (c.env = s); + let l = a(u, h, c), d = [], - f = []; - (c.stdout.on("data", (p) => { + _ = []; + (l.stdout.on("data", (p) => { d.push(p.toString("utf-8")); }), - c.stderr.on("data", (p) => { - f.push(p.toString("utf-8")); + l.stderr.on("data", (p) => { + _.push(p.toString("utf-8")); }), - c.on("close", (p) => { - s({ + l.on("close", (p) => { + n({ stdout: d.join(""), - stderr: f.join(""), + stderr: _.join(""), exitCode: p, elapsed: Date.now() - i, }); }), - c.on("error", (p) => { - s({ + l.on("error", (p) => { + n({ stdout: d.join(""), stderr: - f.join("") + + _.join("") + ` ` + p.message, @@ -1087,20 +1100,20 @@ function ht(u, h, e, t, r, n) { })); }); } -function Ye() { - if (ct) return Ge; - ct = !0; +function et() { + if (ht) return Ye; + ht = !0; try { let u; if (process.platform === "win32") { let h = process.env.ComSpec || "C:\\Windows\\System32\\cmd.exe"; - u = (0, de.execFileSync)(h, ["/c", "where", "git"], { + u = (0, ge.execFileSync)(h, ["/c", "where", "git"], { timeout: 5e3, windowsHide: !0, encoding: "utf-8", }); } else - u = (0, de.execFileSync)("which", ["git"], { + u = (0, ge.execFileSync)("which", ["git"], { timeout: 5e3, encoding: "utf-8", }); @@ -1111,17 +1124,17 @@ function Ye() { ` )[0] .trim(); - h && (Ge = oe.dirname(h)); + h && (Ye = ue.dirname(h)); } } catch (u) {} - return Ge; + return Ye; } -function be() { +function Se() { let u = { ...process.env }, h = process.platform, - e = pt.homedir(), + e = _t.homedir(), t = [], - r = Ye(); + r = et(); (r && t.push(r), h === "darwin" ? t.push( @@ -1132,44 +1145,44 @@ function be() { ) : h === "linux" && t.push("/usr/local/bin", "/usr/bin", `${e}/.local/bin`)); - let n = u.PATH || ""; - return ((u.PATH = [...t, n].filter(Boolean).join(oe.delimiter)), u); + let s = u.PATH || ""; + return ((u.PATH = [...t, s].filter(Boolean).join(ue.delimiter)), u); } -function _t(u) { +function yt(u) { return String(u) .toLowerCase() .replace(/[^a-z0-9]/g, "") .includes("betterbibtex"); } -function Qe(u) { +function tt(u) { if (!u) return !1; try { - if (!le.existsSync(u)) return !1; - for (let h of le.readdirSync(u)) if (_t(h)) return !0; + if (!he.existsSync(u)) return !1; + for (let h of he.readdirSync(u)) if (yt(h)) return !0; } catch (h) {} return !1; } -function Me(u) { +function $e(u) { if (!u) return !1; try { - if (!le.existsSync(u)) return !1; - for (let h of le.readdirSync(u)) { - let e = oe.join(u, h, "extensions"); + if (!he.existsSync(u)) return !1; + for (let h of he.readdirSync(u)) { + let e = ue.join(u, h, "extensions"); try { - if (!le.existsSync(e)) continue; - for (let t of le.readdirSync(e)) if (_t(t)) return !0; + if (!he.existsSync(e)) continue; + for (let t of he.readdirSync(e)) if (yt(t)) return !0; } catch (t) {} } } catch (h) {} return !1; } -var ge = V(require("fs")), - H = V(require("path")), - et = require("child_process"), - ce = null; -function $t(u, h) { - let e = h || ge, - t = H.join(u, "paperforge.json"), +var xe = K(require("fs")), + W = K(require("path")), + rt = require("child_process"), + _e = null; +function Kt(u, h) { + let e = h || xe, + t = W.join(u, "paperforge.json"), r = { system_dir: "System", resources_dir: "Resources", @@ -1179,144 +1192,144 @@ function $t(u, h) { try { if (!e.existsSync(t)) return { ...r, _warning: "paperforge.json not found; using defaults" }; - let n = e.readFileSync(t, "utf-8"), - a = JSON.parse(n), - s = a.vault_config || {}; + let s = e.readFileSync(t, "utf-8"), + a = JSON.parse(s), + n = a.vault_config || {}; return { - system_dir: s.system_dir || a.system_dir || r.system_dir, - resources_dir: s.resources_dir || a.resources_dir || r.resources_dir, - literature_dir: s.literature_dir || a.literature_dir || r.literature_dir, - base_dir: s.base_dir || a.base_dir || r.base_dir, + system_dir: n.system_dir || a.system_dir || r.system_dir, + resources_dir: n.resources_dir || a.resources_dir || r.resources_dir, + literature_dir: n.literature_dir || a.literature_dir || r.literature_dir, + base_dir: n.base_dir || a.base_dir || r.base_dir, _warning: null, }; - } catch (n) { + } catch (s) { return ( console.warn( "PaperForge: Failed to read paperforge.json, using defaults", - n + s ), { ...r, _warning: "paperforge.json invalid; using defaults" } ); } } -function ne(u, h) { - let e = $t(u, h), - t = H.join(u, e.system_dir, "PaperForge"); +function le(u, h) { + let e = Kt(u, h), + t = W.join(u, e.system_dir, "PaperForge"); return { vault: u, systemDir: t, - indexesDir: H.join(t, "indexes"), - logsDir: H.join(t, "logs"), - dbPath: H.join(t, "indexes", "paperforge.db"), - memoryStatePath: H.join(t, "indexes", "memory-runtime-state.json"), - vectorStatePath: H.join(t, "indexes", "vector-runtime-state.json"), - healthStatePath: H.join(t, "indexes", "runtime-health.json"), - buildStatePath: H.join(t, "indexes", "vector-build-state.json"), - orphanStatePath: H.join(t, "indexes", "sync-orphan-state.json"), - exportsDir: H.join(t, "exports"), - ocrDir: H.join(t, "ocr"), - pluginDataPath: H.join( + indexesDir: W.join(t, "indexes"), + logsDir: W.join(t, "logs"), + dbPath: W.join(t, "indexes", "paperforge.db"), + memoryStatePath: W.join(t, "indexes", "memory-runtime-state.json"), + vectorStatePath: W.join(t, "indexes", "vector-runtime-state.json"), + healthStatePath: W.join(t, "indexes", "runtime-health.json"), + buildStatePath: W.join(t, "indexes", "vector-build-state.json"), + orphanStatePath: W.join(t, "indexes", "sync-orphan-state.json"), + exportsDir: W.join(t, "exports"), + ocrDir: W.join(t, "ocr"), + pluginDataPath: W.join( u, ".obsidian", "plugins", "paperforge", "data.json" ), - pfJsonPath: H.join(u, "paperforge.json"), + pfJsonPath: W.join(u, "paperforge.json"), configWarning: e._warning, }; } -function tt(u) { +function st(u) { try { - return ge.existsSync(u) ? JSON.parse(ge.readFileSync(u, "utf-8")) : null; + return xe.existsSync(u) ? JSON.parse(xe.readFileSync(u, "utf-8")) : null; } catch (h) { return null; } } -function Vt(u) { - let h = ne(u); - return tt(h.memoryStatePath); +function Wt(u) { + let h = le(u); + return st(h.memoryStatePath); } -var xe = null; -function Ne(u) { - let h = ne(u), +var Pe = null; +function Ve(u) { + let h = le(u), e = Date.now(); - if (xe && xe.vaultPath === u && e - xe.ts < 2e3) return xe.result; + if (Pe && Pe.vaultPath === u && e - Pe.ts < 2e3) return Pe.result; let t = "", r = [ - H.join(u, ".paperforge-test-venv", "Scripts", "python.exe"), - H.join(u, ".venv", "Scripts", "python.exe"), - H.join(u, "venv", "Scripts", "python.exe"), + W.join(u, ".paperforge-test-venv", "Scripts", "python.exe"), + W.join(u, ".venv", "Scripts", "python.exe"), + W.join(u, "venv", "Scripts", "python.exe"), ]; for (let a = 0; a < r.length; a++) - if (ge.existsSync(r[a])) { + if (xe.existsSync(r[a])) { t = r[a]; break; } if (t) try { - let a = (0, et.execFileSync)( + let a = (0, rt.execFileSync)( t, ["-m", "paperforge", "--vault", u, "embed", "status", "--json"], { encoding: "utf-8", timeout: 1e4, windowsHide: !0 } ), - s = JSON.parse(a); - if (s.ok && s.data) { - let i = s.data; - return ((xe = { vaultPath: u, result: i, ts: e }), i); + n = JSON.parse(a); + if (n.ok && n.data) { + let i = n.data; + return ((Pe = { vaultPath: u, result: i, ts: e }), i); } } catch (a) {} - let n = tt(h.vectorStatePath); - return ((xe = { vaultPath: u, result: n, ts: e }), n); + let s = st(h.vectorStatePath); + return ((Pe = { vaultPath: u, result: s, ts: e }), s); } -function Te(u) { - let h = ne(u); - return tt(h.healthStatePath); +function Ae(u) { + let h = le(u); + return st(h.healthStatePath); } -function ft(u) { +function vt(u) { var e; - let h = Te(u); + let h = Ae(u); return !!(h && ((e = h.summary) == null ? void 0 : e.status) === "ok"); } -function je(u) { - let h = Vt(u); +function He(u) { + let h = Wt(u); return !h || h.paper_count_db === 0 ? "DB not found. Run paperforge memory build." : "Papers: " + h.paper_count_db + " | " + (h.fresh ? "fresh" : "stale"); } -function Ee(u) { - var t, r, n; - let h = Ne(u); +function Ce(u) { + var t, r, s; + let h = Ve(u); return h ? h.healthy === !1 ? "Vector index unreadable - rebuild required" : "Chunks: " + (((t = h.chunk_count) != null ? t : 0) + ((r = h.body_chunk_count) != null ? r : 0) + - ((n = h.object_chunk_count) != null ? n : 0)) + + ((s = h.object_chunk_count) != null ? s : 0)) + " | " + h.model + " | " + h.mode : "Status unavailable"; } -function ue(u, h) { - if (ce) return ce; +function me(u, h) { + if (_e) return _e; if (h && h.python_path && h.python_path.trim()) { let r = h.python_path.trim(); - if (ge.existsSync(r)) - return ((ce = { path: r, source: "manual", extraArgs: [] }), ce); + if (xe.existsSync(r)) + return ((_e = { path: r, source: "manual", extraArgs: [] }), _e); } let e = [ - H.join(u, ".paperforge-test-venv", "Scripts", "python.exe"), - H.join(u, ".venv", "Scripts", "python.exe"), - H.join(u, "venv", "Scripts", "python.exe"), + W.join(u, ".paperforge-test-venv", "Scripts", "python.exe"), + W.join(u, ".venv", "Scripts", "python.exe"), + W.join(u, "venv", "Scripts", "python.exe"), ]; for (let r = 0; r < e.length; r++) - if (ge.existsSync(e[r])) + if (xe.existsSync(e[r])) return ( - (ce = { path: e[r], source: "auto-detected", extraArgs: [] }), - ce + (_e = { path: e[r], source: "auto-detected", extraArgs: [] }), + _e ); let t = [ { path: "py", extraArgs: ["-3"] }, @@ -1325,57 +1338,57 @@ function ue(u, h) { ]; for (let r = 0; r < t.length; r++) try { - let n = t[r], - a = (0, et.execFileSync)(n.path, n.extraArgs.concat(["--version"]), { + let s = t[r], + a = (0, rt.execFileSync)(s.path, s.extraArgs.concat(["--version"]), { encoding: "utf-8", timeout: 5e3, windowsHide: !0, }); if (a && a.toLowerCase().indexOf("python") !== -1) return ( - (ce = { - path: n.path, + (_e = { + path: s.path, source: "auto-detected", - extraArgs: n.extraArgs, + extraArgs: s.extraArgs, }), - ce + _e ); - } catch (n) {} + } catch (s) {} return ( - (ce = { path: "python", source: "auto-detected", extraArgs: [] }), - ce + (_e = { path: "python", source: "auto-detected", extraArgs: [] }), + _e ); } -function rt(u, h, e) { +function nt(u, h, e) { return !u || typeof u != "object" || !Object.prototype.hasOwnProperty.call(u, h) ? !!e : !!u[h]; } -function gt(u, h, e) { - let t = !rt(u, h, e); +function bt(u, h, e) { + let t = !nt(u, h, e); return (u && typeof u == "object" && (u[h] = t), t); } -var te = require("obsidian"), - ae = V(require("fs")), - yt = V(require("path")), - vt = V(require("https")), - Be = require("child_process"); -function mt(u, h) { +var ae = require("obsidian"), + pe = K(require("fs")), + xt = K(require("path")), + kt = K(require("https")), + Le = require("child_process"); +function Et(u, h) { return !h || !h.trim() ? { blocked: !0, reason: "zotero" } : u ? { blocked: !1 } : { blocked: !0, reason: "ocr" }; } -var st = class extends te.Modal { - constructor(e, t, r, n) { +var at = class extends ae.Modal { + constructor(e, t, r, s) { super(e); this._rowEls = []; - ((this.orphans = t.map((a, s) => ({ ...a, _selected: !0, _idx: s }))), + ((this.orphans = t.map((a, n) => ({ ...a, _selected: !0, _idx: n }))), (this.vaultPath = r), - (this.py = n)); + (this.py = s)); } _updateUI() { let e = this.orphans.filter((t) => t._selected); @@ -1401,45 +1414,45 @@ var st = class extends te.Modal { e.createEl("p", { cls: "paperforge-modal-desc", text: o("orphan_desc") }), (this._rowEls = [])); let t = e.createEl("div", { cls: "paperforge-orphan-list" }); - for (let n of this.orphans) { + for (let s of this.orphans) { let a = t.createEl("div", { cls: "paperforge-orphan-row" + - (n._selected ? "" : " paperforge-orphan-dimmed"), + (s._selected ? "" : " paperforge-orphan-dimmed"), }); this._rowEls.push(a); - let s = a.createEl("div", { cls: "paperforge-orphan-info" }), - i = s.createEl("div", { cls: "paperforge-orphan-header" }); + let n = a.createEl("div", { cls: "paperforge-orphan-info" }), + i = n.createEl("div", { cls: "paperforge-orphan-header" }); i.createEl("span", { cls: "paperforge-orphan-key", - text: n.citation_key || n.key, + text: s.citation_key || s.key, }); - let l = i.createEl("span", { cls: "paperforge-orphan-tags" }); - (l.createEl("span", { - cls: "paperforge-tag " + (n.has_pdf ? "tag-pdf" : "tag-nopdf"), - text: n.has_pdf ? "PDF" : "no PDF", + let c = i.createEl("span", { cls: "paperforge-orphan-tags" }); + (c.createEl("span", { + cls: "paperforge-tag " + (s.has_pdf ? "tag-pdf" : "tag-nopdf"), + text: s.has_pdf ? "PDF" : "no PDF", }), - n.collection_path && - l.createEl("span", { + s.collection_path && + c.createEl("span", { cls: "paperforge-tag tag-collection", - text: n.collection_path, + text: s.collection_path, }), - n.title && - s.createEl("div", { cls: "paperforge-orphan-title", text: n.title })); - let c = []; - (n.authors && c.push(n.authors), - n.year && c.push(n.year), - c.length > 0 && - s.createEl("div", { + s.title && + n.createEl("div", { cls: "paperforge-orphan-title", text: s.title })); + let l = []; + (s.authors && l.push(s.authors), + s.year && l.push(s.year), + l.length > 0 && + n.createEl("div", { cls: "paperforge-orphan-meta", - text: c.join(" \xB7 "), + text: l.join(" \xB7 "), }), - s.createEl("div", { + n.createEl("div", { cls: "paperforge-orphan-explain", text: o("orphan_explain"), }), a.addEventListener("click", () => { - ((n._selected = !n._selected), this._updateUI()); + ((s._selected = !s._selected), this._updateUI()); })); } let r = e.createEl("div", { cls: "paperforge-modal-actions" }); @@ -1448,8 +1461,8 @@ var st = class extends te.Modal { text: "Deselect all", })), this._selectAllBtn.addEventListener("click", () => { - let n = this.orphans.every((a) => a._selected); - for (let a of this.orphans) a._selected = !n; + let s = this.orphans.every((a) => a._selected); + for (let a of this.orphans) a._selected = !s; this._updateUI(); }), (this._countEl = r.createEl("button", { @@ -1460,9 +1473,9 @@ var st = class extends te.Modal { .createEl("button", { cls: "paperforge-step-btn", text: "Keep all" }) .addEventListener("click", () => this.close()), this._countEl.addEventListener("click", () => { - let n = this.orphans.filter((s) => s._selected); - if (n.length === 0) { - new te.Notice(o("orphan_none_selected")); + let s = this.orphans.filter((n) => n._selected); + if (s.length === 0) { + new ae.Notice(o("orphan_none_selected")); return; } if ( @@ -1471,11 +1484,11 @@ var st = class extends te.Modal { this._selectAllBtn.setAttr("disabled", ""), !this.py || !this.py.path) ) { - (new te.Notice("PaperForge: Python not found"), this.close()); + (new ae.Notice("PaperForge: Python not found"), this.close()); return; } - let a = n.map((s) => s.key); - (0, Be.execFile)( + let a = s.map((n) => n.key); + (0, Le.execFile)( this.py.path, [ ...this.py.extraArgs, @@ -1489,17 +1502,17 @@ var st = class extends te.Modal { ...a, ], { cwd: this.vaultPath, timeout: 6e4 }, - (s, i) => { - if (s) { - (new te.Notice("PaperForge: prune failed"), this.close()); + (n, i) => { + if (n) { + (new ae.Notice("PaperForge: prune failed"), this.close()); return; } try { - let l = JSON.parse(i), - c = (l.data && l.data.deleted) || []; - new te.Notice("Deleted " + c.length + " orphan workspace(s)"); - } catch (l) { - new te.Notice("PaperForge: prune done"); + let c = JSON.parse(i), + l = (c.data && c.data.deleted) || []; + new ae.Notice("Deleted " + l.length + " orphan workspace(s)"); + } catch (c) { + new ae.Notice("PaperForge: prune done"); } this.close(); } @@ -1510,28 +1523,28 @@ var st = class extends te.Modal { this.contentEl.empty(); } }; -function ze(u, h, e) { +function Ke(u, h, e) { console.log("[PF] checkOrphanState called"); try { - let r = ne(e).orphanStatePath; - if (!ae.existsSync(r)) { + let r = le(e).orphanStatePath; + if (!pe.existsSync(r)) { console.log("[PF] orphan file NOT FOUND"); return; } console.log("[PF] orphan file FOUND"); - let n = ae.readFileSync(r, "utf-8"), - s = JSON.parse(n).orphans || []; - if ((console.log("[PF] orphans count:", s.length), s.length === 0)) return; - let i = ue(e, h.settings); + let s = pe.readFileSync(r, "utf-8"), + n = JSON.parse(s).orphans || []; + if ((console.log("[PF] orphans count:", n.length), n.length === 0)) return; + let i = me(e, h.settings); (console.log("[PF] py.path:", i ? i.path : "null"), - new st(u, s, e, i).open(), - ae.unlinkSync(r), + new at(u, n, e, i).open(), + pe.unlinkSync(r), console.log("[PF] orphan file cleaned")); } catch (t) { console.log("[PF] checkOrphanState exception:", t.message || t); } } -var De = class extends te.Modal { +var Oe = class extends ae.Modal { constructor(e, t) { super(e); this._pendingSave = null; @@ -1561,13 +1574,13 @@ var De = class extends te.Modal { o("wizard_step5"), ], t = this.contentEl.createEl("div", { cls: "paperforge-step-bar" }); - e.forEach((r, n) => { - let a = n + 1, - s = t.createEl("div", { + e.forEach((r, s) => { + let a = s + 1, + n = t.createEl("div", { cls: `paperforge-step-dot ${a === this._step ? "active" : ""} ${a < this._step ? "done" : ""}`, }); - (s.createEl("span", { cls: "paperforge-step-num", text: `${a}` }), - s.createEl("span", { cls: "paperforge-step-label", text: r })); + (n.createEl("span", { cls: "paperforge-step-num", text: `${a}` }), + n.createEl("span", { cls: "paperforge-step-label", text: r })); }); } _renderStepContent() { @@ -1626,33 +1639,33 @@ var De = class extends te.Modal { } _validateStep3() { let e = this.plugin.settings, - t = mt(this._apiKeyValidated, e.zotero_data_dir); + t = Et(this._apiKeyValidated, e.zotero_data_dir); if (t.reason === "ocr") return t; let r = (e.zotero_data_dir || "").trim(); if (!r) return ( - new te.Notice( + new ae.Notice( "Zotero \u6570\u636E\u76EE\u5F55\u4E3A\u5FC5\u586B\u9879\uFF0C\u8BF7\u586B\u5199\u8DEF\u5F84" ), { blocked: !0, reason: "zotero" } ); - if (!ae.existsSync(r)) + if (!pe.existsSync(r)) return ( - new te.Notice( + new ae.Notice( "Zotero \u6570\u636E\u76EE\u5F55\u8DEF\u5F84\u4E0D\u5B58\u5728" ), { blocked: !0, reason: "zotero" } ); - if (!ae.statSync(r).isDirectory()) + if (!pe.statSync(r).isDirectory()) return ( - new te.Notice( + new ae.Notice( "Zotero \u6570\u636E\u76EE\u5F55\u8DEF\u5F84\u4E0D\u662F\u4E00\u4E2A\u76EE\u5F55" ), { blocked: !0, reason: "zotero" } ); - let n = yt.join(r, "storage"); - return !ae.existsSync(n) || !ae.statSync(n).isDirectory() - ? (new te.Notice( + let s = xt.join(r, "storage"); + return !pe.existsSync(s) || !pe.statSync(s).isDirectory() + ? (new ae.Notice( "Zotero \u6570\u636E\u76EE\u5F55\u4E2D\u672A\u627E\u5230 storage/ \u5B50\u76EE\u5F55" ), { blocked: !0, reason: "zotero" }) @@ -1663,11 +1676,11 @@ var De = class extends te.Modal { e.createEl("p", { text: o("wizard_intro") })); let t = this.plugin.settings, r = this.app.vault.adapter.basePath, - n = e.createEl("div", { cls: "paperforge-dir-tree" }), - a = n.createEl("div", { cls: "paperforge-dir-node root" }); + s = e.createEl("div", { cls: "paperforge-dir-tree" }), + a = s.createEl("div", { cls: "paperforge-dir-node root" }); a.textContent = `\u{1F4C1} Vault (${r})`; - let s = n.createEl("div", { cls: "paperforge-dir-children" }), - i = s.createEl("div", { cls: "paperforge-dir-node folder" }); + let n = s.createEl("div", { cls: "paperforge-dir-children" }), + i = n.createEl("div", { cls: "paperforge-dir-node folder" }); ((i.textContent = `\u{1F4C1} ${t.resources_dir || "Resources"}/ \u2014 \u6587\u732E\u5361\u7247\u76EE\u5F55\uFF08Base \u6570\u636E\u6765\u6E90\uFF09`), i .createEl("div", { cls: "paperforge-dir-children" }) @@ -1675,11 +1688,11 @@ var De = class extends te.Modal { cls: "paperforge-dir-node file", text: `\u{1F4C1} ${t.literature_dir || "Literature"}/ \u2014 \u6587\u732E\u5361\u7247`, }), - s.createEl("div", { + n.createEl("div", { cls: "paperforge-dir-node folder", text: `\u{1F4C1} ${t.base_dir || "Bases"}/ \u2014 \u6570\u636E\u7BA1\u7406\u9762\u677F`, }), - s.createEl("div", { + n.createEl("div", { cls: "paperforge-dir-node folder", text: `\u{1F4C1} ${t.system_dir || "System"}/ \u2014 Zotero \u8F6F\u94FE\u63A5 + PaperForge \u7CFB\u7EDF\u6587\u4EF6\u5939`, }), @@ -1691,7 +1704,7 @@ var De = class extends te.Modal { text: o("wizard_safety"), cls: "paperforge-modal-hint", })); - let c = e.createEl("div", { cls: "paperforge-summary" }), + let l = e.createEl("div", { cls: "paperforge-summary" }), d = [ { label: o("dir_resources"), @@ -1704,10 +1717,10 @@ var De = class extends te.Modal { { label: o("dir_base"), val: `${r}/${t.base_dir || "Bases"}` }, { label: o("dir_system"), val: `${r}/${t.system_dir || "System"}` }, ]; - for (let f of d) { - let p = c.createEl("div", { cls: "paperforge-summary-row" }); - (p.createEl("span", { cls: "paperforge-summary-label", text: f.label }), - p.createEl("span", { cls: "paperforge-summary-value", text: f.val })); + for (let _ of d) { + let p = l.createEl("div", { cls: "paperforge-summary-row" }); + (p.createEl("span", { cls: "paperforge-summary-label", text: _.label }), + p.createEl("span", { cls: "paperforge-summary-value", text: _.val })); } } _stepDirectories(e) { @@ -1760,7 +1773,7 @@ var De = class extends te.Modal { text: o("wizard_safety"), cls: "paperforge-modal-hint", })); - let n = e.createEl("div", { cls: "paperforge-summary" }), + let s = e.createEl("div", { cls: "paperforge-summary" }), a = [ { label: o("dir_resources"), val: `${r}/${t.resources_dir || ""}` }, { @@ -1770,10 +1783,10 @@ var De = class extends te.Modal { { label: o("dir_system"), val: `${r}/${t.system_dir || ""}` }, { label: o("dir_base"), val: `${r}/${t.base_dir || ""}` }, ]; - for (let s of a) { - let i = n.createEl("div", { cls: "paperforge-summary-row" }); - (i.createEl("span", { cls: "paperforge-summary-label", text: s.label }), - i.createEl("span", { cls: "paperforge-summary-value", text: s.val })); + for (let n of a) { + let i = s.createEl("div", { cls: "paperforge-summary-row" }); + (i.createEl("span", { cls: "paperforge-summary-label", text: n.label }), + i.createEl("span", { cls: "paperforge-summary-value", text: n.val })); } } _stepKeys(e) { @@ -1798,15 +1811,15 @@ var De = class extends te.Modal { { key: "gemini", name: "Gemini CLI" }, { key: "cline", name: "Cline" }, ], - n = e.createEl("div", { cls: "paperforge-modal-field" }); - n.createEl("label", { + s = e.createEl("div", { cls: "paperforge-modal-field" }); + s.createEl("label", { cls: "paperforge-modal-label", text: o("label_agent"), }); - let a = n.createEl("select", { cls: "paperforge-modal-select" }); - for (let f of r) { - let p = a.createEl("option", { text: f.name, attr: { value: f.key } }); - f.key === (t.agent_platform || "opencode") && (p.selected = !0); + let a = s.createEl("select", { cls: "paperforge-modal-select" }); + for (let _ of r) { + let p = a.createEl("option", { text: _.name, attr: { value: _.key } }); + _.key === (t.agent_platform || "opencode") && (p.selected = !0); } (a.addEventListener("change", () => { ((t.agent_platform = a.value), @@ -1819,26 +1832,26 @@ var De = class extends te.Modal { text: o("wizard_keys_hint"), cls: "paperforge-modal-hint", })); - let s = e.createEl("div", { cls: "paperforge-modal-field" }); - s.createEl("label", { + let n = e.createEl("div", { cls: "paperforge-modal-field" }); + n.createEl("label", { cls: "paperforge-modal-label", text: o("field_paddleocr"), }); - let i = s.createEl("input", { + let i = n.createEl("input", { cls: "paperforge-modal-input", attr: { type: "password", placeholder: "API Key" }, }); ((i.value = t.paddleocr_api_key || ""), (this._apiKeyValidated = !1), - (this._apiKeyStatus = s.createEl("span", { + (this._apiKeyStatus = n.createEl("span", { cls: "paperforge-apikey-status", text: "", }))); - let l = s.createEl("button", { + let c = n.createEl("button", { cls: "paperforge-step-btn", text: "\u9A8C\u8BC1", }); - (l.addEventListener("click", () => this._validateApiKey(i.value, l)), + (c.addEventListener("click", () => this._validateApiKey(i.value, c)), i.addEventListener("input", () => { ((t.paddleocr_api_key = i.value), (this._apiKeyValidated = !1), @@ -1853,12 +1866,12 @@ var De = class extends te.Modal { text: o("wizard_api_hint_skip"), cls: "paperforge-modal-hint", })); - let c = e.createEl("div", { cls: "paperforge-modal-field" }); - c.createEl("label", { + let l = e.createEl("div", { cls: "paperforge-modal-field" }); + l.createEl("label", { cls: "paperforge-modal-label", text: o("field_zotero_data"), }); - let d = c.createEl("input", { + let d = l.createEl("input", { cls: "paperforge-modal-input", attr: { type: "text", placeholder: o("field_zotero_placeholder") }, }); @@ -1883,7 +1896,7 @@ var De = class extends te.Modal { (this._apiKeyStatus.textContent = "\u6B63\u5728\u9A8C\u8BC1\u2026"), (this._apiKeyStatus.className = "paperforge-apikey-status")); let r = JSON.stringify({ model: "PaddleOCR-VL-1.5" }), - n = { + s = { hostname: "paddleocr.aistudio-app.com", path: "/api/v2/ocr/jobs", method: "POST", @@ -1894,20 +1907,20 @@ var De = class extends te.Modal { }, timeout: 1e4, }, - a = vt.request(n, (s) => { + a = kt.request(s, (n) => { ((t.disabled = !1), (t.textContent = "\u9A8C\u8BC1")); let i = ""; - (s.on("data", (l) => (i += l)), - s.on("end", () => { + (n.on("data", (c) => (i += c)), + n.on("end", () => { try { - let l = JSON.parse(i); - s.statusCode === 400 && l.code === 10001 + let c = JSON.parse(i); + n.statusCode === 400 && c.code === 10001 ? ((this._apiKeyStatus.textContent = "\u2713 \u5BC6\u94A5\u6709\u6548"), (this._apiKeyStatus.className = "paperforge-apikey-status ok"), (this._apiKeyValidated = !0)) - : s.statusCode === 401 + : n.statusCode === 401 ? ((this._apiKeyStatus.textContent = "\u9A8C\u8BC1\u5931\u8D25\uFF1A\u5BC6\u94A5\u65E0\u6548\u3002\u53EF\u70B9\u4E0B\u4E00\u6B65\u8DF3\u8FC7\uFF0C\u7B49\u4F1A\u513F\u518D\u914D\u7F6E\u3002"), (this._apiKeyStatus.className = @@ -1915,12 +1928,12 @@ var De = class extends te.Modal { (this._apiKeyValidated = !1)) : ((this._apiKeyStatus.textContent = "\u9A8C\u8BC1\u5931\u8D25\uFF1AAPI \u8FD4\u56DE " + - s.statusCode + + n.statusCode + "\u3002\u53EF\u70B9\u4E0B\u4E00\u6B65\u8DF3\u8FC7\uFF0C\u7B49\u4F1A\u513F\u518D\u914D\u7F6E\u3002"), (this._apiKeyStatus.className = "paperforge-apikey-status error"), (this._apiKeyValidated = !1)); - } catch (l) { + } catch (c) { ((this._apiKeyStatus.textContent = "\u9A8C\u8BC1\u5931\u8D25\uFF1A\u65E0\u6CD5\u89E3\u6790\u54CD\u5E94\u3002\u53EF\u70B9\u4E0B\u4E00\u6B65\u8DF3\u8FC7\uFF0C\u7B49\u4F1A\u513F\u518D\u914D\u7F6E\u3002"), (this._apiKeyStatus.className = @@ -1929,12 +1942,12 @@ var De = class extends te.Modal { } })); }); - (a.on("error", (s) => { + (a.on("error", (n) => { ((t.disabled = !1), (t.textContent = "\u9A8C\u8BC1"), (this._apiKeyStatus.textContent = "\u9A8C\u8BC1\u5931\u8D25\uFF1A\u65E0\u6CD5\u8FDE\u63A5 (" + - s.message + + n.message + ")\u3002\u53EF\u70B9\u4E0B\u4E00\u6B65\u8DF3\u8FC7\uFF0C\u7B49\u4F1A\u513F\u518D\u914D\u7F6E\u3002"), (this._apiKeyStatus.className = "paperforge-apikey-status error"), (this._apiKeyValidated = !1)); @@ -1965,43 +1978,43 @@ var De = class extends te.Modal { ((this._showSkipConfirm = !1), this._render()); })); } - _modalField(e, t, r, n) { + _modalField(e, t, r, s) { let a = e.createEl("div", { cls: "paperforge-modal-field" }); a.createEl("label", { cls: "paperforge-modal-label", text: t }); - let s = a.createEl("input", { + let n = a.createEl("input", { cls: "paperforge-modal-input", attr: { type: "text" }, }); - ((s.value = r), (s.disabled = !!n)); + ((n.value = r), (n.disabled = !!s)); } - _modalInput(e, t, r, n, a) { - let s = e.createEl("div", { cls: "paperforge-modal-field" }); - s.createEl("label", { cls: "paperforge-modal-label", text: t }); - let i = s.createEl("input", { + _modalInput(e, t, r, s, a) { + let n = e.createEl("div", { cls: "paperforge-modal-field" }); + n.createEl("label", { cls: "paperforge-modal-label", text: t }); + let i = n.createEl("input", { cls: "paperforge-modal-input", attr: { type: "text", placeholder: a || "" }, }); - i.value = n; - let l = this.plugin.settings; + i.value = s; + let c = this.plugin.settings; i.addEventListener("input", () => { - ((l[r] = i.value), + ((c[r] = i.value), this._pendingSave && clearTimeout(this._pendingSave), (this._pendingSave = setTimeout(() => { (this.plugin.saveSettings(), (this._pendingSave = null)); }, 500))); }); } - _modalSecret(e, t, r, n, a) { - let s = e.createEl("div", { cls: "paperforge-modal-field" }); - s.createEl("label", { cls: "paperforge-modal-label", text: t }); - let i = s.createEl("input", { + _modalSecret(e, t, r, s, a) { + let n = e.createEl("div", { cls: "paperforge-modal-field" }); + n.createEl("label", { cls: "paperforge-modal-label", text: t }); + let i = n.createEl("input", { cls: "paperforge-modal-input", attr: { type: "password", placeholder: a || "" }, }); - i.value = n; - let l = this.plugin.settings; + i.value = s; + let c = this.plugin.settings; i.addEventListener("input", () => { - ((l[r] = i.value), + ((c[r] = i.value), this._pendingSave && clearTimeout(this._pendingSave), (this._pendingSave = setTimeout(() => { (this.plugin.saveSettings(), (this._pendingSave = null)); @@ -2020,7 +2033,7 @@ var De = class extends te.Modal { t.addEventListener("click", () => this._runInstall(t)); } async _runInstall(e) { - var s, i, l, c, d, f; + var n, i, c, l, d, _; ((e.disabled = !0), (e.textContent = o("install_btn_running")), this._installLog.setText( @@ -2038,36 +2051,36 @@ var De = class extends te.Modal { (e.textContent = o("install_btn_retry"))); return; } - let n = (p, m = {}) => - new Promise((y, x) => { - let { path: _, extraArgs: b = [] } = N( + let s = (p, g = {}) => + new Promise((y, E) => { + let { path: f, extraArgs: b = [] } = z( t.vault_path.trim(), this.plugin.settings, void 0, void 0 ), - E = (0, Be.spawn)(_, [...b, ...p], { + w = (0, Le.spawn)(f, [...b, ...p], { cwd: t.vault_path.trim(), - env: be(), + env: Se(), timeout: 12e4, - ...m, + ...g, }), - k = "", - g = ""; - (E.stdout.on("data", (v) => { - let w = v.toString("utf-8"); - ((k += w), m.logStdout && this._processSetupOutput(w)); + x = "", + m = ""; + (w.stdout.on("data", (v) => { + let k = v.toString("utf-8"); + ((x += k), g.logStdout && this._processSetupOutput(k)); }), - E.stderr.on("data", (v) => { - let w = v.toString("utf-8"); - ((g += w), this._log("[stderr] " + w.trim())); + w.stderr.on("data", (v) => { + let k = v.toString("utf-8"); + ((m += k), this._log("[stderr] " + k.trim())); }), - E.on("close", (v) => { + w.on("close", (v) => { v === 0 - ? y({ stdout: k, stderr: g }) - : x(new Error(g.trim() || k.trim() || `exit code ${v}`)); + ? y({ stdout: x, stderr: m }) + : E(new Error(m.trim() || x.trim() || `exit code ${v}`)); }), - E.on("error", (v) => x(v))); + w.on("error", (v) => E(v))); }), a = [ "-m", @@ -2096,34 +2109,34 @@ var De = class extends te.Modal { try { let p = !0; try { - await n(["-c", "import paperforge"]); - } catch (m) { + await s(["-c", "import paperforge"]); + } catch (g) { p = !1; } if (!p) { this._log(o("install_bootstrapping")); - let m = this.plugin.manifest.version; - this._log(`[install] Trying PyPI: pip install paperforge==${m}`); + let g = this.plugin.manifest.version; + this._log(`[install] Trying PyPI: pip install paperforge==${g}`); let y = ["-m", "pip", "install", "--upgrade"]; (process.platform !== "win32" && y.push("--user"), - y.push(`paperforge==${m}`)); + y.push(`paperforge==${g}`)); try { - await n(y, { logStdout: !0 }); - } catch (x) { + await s(y, { logStdout: !0 }); + } catch (E) { (this._log( - `[install] PyPI failed, falling back to git: git+https://...@v${m}` + `[install] PyPI failed, falling back to git: git+https://...@v${g}` ), console.warn( "[PaperForge] PyPI install failed, falling back to git:", - (s = x.message) == null ? void 0 : s.slice(0, 200) + (n = E.message) == null ? void 0 : n.slice(0, 200) )); - let _ = ["-m", "pip", "install", "--upgrade"]; - (process.platform !== "win32" && _.push("--user"), - _.push(`git+https://github.com/LLLin000/PaperForge.git@v${m}`), - await n(_, { logStdout: !0 })); + let f = ["-m", "pip", "install", "--upgrade"]; + (process.platform !== "win32" && f.push("--user"), + f.push(`git+https://github.com/LLLin000/PaperForge.git@v${g}`), + await s(f, { logStdout: !0 })); } } - (await n(a, { logStdout: !0, env: be() }), + (await s(a, { logStdout: !0, env: Se() }), this._log(o("install_complete")), (t.setup_complete = !0), await this.plugin.saveSettings(), @@ -2132,8 +2145,8 @@ var De = class extends te.Modal { }, 800)); } catch (p) { console.error("PaperForge setup failed:", p.message); - let m = this._formatSetupError(p.message); - this._log(o("install_failed") + m); + let g = this._formatSetupError(p.message); + this._log(o("install_failed") + g); let y = (i = this._installLog.parentElement) == null ? void 0 @@ -2142,49 +2155,49 @@ var De = class extends te.Modal { text: o("error_copy_diagnostic") || "Copy diagnostic", }); if (y) { - let x = p.message, - _ = - ((c = (l = this.plugin) == null ? void 0 : l.settings) == null + let E = p.message, + f = + ((l = (c = this.plugin) == null ? void 0 : c.settings) == null ? void 0 - : c.python_path) || "auto", + : l.python_path) || "auto", b = - ((f = (d = this.plugin) == null ? void 0 : d.manifest) == null + ((_ = (d = this.plugin) == null ? void 0 : d.manifest) == null ? void 0 - : f.version) || "?", - E = process.platform + " " + process.arch, - k, - g; + : _.version) || "?", + w = process.platform + " " + process.arch, + x, + m; try { - k = Ye() || "(not found)"; + x = et() || "(not found)"; } catch (P) { - k = "(error)"; + x = "(error)"; } try { - g = N(t.vault_path.trim(), this.plugin.settings, void 0, void 0); + m = z(t.vault_path.trim(), this.plugin.settings, void 0, void 0); } catch (P) { - g = null; + m = null; } let v = (process.env.PATH || "").length, - w = (process.env.PATH || "").toLowerCase().includes("git"), - T = [ + k = (process.env.PATH || "").toLowerCase().includes("git"), + R = [ "[PaperForge Diagnostic]", - "Category: " + m, + "Category: " + g, "Plugin version: " + b, - "Python: " + _, - "Resolved Python: " + ((g == null ? void 0 : g.path) || "?"), - "OS: " + E, + "Python: " + f, + "Resolved Python: " + ((m == null ? void 0 : m.path) || "?"), + "OS: " + w, "Vault path: " + (t.vault_path || "?"), "--- Git ---", - "Git dir (resolved): " + k, + "Git dir (resolved): " + x, "PATH length: " + v + " chars", - "PATH contains git: " + w, + "PATH contains git: " + k, "--- Raw error ---", - x.slice(0, 2e3), + E.slice(0, 2e3), ].join(` `); y.addEventListener("click", () => { navigator.clipboard - .writeText(T) + .writeText(R) .then(() => { (y.setText(o("error_copied") || "Copied!"), setTimeout(() => { @@ -2192,7 +2205,7 @@ var De = class extends te.Modal { }, 3e3)); }) .catch(() => { - new te.Notice("[!!] Clipboard write failed", 6e3); + new ae.Notice("[!!] Clipboard write failed", 6e3); }); }); } @@ -2234,11 +2247,11 @@ var De = class extends te.Modal { .filter(Boolean); for (let r of t) if (r.includes("[*]") || r.includes("[OK]") || r.includes("[FAIL]")) { - let n = r + let s = r .replace(/^\[\*\].*\d+:?\s*/, "") .replace(/^\[OK\]\s*/, "") .replace(/^\[FAIL\]\s*/, ""); - this._log(" " + n); + this._log(" " + s); } } _formatSetupError(e) { @@ -2276,7 +2289,7 @@ var De = class extends te.Modal { { match: /ENOENT/i, msg: "Path not found" }, { match: /timeout|timed out/i, msg: "Timeout" }, ]; - for (let n of t) if (n.match.test(e)) return n.msg; + for (let s of t) if (s.match.test(e)) return s.msg; return ( e .split( @@ -2297,16 +2310,16 @@ var De = class extends te.Modal { text: o("complete_summary"), }); let r = this.plugin.settings, - n = this.app.vault.adapter.basePath, + s = this.app.vault.adapter.basePath, a = [ - { label: o("dir_vault"), val: n }, - { label: o("dir_resources"), val: `${n}/${r.resources_dir}` }, + { label: o("dir_vault"), val: s }, + { label: o("dir_resources"), val: `${s}/${r.resources_dir}` }, { label: o("dir_notes"), - val: `${n}/${r.resources_dir}/${r.literature_dir}`, + val: `${s}/${r.resources_dir}/${r.literature_dir}`, }, - { label: o("dir_base"), val: `${n}/${r.base_dir}` }, - { label: o("dir_system"), val: `${n}/${r.system_dir}` }, + { label: o("dir_base"), val: `${s}/${r.base_dir}` }, + { label: o("dir_system"), val: `${s}/${r.system_dir}` }, { label: "API Key", val: r.paddleocr_api_key ? o("api_key_set") : o("api_key_missing"), @@ -2317,121 +2330,154 @@ var De = class extends te.Modal { }, ]; for (let d of a) { - let f = t.createEl("div", { cls: "paperforge-summary-row" }); - (f.createEl("span", { cls: "paperforge-summary-label", text: d.label }), - f.createEl("span", { cls: "paperforge-summary-value", text: d.val })); + let _ = t.createEl("div", { cls: "paperforge-summary-row" }); + (_.createEl("span", { cls: "paperforge-summary-label", text: d.label }), + _.createEl("span", { cls: "paperforge-summary-value", text: d.val })); } - let s = t.createEl("div", { cls: "paperforge-summary-row" }); - s.createEl("span", { cls: "paperforge-summary-label", text: "PaperForge" }); - let i = s.createEl("span", { + let n = t.createEl("div", { cls: "paperforge-summary-row" }); + n.createEl("span", { cls: "paperforge-summary-label", text: "PaperForge" }); + let i = n.createEl("span", { cls: "paperforge-summary-value", text: "\u2014", }); { - let d = n, - { path: f, extraArgs: p = [] } = N( + let d = s, + { path: _, extraArgs: p = [] } = z( d, this.plugin.settings, void 0, void 0 ); - (0, Be.execFile)( - f, + (0, Le.execFile)( + _, [...p, "-c", "import paperforge; print(paperforge.__version__)"], { cwd: d, timeout: 1e4 }, - (m, y) => { - !m && y && (i.textContent = "v" + y.trim()); + (g, y) => { + !g && y && (i.textContent = "v" + y.trim()); } ); } for (let d of a) { - let f = t.createEl("div", { cls: "paperforge-summary-row" }); - (f.createEl("span", { cls: "paperforge-summary-label", text: d.label }), - f.createEl("span", { cls: "paperforge-summary-value", text: d.val })); + let _ = t.createEl("div", { cls: "paperforge-summary-row" }); + (_.createEl("span", { cls: "paperforge-summary-label", text: d.label }), + _.createEl("span", { cls: "paperforge-summary-value", text: d.val })); } e.createEl("h3", { text: o("complete_next") }); - let l = e.createEl("div", { cls: "paperforge-nextsteps" }), - c = [ + let c = e.createEl("div", { cls: "paperforge-nextsteps" }), + l = [ [o("complete_step4"), o("complete_step4_desc")], [ "", - `${o("complete_export_path")} ${n}/${r.system_dir}/PaperForge/exports/`, + `${o("complete_export_path")} ${s}/${r.system_dir}/PaperForge/exports/`, ], [o("complete_step1"), o("complete_step1_desc")], [o("complete_step2"), o("complete_step2_desc")], [o("complete_step3"), o("complete_step3_desc")], ]; - for (let [d, f] of c) { - let p = l.createEl("div", { cls: "paperforge-nextstep-item" }); - (d && p.createEl("strong", { text: d }), p.createEl("span", { text: f })); + for (let [d, _] of l) { + let p = c.createEl("div", { cls: "paperforge-nextstep-item" }); + (d && p.createEl("strong", { text: d }), p.createEl("span", { text: _ })); } } }; -var ke = V(require("fs")), - $e = V(require("path")), - xt = require("child_process"); -function Et(u) { - return $e.join(u, "System", "PaperForge", "cache", "ocr_maintenance.json"); +var Re = K(require("fs")), + We = K(require("path")), + St = require("child_process"); +function Pt(u) { + return We.join(u, "System", "PaperForge", "cache", "ocr_maintenance.json"); } -function kt(u) { +function Ct(u) { try { - let h = Et(u), - e = ke.readFileSync(h, "utf-8"); + let h = Pt(u), + e = Re.readFileSync(h, "utf-8"); return JSON.parse(e); } catch (h) { return null; } } -function Ve(u, h) { - let e = Et(u), - t = $e.dirname(e); - (ke.mkdirSync(t, { recursive: !0 }), - ke.writeFileSync(e, JSON.stringify(h, null, 2), "utf-8")); +function Ie(u, h) { + let e = Pt(u), + t = We.dirname(e); + (Re.mkdirSync(t, { recursive: !0 }), + Re.writeFileSync(e, JSON.stringify(h, null, 2), "utf-8")); } -function bt(u, h, e) { +function wt(u, h, e) { return new Promise((t, r) => { - (0, xt.execFile)(u, h, e, (n, a) => { - n ? r(n) : t(a); + (0, St.execFile)(u, h, e, (s, a) => { + s ? r(s) : t(a); }); }); } -async function wt(u, h, e, t) { - let r = await bt(h, [...e, "-m", "paperforge", "ocr", "list", "--manifest"], { +async function it(u, h, e, t) { + let r = await wt(h, [...e, "-m", "paperforge", "ocr", "list", "--manifest"], { cwd: u, timeout: 3e4, }), - n = JSON.parse(r); + s = JSON.parse(r); if (t) { let d = Object.keys(t.manifest), - f = Object.keys(n); - if (d.length === f.length && d.every((m) => t.manifest[m] === n[m])) + _ = Object.keys(s); + if (d.length === _.length && d.every((g) => t.manifest[g] === s[g])) return { data: Object.values(t.papers).filter((y) => y.visible_in_maintenance), changed: !1, }; } - let a = Object.keys(n).filter( - (d) => !(t != null && t.manifest[d]) || t.manifest[d] !== n[d] + let a = Object.keys(s).filter( + (d) => !(t != null && t.manifest[d]) || t.manifest[d] !== s[d] ), - s = await bt( + n = await wt( h, [...e, "-m", "paperforge", "ocr", "list", "--json", "--keys", ...a], { cwd: u, timeout: 3e4 } ), - i = JSON.parse(s), - l = { manifest: n, papers: {}, cached_at: new Date().toISOString() }; + i = JSON.parse(n), + c = { manifest: s, papers: {}, cached_at: new Date().toISOString() }; if (t != null && t.papers) - for (let d of Object.keys(n)) t.papers[d] && (l.papers[d] = t.papers[d]); - for (let d of i) l.papers[d.key] = d; + for (let d of Object.keys(s)) t.papers[d] && (c.papers[d] = t.papers[d]); + for (let d of i) c.papers[d.key] = d; return ( - Ve(u, l), + Ie(u, c), { - data: Object.values(l.papers).filter((d) => d.visible_in_maintenance), + data: Object.values(c.papers).filter((d) => d.visible_in_maintenance), changed: !0, } ); } -var He = class extends R.PluginSettingTab { +var Zt = ["EMBED", "OCR_REBUILD", "OCR_REDO"]; +function ot(u, h) { + var a, n; + let t = (h + u).split(` +`), + r = (a = t.pop()) != null ? a : "", + s = []; + for (let i of t) + for (let c of Zt) { + let l = c.length; + if (i.startsWith(c + "_START:")) { + let d = parseInt(i.slice(l + 7), 10) || 0; + s.push({ prefix: c, event: "START", total: d }); + break; + } + if (i.startsWith(c + "_PROGRESS:")) { + let _ = i.slice(l + 10).split(":"); + s.push({ + prefix: c, + event: "PROGRESS", + current: parseInt(_[0], 10) || 0, + total: parseInt(_[1], 10) || 0, + key: (n = _[2]) != null ? n : "", + }); + break; + } + if (i === c + "_DONE" || i.startsWith(c + "_DONE:")) { + s.push({ prefix: c, event: "DONE" }); + break; + } + } + return { events: s, buffer: r }; +} +var Ze = class extends C.PluginSettingTab { constructor(e, t) { super(e, t); this._saveTimeout = null; @@ -2498,7 +2544,7 @@ var He = class extends R.PluginSettingTab { { id: "maintenance", label: o("tab_maintenance") || "\u7EF4\u62A4" }, { id: "release-notes", label: "\u66F4\u65B0\u4E0E\u624B\u518C" }, ], - n = {}; + s = {}; (r.forEach((a) => { t.createEl("button", { cls: @@ -2510,73 +2556,73 @@ var He = class extends R.PluginSettingTab { }); }), r.forEach((a) => { - n[a.id] = e.createDiv({ + s[a.id] = e.createDiv({ cls: "paperforge-tab-content" + (a.id === this.activeTab ? " paperforge-tab-content--active" : ""), }); }), this.activeTab === "setup" - ? this._renderSetupTab(n.setup) + ? this._renderSetupTab(s.setup) : this.activeTab === "features" - ? this._renderFeaturesTab(n.features) + ? this._renderFeaturesTab(s.features) : this.activeTab === "maintenance" - ? this._renderMaintenanceTab(n.maintenance) - : this._renderReleaseNotesTab(n["release-notes"])); + ? this._renderMaintenanceTab(s.maintenance) + : this._renderReleaseNotesTab(s["release-notes"])); } _renderSetupTab(e) { let t = this.app.vault.adapter.basePath; (this.plugin.settings.vault_path || ((this.plugin.settings.vault_path = t), this._debouncedSave()), this.plugin.settings.setup_complete && - ($.existsSync(X.join(t, "paperforge.json")) || + (V.existsSync(te.join(t, "paperforge.json")) || ((this.plugin.settings.setup_complete = !1), this._debouncedSave())), e.createEl("h2", { text: o("header_title") || "PaperForge" }), e.createEl("p", { text: o("desc"), cls: "paperforge-settings-desc" })); - let n = e + let s = e .createEl("div", { cls: "paperforge-setup-bar" }) .createEl("span", { cls: "paperforge-setup-label" }); this.plugin.settings.setup_complete - ? (n.setText(o("setup_done")), n.addClass("paperforge-setup-done")) - : (n.setText(o("setup_pending")), n.addClass("paperforge-setup-pending")); + ? (s.setText(o("setup_done")), s.addClass("paperforge-setup-done")) + : (s.setText(o("setup_pending")), s.addClass("paperforge-setup-pending")); let a = this.app.vault.adapter.basePath, - s = N(a, this.plugin.settings, void 0, void 0), - i = s.path, - l = this.plugin.settings._python_path_stale ? "stale" : s.source, - c = new R.Setting(e) + n = z(a, this.plugin.settings, void 0, void 0), + i = n.path, + c = this.plugin.settings._python_path_stale ? "stale" : n.source, + l = new C.Setting(e) .setName(o("field_python_interp")) - .setDesc(this._getPythonDesc(i, l)); - this._pythonInterpDescEl = c.descEl; - let d = new R.Setting(e).setName(o("field_python_custom")).setDesc(""); + .setDesc(this._getPythonDesc(i, c)); + this._pythonInterpDescEl = l.descEl; + let d = new C.Setting(e).setName(o("field_python_custom")).setDesc(""); ((this._customPathDescEl = d.descEl), - d.addText((k) => { - k.setPlaceholder("e.g. C:\\Python310\\python.exe") + d.addText((x) => { + x.setPlaceholder("e.g. C:\\Python310\\python.exe") .setValue(this.plugin.settings.python_path || "") - .onChange((g) => { + .onChange((m) => { if ( - ((this.plugin.settings.python_path = g), + ((this.plugin.settings.python_path = m), this.plugin.saveSettings(), - g && g.trim()) + m && m.trim()) ) { - let T = $.existsSync(g.trim()); - this.plugin.settings._python_path_stale = !T; + let R = V.existsSync(m.trim()); + this.plugin.settings._python_path_stale = !R; } else this.plugin.settings._python_path_stale = !1; - let v = N( + let v = z( this.app.vault.adapter.basePath, this.plugin.settings, void 0, void 0 ), - w = this.plugin.settings._python_path_stale ? "stale" : v.source; + k = this.plugin.settings._python_path_stale ? "stale" : v.source; this._pythonInterpDescEl && (this._pythonInterpDescEl.textContent = this._getPythonDesc( v.path, - w + k )); }); }), - d.addButton((k) => { - k.setButtonText(o("btn_validate")).onClick(() => + d.addButton((x) => { + x.setButtonText(o("btn_validate")).onClick(() => this._validatePythonOverride() ); }), @@ -2585,51 +2631,51 @@ var He = class extends R.PluginSettingTab { text: o("runtime_health_desc"), cls: "paperforge-settings-desc", })); - let f = new R.Setting(e) + let _ = new C.Setting(e) .setName("PaperForge") .setDesc(o("runtime_health_checking")), - p = f.descEl.createEl("span", { cls: "paperforge-runtime-badge" }), - m = null; - f.addButton((k) => { - ((m = k), - k + p = _.descEl.createEl("span", { cls: "paperforge-runtime-badge" }), + g = null; + _.addButton((x) => { + ((g = x), + x .setButtonText(o("runtime_health_sync")) .setDisabled(!0) - .onClick(() => this._syncRuntime(k))); + .onClick(() => this._syncRuntime(x))); }); { - let k = this.app.vault.adapter.basePath, - { path: g, extraArgs: v = [] } = N( - k, + let x = this.app.vault.adapter.basePath, + { path: m, extraArgs: v = [] } = z( + x, this.plugin.settings, void 0, void 0 ), - w = this.plugin.manifest.version || "?"; - (0, K.execFile)( - g, + k = this.plugin.manifest.version || "?"; + (0, G.execFile)( + m, [...v, "-c", "import paperforge; print(paperforge.__version__)"], - { cwd: k, timeout: 1e4 }, - (T, P) => { - let L = this.plugin.settings.setup_complete, - O = !T && P ? P.trim() : null, - z = O - ? `${o("runtime_health_plugin_ver").replace("{0}", w)} \u2192 ${o("runtime_health_package_ver").replace("{0}", O)}` - : L - ? `Plugin v${w} \u2192 Python package not installed. Click "Sync Runtime" to install.` - : `Plugin v${w} \u2192 Not configured. Please open the setup wizard first.`; - (f.setDesc(z), - O === w + { cwd: x, timeout: 1e4 }, + (R, P) => { + let O = this.plugin.settings.setup_complete, + D = !R && P ? P.trim() : null, + j = D + ? `${o("runtime_health_plugin_ver").replace("{0}", k)} \u2192 ${o("runtime_health_package_ver").replace("{0}", D)}` + : O + ? `Plugin v${k} \u2192 Python package not installed. Click "Sync Runtime" to install.` + : `Plugin v${k} \u2192 Not configured. Please open the setup wizard first.`; + (_.setDesc(j), + D === k ? (p.setText(o("runtime_health_match")), (p.className = "paperforge-runtime-badge match"), - m && m.setDisabled(!0)) - : O + g && g.setDisabled(!0)) + : D ? (p.setText(o("runtime_health_mismatch")), (p.className = "paperforge-runtime-badge mismatch"), - m && m.setDisabled(!1)) - : (p.setText(L ? "Not installed" : "Setup needed"), + g && g.setDisabled(!1)) + : (p.setText(O ? "Not installed" : "Setup needed"), (p.className = "paperforge-runtime-badge missing"), - m && m.setDisabled(!1))); + g && g.setDisabled(!1))); } ); } @@ -2639,51 +2685,51 @@ var He = class extends R.PluginSettingTab { cls: "paperforge-settings-desc", })); let y = e.createEl("div", { cls: "paperforge-guide" }), - x = [ + E = [ ["prep_python", "prep_python_desc"], ["prep_zotero", "prep_zotero_desc"], ["prep_bbt", "prep_bbt_desc"], ["prep_key", "prep_key_desc"], ]; - for (let [k, g] of x) { + for (let [x, m] of E) { let v = y.createEl("div", { cls: "paperforge-guide-item" }); - (v.createEl("strong", { text: o(k) }), - v.createEl("span", { text: " \u2014 " + o(g) })); + (v.createEl("strong", { text: o(x) }), + v.createEl("span", { text: " \u2014 " + o(m) })); } this._checkEl = e.createEl("div", { cls: "paperforge-message" }); - let _ = !this.plugin.settings.setup_complete; - (new R.Setting(e) - .setName(o(_ ? "btn_install" : "btn_reconfig")) - .setDesc(o(_ ? "btn_install_desc" : "btn_reconfig_desc")) - .addButton((k) => { - k.setButtonText(o(_ ? "btn_install" : "btn_reconfig")) + let f = !this.plugin.settings.setup_complete; + (new C.Setting(e) + .setName(o(f ? "btn_install" : "btn_reconfig")) + .setDesc(o(f ? "btn_install_desc" : "btn_reconfig_desc")) + .addButton((x) => { + x.setButtonText(o(f ? "btn_install" : "btn_reconfig")) .setCta() .onClick(() => { - _ + f ? this._preCheck(() => { - new De(this.app, this.plugin).open(); + new Oe(this.app, this.plugin).open(); }) - : new De(this.app, this.plugin).open(); + : new Oe(this.app, this.plugin).open(); }); }), e.createEl("h3", { text: o("section_guide") })); let b = e.createEl("div", { cls: "paperforge-guide" }), - E = [ + w = [ ["guide_open", "guide_open_desc"], ["guide_sync", "guide_sync_desc"], ["guide_ocr", "guide_ocr_desc"], ]; - for (let [k, g] of E) { + for (let [x, m] of w) { let v = b.createEl("div", { cls: "paperforge-guide-item" }); - (v.createEl("strong", { text: o(k) }), - v.createEl("span", { text: " \u2014 " + o(g) })); + (v.createEl("strong", { text: o(x) }), + v.createEl("span", { text: " \u2014 " + o(m) })); } if (this.plugin.settings.setup_complete) { e.createEl("h3", { text: o("section_config") }); - let k = e.createEl("div", { cls: "paperforge-summary" }), - g = this.plugin.settings, + let x = e.createEl("div", { cls: "paperforge-summary" }), + m = this.plugin.settings, v = this._pfConfig, - w = [ + k = [ { label: o("dir_vault"), val: t }, { label: o("dir_resources"), @@ -2703,61 +2749,61 @@ var He = class extends R.PluginSettingTab { }, { label: "API Key", - val: g.paddleocr_api_key ? o("api_key_set") : o("api_key_missing"), + val: m.paddleocr_api_key ? o("api_key_set") : o("api_key_missing"), }, { label: o("field_zotero_data"), - val: g.zotero_data_dir || o("not_set"), + val: m.zotero_data_dir || o("not_set"), }, ]; - for (let T of w) { - let P = k.createEl("div", { cls: "paperforge-summary-row" }); - (P.createEl("span", { cls: "paperforge-summary-label", text: T.label }), - P.createEl("span", { cls: "paperforge-summary-value", text: T.val })); + for (let R of k) { + let P = x.createEl("div", { cls: "paperforge-summary-row" }); + (P.createEl("span", { cls: "paperforge-summary-label", text: R.label }), + P.createEl("span", { cls: "paperforge-summary-value", text: R.val })); } } } _execMemoryStatus(e, t, r) { - (0, K.exec)( + (0, G.exec)( `"${e}" -m paperforge --vault "${t}" memory status --json`, { encoding: "utf-8", timeout: 15e3 }, - (n, a) => { - if (n) { + (s, a) => { + if (s) { r("Status unavailable"); return; } try { - let s = JSON.parse(a); - if (s.ok) { - let i = s.data, - l = i.fresh ? "fresh" : "stale"; + let n = JSON.parse(a); + if (n.ok) { + let i = n.data, + c = i.fresh ? "fresh" : "stale"; r( - `Papers: ${i.paper_count_db} | ${l}${i.needs_rebuild ? " - needs rebuild" : ""}` + `Papers: ${i.paper_count_db} | ${c}${i.needs_rebuild ? " - needs rebuild" : ""}` ); } else r("DB not found. Run paperforge memory build."); - } catch (s) { + } catch (n) { r("Could not parse status."); } } ); } _execEmbedStatus(e, t, r) { - (0, K.exec)( + (0, G.exec)( `"${e}" -m paperforge --vault "${t}" embed status --json`, { encoding: "utf-8", timeout: 15e3 }, - (n, a) => { - if (n) { + (s, a) => { + if (s) { r("Status unavailable"); return; } try { - let s = JSON.parse(a); - s.ok + let n = JSON.parse(a); + n.ok ? r( - `Chunks: ${s.data.chunk_count} | ${s.data.model} | ${s.data.mode}` + `Chunks: ${n.data.chunk_count} | ${n.data.model} | ${n.data.mode}` ) : r("Could not parse status."); - } catch (s) { + } catch (n) { r("Could not parse status."); } } @@ -2765,29 +2811,29 @@ var He = class extends R.PluginSettingTab { } _callPython(e, t) { let r = this.app.vault.adapter.basePath, - n = ue(r, this.plugin.settings), - a = [...n.extraArgs, "-m", "paperforge", "--vault", r, ...e]; + s = me(r, this.plugin.settings), + a = [...s.extraArgs, "-m", "paperforge", "--vault", r, ...e]; if (t && t.stream) { - let s = (0, K.spawn)(n.path, a, { + let n = (0, G.spawn)(s.path, a, { cwd: r, env: t.env || process.env, windowsHide: !0, }); return ( - t.onData && s.stdout.on("data", t.onData), - t.onStderr && s.stderr.on("data", t.onStderr), - t.onError && s.on("error", t.onError), - s.on("close", t.onClose), - s + t.onData && n.stdout.on("data", t.onData), + t.onStderr && n.stderr.on("data", t.onStderr), + t.onError && n.on("error", t.onError), + n.on("close", t.onClose), + n ); } return ( - (0, K.execFile)( - n.path, + (0, G.execFile)( + s.path, a, { cwd: r, timeout: (t && t.timeout) || 6e4 }, - (s, i, l) => { - t && t.onClose && t.onClose(s ? 1 : 0, i, l); + (n, i, c) => { + t && t.onClose && t.onClose(n ? 1 : 0, i, c); } ), null @@ -2802,40 +2848,40 @@ var He = class extends R.PluginSettingTab { cls: "paperforge-sync-status", }) : r && e.createEl("span", { text: r, cls: "paperforge-sync-status" })); - let n = e.createEl("button", { + let s = e.createEl("button", { cls: "paperforge-rebuild-btn", text: o("feat_memory_rebuild_btn"), }); - ((n.title = "Rebuild memory database"), - (n.onclick = () => { - let s = this.app.vault.adapter.basePath, - i = ue(s, this.plugin.settings); + ((s.title = "Rebuild memory database"), + (s.onclick = () => { + let n = this.app.vault.adapter.basePath, + i = me(n, this.plugin.settings); if (!i.path) { - new R.Notice(o("feat_no_python")); + new C.Notice(o("feat_no_python")); return; } (console.log("[PaperForge] Rebuilding memory:", i.path), - n.setText(o("feat_memory_rebuilding")), - n.setAttr("disabled", ""), + s.setText(o("feat_memory_rebuilding")), + s.setAttr("disabled", ""), this._callPython(["memory", "build"], { timeout: 6e4, - onClose: (l, c, d) => { + onClose: (c, l, d) => { (console.log( "[PaperForge] memory build exit:", - l ? "FAIL:" + l : "OK", - (c || "").slice(0, 200), + c ? "FAIL:" + c : "OK", + (l || "").slice(0, 200), (d || "").slice(0, 200) ), - n.setText(o("feat_memory_rebuild_btn")), - n.removeAttribute("disabled"), - l === 0 - ? new R.Notice(o("feat_memory_rebuild_done")) - : new R.Notice( + s.setText(o("feat_memory_rebuild_btn")), + s.removeAttribute("disabled"), + c === 0 + ? new C.Notice(o("feat_memory_rebuild_done")) + : new C.Notice( o("feat_memory_rebuild_failed") + (d ? " " + d.slice(0, 80) : "") ), - (this._memoryStatusText = je(s)), - this._refreshSnapshots(s)); + (this._memoryStatusText = He(n)), + this._refreshSnapshots(n)); }, })); })); @@ -2850,31 +2896,31 @@ var He = class extends R.PluginSettingTab { } _getBuildCommand(e) { let t = this.app.vault.adapter.basePath, - r = N(t, e, void 0, void 0); + r = z(t, e, void 0, void 0); return r.path ? `"${r.path}" -m paperforge --vault "${t}" sync` : null; } _runManualSync() { let e = this.app.vault.adapter.basePath; - if (!ue(e, this.plugin.settings).path) return; + if (!me(e, this.plugin.settings).path) return; let r = document.querySelector(".paperforge-memory-status"); (r && this._renderMemoryStatusText(r, "Checking...", "syncing"), (this.plugin._autoSyncRunning = !0), this._callPython(["sync"], { timeout: 12e4, - onClose: (n) => { + onClose: (s) => { ((this.plugin._autoSyncRunning = !1), (this._memoryStatusText = null), - n === 0 && + s === 0 && ((this._lastSyncTime = new Date().toLocaleTimeString()), (this.plugin._lastSyncTime = this._lastSyncTime)), this.display(), this._refreshSnapshots(e), - ze(this.app, this.plugin, e)); + Ke(this.app, this.plugin, e)); }, })); } _refreshSnapshots(e) { - let t = ue(e, this.plugin.settings), + let t = me(e, this.plugin.settings), r = [ ...t.extraArgs, "-m", @@ -2885,14 +2931,14 @@ var He = class extends R.PluginSettingTab { "--json", ]; ((this._refreshPending = !0), - (0, K.execFile)( + (0, G.execFile)( t.path, r, { cwd: e, timeout: 3e4, windowsHide: !0 }, - (n, a, s) => { + (s, a, n) => { ((this._refreshPending = !1), - (this._memoryStatusText = je(e)), - (this._embedStatusText = Ee(e)), + (this._memoryStatusText = He(e)), + (this._embedStatusText = Ce(e)), this.display()); } )); @@ -2912,7 +2958,7 @@ var He = class extends R.PluginSettingTab { github_copilot: "GitHub Copilot", gemini: "Gemini CLI", }, - n = { + s = { opencode: ".opencode/skills", claude: ".claude/skills", codex: ".codex/skills", @@ -2922,174 +2968,174 @@ var He = class extends R.PluginSettingTab { gemini: ".gemini/skills", }, a = this.app.vault.adapter.basePath, - s = this.plugin.settings.selected_skill_platform || "opencode"; - new R.Setting(e) + n = this.plugin.settings.selected_skill_platform || "opencode"; + new C.Setting(e) .setName(o("feat_agent_platform")) .setDesc(o("feat_agent_platform_desc")) - .addDropdown((g) => { - (Object.entries(r).forEach(([v, w]) => g.addOption(v, w)), - g.setValue(s).onChange((v) => { + .addDropdown((m) => { + (Object.entries(r).forEach(([v, k]) => m.addOption(v, k)), + m.setValue(n).onChange((v) => { ((this.plugin.settings.selected_skill_platform = v), this.plugin.saveSettings(), this.display()); })); }) - .addExtraButton((g) => { - g.setIcon("folder") + .addExtraButton((m) => { + m.setIcon("folder") .setTooltip("Open skills folder") .onClick(() => { - let v = n[s] || ".opencode/skills", - w = X.join(a, v); - $.existsSync(w) - ? (0, K.exec)(`start "" "${w}"`) - : new R.Notice(`Skills folder not found: ${v}`); + let v = s[n] || ".opencode/skills", + k = te.join(a, v); + V.existsSync(k) + ? (0, G.exec)(`start "" "${k}"`) + : new C.Notice(`Skills folder not found: ${v}`); }); }); - let i = X.join(a, n[s]), - l = [], - c = []; - $.existsSync(i) && - $.readdirSync(i, { withFileTypes: !0 }).forEach((g) => { - if (!g.isDirectory()) return; - let v = X.join(i, g.name, "SKILL.md"); - if (!$.existsSync(v)) return; - let w = $.readFileSync(v, "utf-8"), - T = w.match(/^name:\s*(.+)$/m), - P = w.split(` + let i = te.join(a, s[n]), + c = [], + l = []; + V.existsSync(i) && + V.readdirSync(i, { withFileTypes: !0 }).forEach((m) => { + if (!m.isDirectory()) return; + let v = te.join(i, m.name, "SKILL.md"); + if (!V.existsSync(v)) return; + let k = V.readFileSync(v, "utf-8"), + R = k.match(/^name:\s*(.+)$/m), + P = k.split(` `), - L = P.findIndex((M) => /^description:/.test(M)), - O = ""; - if (L >= 0) { - let M = P[L].match(/^description:\s*(.+)$/); - if (M && M[1] && M[1] !== ">" && M[1] !== "|-" && M[1] !== "|") - O = M[1].trim(); + O = P.findIndex((N) => /^description:/.test(N)), + D = ""; + if (O >= 0) { + let N = P[O].match(/^description:\s*(.+)$/); + if (N && N[1] && N[1] !== ">" && N[1] !== "|-" && N[1] !== "|") + D = N[1].trim(); else { for ( - let C = L + 1; - C < P.length && (/^\s{2,}/.test(P[C]) || P[C].trim() === ""); - C++ + let U = O + 1; + U < P.length && (/^\s{2,}/.test(P[U]) || P[U].trim() === ""); + U++ ) - O += P[C].trim() + " "; - O = O.trim(); + D += P[U].trim() + " "; + D = D.trim(); } } - let z = w.match(/^source:\s*(.+)$/m), - q = w.match(/^disable-model-invocation:\s*(.+)$/m), - D = w.match(/^version:\s*(.+)$/m), + let j = k.match(/^source:\s*(.+)$/m), + H = k.match(/^disable-model-invocation:\s*(.+)$/m), + B = k.match(/^version:\s*(.+)$/m), S = { - name: T ? T[1].trim() : g.name, - desc: O, - source: z ? z[1].trim() : "user", - disabled: q && q[1].trim() === "true", - version: D ? D[1].trim() : "", + name: R ? R[1].trim() : m.name, + desc: D, + source: j ? j[1].trim() : "user", + disabled: H && H[1].trim() === "true", + version: B ? B[1].trim() : "", path: v, - content: w, - dirName: g.name, + content: k, + dirName: m.name, }; - S.source === "paperforge" ? l.push(S) : c.push(S); + S.source === "paperforge" ? c.push(S) : l.push(S); }); let d = e.createEl("div", { cls: "paperforge-skills-box" }), - f = (g, v, w) => { + _ = (m, v, k) => { if (v.length === 0) return; - let T = d.createEl("div", { cls: "paperforge-skills-group" }), - P = T.createEl("div", { cls: "paperforge-skills-collapse-header" }), - L = T.createEl("div", { cls: "paperforge-skills-collapse-content" }), - O = P.createEl("span", { + let R = d.createEl("div", { cls: "paperforge-skills-group" }), + P = R.createEl("div", { cls: "paperforge-skills-collapse-header" }), + O = R.createEl("div", { cls: "paperforge-skills-collapse-content" }), + D = P.createEl("span", { text: "\u25BC", cls: "paperforge-skills-arrow", }); (P.createEl("h4", { - text: `${g} (${v.length})`, + text: `${m} (${v.length})`, cls: "paperforge-skills-subheader", }), - v.forEach((D) => { - let S = D.name + (D.version ? " v" + D.version : ""), - M = w ? " [system]" : " [user]", - C = D.desc || "", - F = new R.Setting(L).setName(S + M).setDesc(C); - ((F.settingEl.style.opacity = D.disabled ? "0.4" : "1"), - F.addToggle((A) => { - A.setValue(!D.disabled).onChange((J) => { - let G = !J, - Q = D.content.match(/^disable-model-invocation:\s*(.+)$/m) - ? D.content.replace( + v.forEach((B) => { + let S = B.name + (B.version ? " v" + B.version : ""), + N = k ? " [system]" : " [user]", + U = B.desc || "", + $ = new C.Setting(O).setName(S + N).setDesc(U); + (($.settingEl.style.opacity = B.disabled ? "0.4" : "1"), + $.addToggle((L) => { + L.setValue(!B.disabled).onChange((ne) => { + let Y = !ne, + re = B.content.match(/^disable-model-invocation:\s*(.+)$/m) + ? B.content.replace( /^disable-model-invocation:\s*.+$/m, - `disable-model-invocation: ${G}` + `disable-model-invocation: ${Y}` ) - : D.content.replace( + : B.content.replace( /^(---\r?\n)/, - `$1disable-model-invocation: ${G} + `$1disable-model-invocation: ${Y} ` ); - ($.writeFileSync(D.path, Q, "utf-8"), - (D.disabled = G), - (D.content = Q), - (F.settingEl.style.opacity = D.disabled ? "0.4" : "1")); + (V.writeFileSync(B.path, re, "utf-8"), + (B.disabled = Y), + (B.content = re), + ($.settingEl.style.opacity = B.disabled ? "0.4" : "1")); }); })); })); - let z = w ? "system" : "user"; - ((this._skillsCollapsed[z] || !1) && - ((L.style.display = "none"), (O.style.transform = "rotate(-90deg)")), + let j = k ? "system" : "user"; + ((this._skillsCollapsed[j] || !1) && + ((O.style.display = "none"), (D.style.transform = "rotate(-90deg)")), P.addEventListener("click", () => { - (L.style.display !== "none" - ? ((L.style.display = "none"), - (O.style.transform = "rotate(-90deg)")) - : ((L.style.display = ""), (O.style.transform = "rotate(0deg)")), - (this._skillsCollapsed[z] = L.style.display === "none")); + (O.style.display !== "none" + ? ((O.style.display = "none"), + (D.style.transform = "rotate(-90deg)")) + : ((O.style.display = ""), (D.style.transform = "rotate(0deg)")), + (this._skillsCollapsed[j] = O.style.display === "none")); })); }; - (f("System Skills", l, !0), - f("User Skills", c, !1), - l.length === 0 && - c.length === 0 && + (_("System Skills", c, !0), + _("User Skills", l, !1), + c.length === 0 && + l.length === 0 && d.createEl("p", { - text: `No skills found in ${n[s]}. Run setup to deploy skills.`, + text: `No skills found in ${s[n]}. Run setup to deploy skills.`, cls: "setting-item-description", }), this._advCollapsed === void 0 && (this._advCollapsed = !0)); let p = e.createEl("div", { cls: "paperforge-collapsible-header" }), - m = p.createEl("span", { + g = p.createEl("span", { text: "\u25B6", cls: "paperforge-collapsible-arrow", }); - m.style.transform = this._advCollapsed ? "rotate(0deg)" : "rotate(90deg)"; + g.style.transform = this._advCollapsed ? "rotate(0deg)" : "rotate(90deg)"; let y = p.createEl("span", { cls: "paperforge-collapsible-title", text: "Advanced", }), - x = p.createEl("span", { + E = p.createEl("span", { cls: "paperforge-collapsible-sub", text: "Memory + Vector DB + Embedding", }), - _ = e.createEl("div", { cls: "paperforge-collapsible-content" }); - ((_.style.display = this._advCollapsed ? "none" : ""), + f = e.createEl("div", { cls: "paperforge-collapsible-content" }); + ((f.style.display = this._advCollapsed ? "none" : ""), p.addEventListener("click", () => { ((this._advCollapsed = !this._advCollapsed), - (_.style.display = this._advCollapsed ? "none" : ""), - (m.style.transform = this._advCollapsed + (f.style.display = this._advCollapsed ? "none" : ""), + (g.style.transform = this._advCollapsed ? "rotate(0deg)" : "rotate(90deg)")); }), - _.createEl("h4", { text: "Memory Layer" }), - _.createEl("div", { cls: "paperforge-desc-box" }).setText( - o("feat_memory_desc") - )); - let E = _.createEl("div", { cls: "paperforge-memory-status" }), - k = this.app.vault.adapter.basePath; + f.createEl("h4", { text: "Memory Layer" }), + f + .createEl("div", { cls: "paperforge-desc-box" }) + .setText(o("feat_memory_desc"))); + let w = f.createEl("div", { cls: "paperforge-memory-status" }), + x = this.app.vault.adapter.basePath; (this.plugin._lastSyncTime && !this._lastSyncTime && (this._lastSyncTime = this.plugin._lastSyncTime), - this._memoryStatusText === null && (this._memoryStatusText = je(k)), + this._memoryStatusText === null && (this._memoryStatusText = He(x)), this._renderMemoryStatusText( - E, + w, this._memoryStatusText, this._lastSyncTime ), - this._renderVectorSection(_)); + this._renderVectorSection(f)); } _renderVectorSection(e) { - var l; + var c; if ( (e.createEl("h4", { text: "Vector Database" }), this.plugin.settings.features || @@ -3097,11 +3143,11 @@ var He = class extends R.PluginSettingTab { e .createEl("div", { cls: "paperforge-desc-box" }) .setText(o("feat_vector_desc")), - new R.Setting(e) + new C.Setting(e) .setName(o("feat_vector_enable")) .setDesc(o("feat_vector_enable_desc")) - .addToggle((c) => { - c.setValue(!!this.plugin.settings.features.vector_db).onChange( + .addToggle((l) => { + l.setValue(!!this.plugin.settings.features.vector_db).onChange( (d) => { ((this.plugin.settings.features.vector_db = d), this.plugin.saveSettings(), @@ -3115,44 +3161,44 @@ var He = class extends R.PluginSettingTab { ) return; let r = this.app.vault.adapter.basePath, - n = e.createEl("div", { cls: "paperforge-vec-header" }), - a = n.createEl("span", { + s = e.createEl("div", { cls: "paperforge-vec-header" }), + a = s.createEl("span", { text: "\u25BC", cls: "paperforge-skills-arrow", }); - n.createEl("span", { + s.createEl("span", { cls: "paperforge-vec-header-label", text: o("feat_vector_config_label"), }); - let s = e.createEl("div", { cls: "paperforge-vector-config" }), - i = (c) => { - ((s.style.display = c ? "none" : ""), - (a.style.transform = c ? "rotate(-90deg)" : "rotate(0deg)")); + let n = e.createEl("div", { cls: "paperforge-vector-config" }), + i = (l) => { + ((n.style.display = l ? "none" : ""), + (a.style.transform = l ? "rotate(-90deg)" : "rotate(0deg)")); }; if ( - (i(rt(this._featurePanelsCollapsed, "vectorConfig", !1)), - n.addEventListener("click", () => { - let c = gt(this._featurePanelsCollapsed, "vectorConfig", !1); - i(c); + (i(nt(this._featurePanelsCollapsed, "vectorConfig", !1)), + s.addEventListener("click", () => { + let l = bt(this._featurePanelsCollapsed, "vectorConfig", !1); + i(l); }), this._vectorDepsOk === !0) ) { - this._renderVectorReady(s, r); + this._renderVectorReady(n, r); return; } if (this._vectorDepsOk === !1) { - this._renderVectorNoDeps(s); + this._renderVectorNoDeps(n); return; } if (this._vectorDepsOk === null) { - let c = Ne(r); - ((this._vectorDepsOk = c && (l = c.deps_installed) != null ? l : !1), - this._vectorDepsOk && (this._embedStatusText = Ee(r)), + let l = Ve(r); + ((this._vectorDepsOk = l && (c = l.deps_installed) != null ? c : !1), + this._vectorDepsOk && (this._embedStatusText = Ce(r)), this.display()); } } _renderApiConfig(e) { - (new R.Setting(e) + (new C.Setting(e) .setName(o("feat_openai_key")) .setDesc(o("feat_openai_key_desc")) .addText((t) => { @@ -3163,7 +3209,7 @@ var He = class extends R.PluginSettingTab { this.plugin.saveSettings()); }); }), - new R.Setting(e) + new C.Setting(e) .setName(o("feat_api_base_url")) .setDesc(o("feat_api_base_url_desc")) .addText((t) => { @@ -3174,7 +3220,7 @@ var He = class extends R.PluginSettingTab { this.plugin.saveSettings()); }); }), - new R.Setting(e) + new C.Setting(e) .setName(o("feat_api_model")) .setDesc(o("feat_api_model_desc")) .addText((t) => { @@ -3193,50 +3239,50 @@ var He = class extends R.PluginSettingTab { (e .createEl("div", { cls: "paperforge-desc-box" }) .setText(o("feat_deps_missing")), - new R.Setting(e) + new C.Setting(e) .setName(o("feat_install_deps")) .setDesc(o("feat_install_deps_desc")) .addButton((r) => { r.setButtonText(o("feat_install_btn")) .setCta() .onClick(async () => { - let n = this.app.vault.adapter.basePath, - a = ue(n, this.plugin.settings); + let s = this.app.vault.adapter.basePath, + a = me(s, this.plugin.settings); if (!a.path) { - new R.Notice(o("feat_no_python")); + new C.Notice(o("feat_no_python")); return; } (r.setButtonText(o("feat_installing")), r.setDisabled(!0)); - let s = "chromadb openai", - i = new R.Notice( - o("feat_installing_pkgs").replace("{pkgs}", s), + let n = "chromadb openai", + i = new C.Notice( + o("feat_installing_pkgs").replace("{pkgs}", n), 0 ); try { - let l = Object.assign({}, process.env, { + let c = Object.assign({}, process.env, { PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1", }), - c = s.split(" "); - (await new Promise((d, f) => { - (0, K.execFile)( + l = n.split(" "); + (await new Promise((d, _) => { + (0, G.execFile)( a.path, - [...a.extraArgs, "-m", "pip", "install", ...c], - { cwd: n, timeout: 3e5, env: l, windowsHide: !0 }, + [...a.extraArgs, "-m", "pip", "install", ...l], + { cwd: s, timeout: 3e5, env: c, windowsHide: !0 }, (p) => { - p ? f(p) : d(); + p ? _(p) : d(); } ); }), i.hide(), - new R.Notice(o("feat_install_done")), + new C.Notice(o("feat_install_done")), (this._vectorDepsOk = !0), - (this._embedStatusText = Ee(n)), + (this._embedStatusText = Ce(s)), this.display()); - } catch (l) { + } catch (c) { (i.hide(), - new R.Notice( - o("feat_install_failed") + (l.stderr || l.message || l) + new C.Notice( + o("feat_install_failed") + (c.stderr || c.message || c) ), r.setButtonText(o("feat_retry_btn")), r.setDisabled(!1)); @@ -3245,78 +3291,78 @@ var He = class extends R.PluginSettingTab { })); } _renderVectorReady(e, t) { - (e.createEl("div", { cls: "paperforge-desc-box" }).setText(Ee(t)), + (e.createEl("div", { cls: "paperforge-desc-box" }).setText(Ce(t)), this._renderApiConfig(e)); - let n = e.createEl("div", { cls: "paperforge-embed-section" }); - n.createEl("div", { cls: "paperforge-embed-header" }).createEl("span", { + let s = e.createEl("div", { cls: "paperforge-embed-section" }); + s.createEl("div", { cls: "paperforge-embed-header" }).createEl("span", { text: o("retrieval_rebuild_vectors"), cls: "setting-item-name", }); - let s = n.createEl("div", { cls: "paperforge-embed-controls" }), - i = n.createEl("div", { + let n = s.createEl("div", { cls: "paperforge-embed-controls" }), + i = s.createEl("div", { cls: "paperforge-embed-status-text", attr: { "aria-live": "polite" }, }); (() => { - (s.empty(), i.empty()); - let c = Ne(t), - d = c == null ? void 0 : c.build_state, - f = d && typeof d == "object" && !Array.isArray(d) ? d : {}; + (n.empty(), i.empty()); + let l = Ve(t), + d = l == null ? void 0 : l.build_state, + _ = d && typeof d == "object" && !Array.isArray(d) ? d : {}; ((this.plugin._embedProgress = this.plugin._embedProgress || { current: 0, total: 0, key: "", }), !this.plugin._embedProcess && - f.status === "running" && + _.status === "running" && (this.plugin._embedProgress = { - current: typeof f.current == "number" ? f.current : 0, - total: typeof f.total == "number" ? f.total : 1, - key: typeof f.paper_id == "string" ? f.paper_id : "", + current: typeof _.current == "number" ? _.current : 0, + total: typeof _.total == "number" ? _.total : 1, + key: typeof _.paper_id == "string" ? _.paper_id : "", })); - let { current: p, total: m, key: y } = this.plugin._embedProgress, - x = - typeof (c == null ? void 0 : c.body_chunk_count) == "number" - ? c.body_chunk_count - : 0, - _ = - typeof (c == null ? void 0 : c.object_chunk_count) == "number" - ? c.object_chunk_count - : 0, + let { current: p, total: g, key: y } = this.plugin._embedProgress, E = - (typeof (c == null ? void 0 : c.chunk_count) == "number" - ? c.chunk_count + typeof (l == null ? void 0 : l.body_chunk_count) == "number" + ? l.body_chunk_count + : 0, + f = + typeof (l == null ? void 0 : l.object_chunk_count) == "number" + ? l.object_chunk_count + : 0, + w = + (typeof (l == null ? void 0 : l.chunk_count) == "number" + ? l.chunk_count : 0) + - x + - _, - k = E > 0, - g = c !== null && typeof c.corrupted == "boolean" && c.corrupted, + E + + f, + x = w > 0, + m = l !== null && typeof l.corrupted == "boolean" && l.corrupted, v = !!this.plugin._embedProcess, - w = !this.plugin._embedProcess && f.status === "running", - T = - (c == null ? void 0 : c.deps_installed) !== void 0 - ? !!c.deps_installed + k = !this.plugin._embedProcess && _.status === "running", + R = + (l == null ? void 0 : l.deps_installed) !== void 0 + ? !!l.deps_installed : !0, - P = typeof f.status == "string" ? f.status : "", - L = typeof f.message == "string" ? f.message : "", - O = (S) => { - var F; - if (S === "--resume" && k && !g) { - let A = o("retrieval_rebuild_warning").replace("{n}", String(E)); - if (!confirm(A)) return; + P = typeof _.status == "string" ? _.status : "", + O = typeof _.message == "string" ? _.message : "", + D = (S) => { + var $; + if (S === "--resume" && x && !m) { + let L = o("retrieval_rebuild_warning").replace("{n}", String(w)); + if (!confirm(L)) return; } - if (S === "--force" && k && !g) { - let A = + if (S === "--force" && x && !m) { + let L = "Force rebuild will replace " + - E + + w + " existing chunk(s). Continue?"; - if (!confirm(A)) return; + if (!confirm(L)) return; } - if (!ue(t, this.plugin.settings).path) { - new R.Notice(o("retrieval_no_python")); + if (!me(t, this.plugin.settings).path) { + new C.Notice(o("retrieval_no_python")); return; } - let C = Object.assign({}, process.env, { + let U = Object.assign({}, process.env, { PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1", VECTOR_DB_API_KEY: this.plugin.settings.vector_db_api_key || "", @@ -3329,63 +3375,66 @@ var He = class extends R.PluginSettingTab { ["embed", "build", S], { stream: !0, - env: C, - onData: (A) => { - let G = ( - typeof A == "string" - ? A - : Buffer.isBuffer(A) - ? A.toString("utf-8") - : String(A) - ).split(` -`); - for (let ee of G) - if (ee.startsWith("EMBED_START:")) - this.plugin._embedProgress.total = - parseInt(ee.split(":")[1]) || 0; - else if (ee.startsWith("EMBED_PROGRESS:")) { - let Q = ee.split(":"); - ((this.plugin._embedProgress.current = - parseInt(Q[1]) || 0), - (this.plugin._embedProgress.key = Q[3] || "")); - } else - ee.startsWith("EMBED_DONE") && - ((this.plugin._embedProcess = null), - (this.plugin._embedProgress.current = - this.plugin._embedProgress.total)); + env: U, + onData: (L) => { + var re; + let ne = + typeof L == "string" + ? L + : Buffer.isBuffer(L) + ? L.toString("utf-8") + : String(L), + { events: Y, buffer: ce } = ot( + ne, + (re = this.plugin._embedBuffer) != null ? re : "" + ); + this.plugin._embedBuffer = ce; + for (let q of Y) + q.event === "START" + ? (this.plugin._embedProgress.total = q.total || 0) + : q.event === "PROGRESS" + ? ((this.plugin._embedProgress.current = + q.current || 0), + (this.plugin._embedProgress.key = q.key || "")) + : q.event === "DONE" && + ((this.plugin._embedProcess = null), + (this.plugin._embedProgress.current = + this.plugin._embedProgress.total)); this.display(); }, - onStderr: (A) => { + onStderr: (L) => { (this.plugin._embedStderr || (this.plugin._embedStderr = ""), - (this.plugin._embedStderr += String(A))); + (this.plugin._embedStderr += String(L))); }, - onError: (A) => { + onError: (L) => { ((this.plugin._embedProcess = null), - new R.Notice( - o("feat_build_failed") + ": " + (A.message || A) + new C.Notice( + o("feat_build_failed") + ": " + (L.message || L) ), this.display()); }, - onClose: (A) => { - var J; + onClose: (L) => { + var ne; if ( (clearInterval( - (J = this.plugin._embedPollInterval) != null ? J : void 0 + (ne = this.plugin._embedPollInterval) != null + ? ne + : void 0 ), (this.plugin._embedPollInterval = null), (this.plugin._embedProcess = null), - A === 0) + L === 0) ) ((this.plugin._embedProgress.current = this.plugin._embedProgress.total), this.plugin.saveSettings(), - (this._embedStatusText = Ee(t)), - new R.Notice(o("feat_build_complete"))); + (this._embedStatusText = Ce(t)), + new C.Notice(o("feat_build_complete"))); else { this._embedStatusText = null; - let G = (this.plugin._embedStderr || "").slice(0, 200); - new R.Notice( - o("feat_build_failed") + (G ? ": " + G : ""), + let Y = (this.plugin._embedStderr || "").slice(0, 200); + new C.Notice( + o("feat_build_failed") + (Y ? ": " + Y : ""), 8e3 ); } @@ -3396,150 +3445,148 @@ var He = class extends R.PluginSettingTab { } )), clearInterval( - (F = this.plugin._embedPollInterval) != null ? F : void 0 + ($ = this.plugin._embedPollInterval) != null ? $ : void 0 ), (this.plugin._embedPollInterval = setInterval(() => { this.plugin._embedPolling || ((this.plugin._embedPolling = !0), this._callPython(["embed", "status", "--json"], { timeout: 5e3, - onClose: (A, J) => { - var G; - if (((this.plugin._embedPolling = !1), A === 0 && J)) + onClose: (L, ne) => { + var Y; + if (((this.plugin._embedPolling = !1), L === 0 && ne)) try { - let Q = JSON.parse(J).data; - if (Q && Q.build_state) { - let re = Q.build_state; - ((re.status === "stopping" || re.status === "idle") && + let re = JSON.parse(ne).data; + if (re && re.build_state) { + let q = re.build_state; + ((q.status === "stopping" || q.status === "idle") && this.plugin._embedProcess && ((this.plugin._embedProcess = null), clearInterval( - (G = this.plugin._embedPollInterval) != null - ? G + (Y = this.plugin._embedPollInterval) != null + ? Y : void 0 ), (this.plugin._embedPollInterval = null), this.display()), - re.current !== void 0 && - re.total !== void 0 && - ((this.plugin._embedProgress.current = - re.current), - (this.plugin._embedProgress.total = - re.total || 1), + q.current !== void 0 && + q.total !== void 0 && + ((this.plugin._embedProgress.current = q.current), + (this.plugin._embedProgress.total = q.total || 1), (this.plugin._embedProgress.key = - re.paper_id || ""))); + q.paper_id || ""))); } - } catch (ee) {} + } catch (ce) {} }, })); }, 2e3)), this.display()); }, - z = Te(t), - q = !1; - z && - typeof z.summary == "object" && - z.summary !== null && - "status" in z.summary && - (q = z.summary.status === "version_mismatch"); - let D; + j = Ae(t), + H = !1; + j && + typeof j.summary == "object" && + j.summary !== null && + "status" in j.summary && + (H = j.summary.status === "version_mismatch"); + let B; switch ( - (T - ? q - ? (D = "runtime-mismatch") + (R + ? H + ? (B = "runtime-mismatch") : P === "stopping" - ? (D = "stopping") + ? (B = "stopping") : v && P === "running" - ? (D = "building") + ? (B = "building") : P === "failed" - ? (D = "failed") + ? (B = "failed") : P === "stopped" - ? (D = "stopped") - : w - ? (D = "stale") - : g - ? (D = "corrupted") - : k - ? (D = "ready") - : (D = "idle") - : (D = "deps-missing"), - D) + ? (B = "stopped") + : k + ? (B = "stale") + : m + ? (B = "corrupted") + : x + ? (B = "ready") + : (B = "idle") + : (B = "deps-missing"), + B) ) { case "building": { - let S = s.createEl("div", { cls: "paperforge-progress-track" }); + let S = n.createEl("div", { cls: "paperforge-progress-track" }); S.style.cssText = "flex:1;"; - let M = m > 0 ? ((p / m) * 100).toFixed(1) : "0", - C = S.createEl("div", { cls: "paperforge-progress-seg done" }); + let N = g > 0 ? ((p / g) * 100).toFixed(1) : "0", + U = S.createEl("div", { cls: "paperforge-progress-seg done" }); if ( - ((C.style.cssText = `width:${M}%; min-width:${p > 0 ? "2px" : "0"};`), - p < m) + ((U.style.cssText = `width:${N}%; min-width:${p > 0 ? "2px" : "0"};`), + p < g) ) { - let A = S.createEl("div", { + let L = S.createEl("div", { cls: "paperforge-progress-seg pending", }); - A.style.cssText = `width:${(100 - parseFloat(M)).toFixed(1)}%;`; + L.style.cssText = `width:${(100 - parseFloat(N)).toFixed(1)}%;`; } (i.createEl("span", { cls: "paperforge-embed-progress-text", - text: `${p}/${m} papers`, + text: `${p}/${g} papers`, }), y && i.createEl("span", { cls: "paperforge-embed-progress-key", text: ` (${y})`, })); - let F = s.createEl("button"); - (F.setText(o("retrieval_stop")), - (F.className = "mod-warning"), - F.addEventListener("click", () => { + let $ = n.createEl("button"); + ($.setText(o("retrieval_stop")), + ($.className = "mod-warning"), + $.addEventListener("click", () => { (this._callPython(["embed", "stop", "--json"], { timeout: 8e3 }), this.display()); })); break; } case "stopping": { - let S = s.createEl("div", { cls: "paperforge-progress-track" }); + let S = n.createEl("div", { cls: "paperforge-progress-track" }); S.style.cssText = "flex:1; opacity:0.5;"; - let M = m > 0 ? ((p / m) * 100).toFixed(1) : "0", - C = S.createEl("div", { cls: "paperforge-progress-seg done" }); + let N = g > 0 ? ((p / g) * 100).toFixed(1) : "0", + U = S.createEl("div", { cls: "paperforge-progress-seg done" }); if ( - ((C.style.cssText = `width:${M}%; min-width:${p > 0 ? "2px" : "0"};`), - p < m) + ((U.style.cssText = `width:${N}%; min-width:${p > 0 ? "2px" : "0"};`), + p < g) ) { - let A = S.createEl("div", { + let L = S.createEl("div", { cls: "paperforge-progress-seg pending", }); - A.style.cssText = `width:${(100 - parseFloat(M)).toFixed(1)}%;`; + L.style.cssText = `width:${(100 - parseFloat(N)).toFixed(1)}%;`; } i.createEl("span", { text: o("retrieval_build_stopping") }); - let F = s.createEl("button"); - (F.setText(o("retrieval_stop")), - (F.className = "mod-warning"), - F.setAttr("disabled", "")); + let $ = n.createEl("button"); + ($.setText(o("retrieval_stop")), + ($.className = "mod-warning"), + $.setAttr("disabled", "")); break; } case "failed": { i.createEl("div", { cls: "paperforge-desc-box", - text: o("retrieval_build_failed") + (L ? ": " + L : ""), + text: o("retrieval_build_failed") + (O ? ": " + O : ""), attr: { style: "color:var(--text-error);" }, }); - let S = s.createEl("button"); + let S = n.createEl("button"); (S.setText(o("retrieval_retry")), (S.className = "mod-cta"), - S.addEventListener("click", () => O("--resume"))); - let M = s.createEl("button"); - (M.setText(o("retrieval_force_rebuild")), - (M.style.marginLeft = "6px"), - M.addEventListener("click", () => O("--force"))); + S.addEventListener("click", () => D("--resume"))); + let N = n.createEl("button"); + (N.setText(o("retrieval_force_rebuild")), + (N.style.marginLeft = "6px"), + N.addEventListener("click", () => D("--force"))); break; } case "stopped": { i.setText(o("retrieval_build_stopped")); - let S = s.createEl("button"); + let S = n.createEl("button"); (S.setText(o("retrieval_retry")), (S.className = "mod-cta"), - S.addEventListener("click", () => O("--resume"))); + S.addEventListener("click", () => D("--resume"))); break; } case "corrupted": { @@ -3548,10 +3595,10 @@ var He = class extends R.PluginSettingTab { text: o("feat_vector_corrupted"), attr: { style: "background:var(--background-modifier-warning);" }, }); - let S = s.createEl("button"); + let S = n.createEl("button"); (S.setText(o("retrieval_force_rebuild")), (S.className = "mod-cta"), - S.addEventListener("click", () => O("--force"))); + S.addEventListener("click", () => D("--force"))); break; } case "stale": { @@ -3560,30 +3607,30 @@ var He = class extends R.PluginSettingTab { text: o("retrieval_build_stale"), attr: { style: "color:var(--text-warning);" }, }); - let S = s.createEl("button"); + let S = n.createEl("button"); (S.setText(o("retrieval_rebuild_vectors")), (S.className = "mod-cta"), - S.addEventListener("click", () => O("--resume"))); + S.addEventListener("click", () => D("--resume"))); break; } case "ready": { - s.createEl("span", { - text: E + " chunks embedded", + n.createEl("span", { + text: w + " chunks embedded", cls: "setting-item-description", }); - let S = s.createEl("button"); + let S = n.createEl("button"); (S.setText(o("retrieval_rebuild_vectors")), (S.className = "mod-cta"), - S.addEventListener("click", () => O("--resume"))); - let M = s.createEl("button"); - (M.setText(o("retrieval_force_rebuild")), - (M.style.marginLeft = "6px"), - M.addEventListener("click", () => O("--force"))); + S.addEventListener("click", () => D("--resume"))); + let N = n.createEl("button"); + (N.setText(o("retrieval_force_rebuild")), + (N.style.marginLeft = "6px"), + N.addEventListener("click", () => D("--force"))); break; } case "deps-missing": { i.setText(o("retrieval_build_deps_missing")); - let S = s.createEl("a"); + let S = n.createEl("a"); (S.setText(o("feat_install_deps")), (S.style.cssText = "cursor:pointer; text-decoration:underline;"), S.addEventListener("click", () => { @@ -3597,7 +3644,7 @@ var He = class extends R.PluginSettingTab { text: o("retrieval_build_runtime_mismatch"), attr: { style: "color:var(--text-warning);" }, }); - let S = s.createEl("a"); + let S = n.createEl("a"); (S.setText(o("runtime_health_sync")), (S.style.cssText = "cursor:pointer; text-decoration:underline;"), S.addEventListener("click", () => { @@ -3608,10 +3655,10 @@ var He = class extends R.PluginSettingTab { case "idle": default: { i.setText(o("retrieval_build_idle")); - let S = s.createEl("button"); + let S = n.createEl("button"); (S.setText(o("feat_build_btn")), (S.className = "mod-cta"), - S.addEventListener("click", () => O("--resume"))); + S.addEventListener("click", () => D("--resume"))); break; } } @@ -3630,8 +3677,8 @@ var He = class extends R.PluginSettingTab { ` ) .forEach((r) => { - let n = r.match(/^\s*([^:]+):\s*(.*)/); - n && (t[n[1].trim()] = n[2].trim()); + let s = r.match(/^\s*([^:]+):\s*(.*)/); + s && (t[s[1].trim()] = s[2].trim()); }), t.db_exists !== void 0 && (t.db_exists = t.db_exists === "True"), t.chunk_count !== void 0 && @@ -3664,122 +3711,122 @@ var He = class extends R.PluginSettingTab { let r = "\u8BF7\u8F93\u5165\u8DEF\u5F84 / Enter a path first"; (t && (t.innerHTML = `\u2717 ${r}`), - new R.Notice(r)); + new C.Notice(r)); return; } - if (!$.existsSync(e)) { + if (!V.existsSync(e)) { let r = "\u8DEF\u5F84\u4E0D\u5B58\u5728 / Path does not exist"; (t && (t.innerHTML = `\u2717 ${r}`), - new R.Notice(r, 4e3)); + new C.Notice(r, 4e3)); return; } try { - $.accessSync(e, $.constants.X_OK); + V.accessSync(e, V.constants.X_OK); } catch (r) { - let n = "\u4E0D\u53EF\u6267\u884C / Not executable"; + let s = "\u4E0D\u53EF\u6267\u884C / Not executable"; (t && - (t.innerHTML = `\u2717 ${n}`), - new R.Notice(n, 4e3)); + (t.innerHTML = `\u2717 ${s}`), + new C.Notice(s, 4e3)); return; } - (0, K.execFile)(e, ["--version"], { timeout: 8e3 }, (r, n) => { - if (r || !n) { - let l = "\u65E0\u6CD5\u8FD0\u884C / Cannot run"; + (0, G.execFile)(e, ["--version"], { timeout: 8e3 }, (r, s) => { + if (r || !s) { + let c = "\u65E0\u6CD5\u8FD0\u884C / Cannot run"; (t && - (t.innerHTML = `\u2717 ${l}`), - new R.Notice(l, 4e3)); + (t.innerHTML = `\u2717 ${c}`), + new C.Notice(c, 4e3)); return; } - let a = n.match(/Python (\d+)\.(\d+)/); + let a = s.match(/Python (\d+)\.(\d+)/); if (!a) { - let l = "\u65E0\u6CD5\u89E3\u6790\u7248\u672C / Cannot parse version"; + let c = "\u65E0\u6CD5\u89E3\u6790\u7248\u672C / Cannot parse version"; (t && - (t.innerHTML = `\u2717 ${l}`), - new R.Notice(l, 4e3)); + (t.innerHTML = `\u2717 ${c}`), + new C.Notice(c, 4e3)); return; } - let s = parseInt(a[1], 10), + let n = parseInt(a[1], 10), i = parseInt(a[2], 10); - if (s < 3 || (s === 3 && i < 10)) { - let l = + if (n < 3 || (n === 3 && i < 10)) { + let c = "Python \u7248\u672C\u8FC7\u4F4E\uFF0C\u9700\u8981 3.10+ / Python version too low, need 3.10+"; (t && - (t.innerHTML = `\u2717 ${l}`), - new R.Notice(l, 4e3)); + (t.innerHTML = `\u2717 ${c}`), + new C.Notice(c, 4e3)); return; } - (0, K.execFile)(e, ["-m", "pip", "--version"], { timeout: 8e3 }, (l) => { - if (l) { - let c = `\u2713 Python ${s}.${i} \u6709\u6548\uFF0C\u4F46\u672A\u68C0\u6D4B\u5230 pip / Valid, but pip not found`; + (0, G.execFile)(e, ["-m", "pip", "--version"], { timeout: 8e3 }, (c) => { + if (c) { + let l = `\u2713 Python ${n}.${i} \u6709\u6548\uFF0C\u4F46\u672A\u68C0\u6D4B\u5230 pip / Valid, but pip not found`; (t && - (t.innerHTML = `\u26A0 ${c}`), - new R.Notice(c, 4e3)); + (t.innerHTML = `\u26A0 ${l}`), + new C.Notice(l, 4e3)); } else { - let c = `\u2713 Python ${s}.${i} \u6709\u6548 / Valid`; + let l = `\u2713 Python ${n}.${i} \u6709\u6548 / Valid`; (t && - (t.innerHTML = `${c}`), - new R.Notice(c, 4e3)); + (t.innerHTML = `${l}`), + new C.Notice(l, 4e3)); } }); }); } _syncRuntime(e) { let t = this.app.vault.adapter.basePath, - { path: r, extraArgs: n = [] } = N( + { path: r, extraArgs: s = [] } = z( t, this.plugin.settings, void 0, void 0 ), a = this.plugin.manifest.version, - s = ut(r, a, n); + n = gt(r, a, s); (e.setDisabled(!0), e.setButtonText(o("runtime_health_syncing"))); - let i = (c, d) => ( + let i = (l, d) => ( console.log(`[PaperForge] Sync Runtime: trying ${d}`), - ht(s.cmd, c, t, s.timeout, void 0, be()) + mt(n.cmd, l, t, n.timeout, void 0, Se()) ), - l = () => { - let c = "opencode"; + c = () => { + let l = "opencode"; try { - let m = $.readFileSync(X.join(t, "paperforge.json"), "utf-8"), - y = JSON.parse(m); - y.agent_key && (c = y.agent_key); - } catch (m) {} + let g = V.readFileSync(te.join(t, "paperforge.json"), "utf-8"), + y = JSON.parse(g); + y.agent_key && (l = y.agent_key); + } catch (g) {} let d = [ - ...n, + ...s, "-c", 'from paperforge.services.skill_deploy import deploy_skills; from pathlib import Path; r=deploy_skills(vault=Path(r"' + t.replace(/\\/g, "\\\\") + '"), agent_key="' + - c + + l + '", overwrite=True); print("skills deployed" if r["skill_deployed"] else "skills skipped", flush=True)', ], - f = (0, K.spawn)(r, d, { cwd: t, timeout: 3e4, windowsHide: !0 }), + _ = (0, G.spawn)(r, d, { cwd: t, timeout: 3e4, windowsHide: !0 }), p = ""; - (f.stdout.on("data", (m) => { - p += m.toString("utf-8"); + (_.stdout.on("data", (g) => { + p += g.toString("utf-8"); }), - f.on("close", (m) => { - console.log(`[PaperForge] Skill deploy: ${p.trim()} (exit ${m})`); + _.on("close", (g) => { + console.log(`[PaperForge] Skill deploy: ${p.trim()} (exit ${g})`); })); }; - i(s.pypiArgs, "PyPI").then((c) => { - if (c.exitCode === 0) { + i(n.pypiArgs, "PyPI").then((l) => { + if (l.exitCode === 0) { (console.log("[PaperForge] Sync Runtime: installed via PyPI"), - l(), - new R.Notice(o("runtime_health_sync_done").replace("{0}", a), 5e3), + c(), + new C.Notice(o("runtime_health_sync_done").replace("{0}", a), 5e3), this.display()); return; } (console.warn( "[PaperForge] Sync Runtime: PyPI failed, falling back to git..." ), - i(s.gitArgs, "git").then((d) => { + i(n.gitArgs, "git").then((d) => { d.exitCode === 0 ? (console.log("[PaperForge] Sync Runtime: installed via git"), - l(), - new R.Notice( + c(), + new C.Notice( o("runtime_health_sync_done").replace("{0}", a), 5e3 ), @@ -3787,7 +3834,7 @@ var He = class extends R.PluginSettingTab { : (e.setDisabled(!1), e.setButtonText(o("runtime_health_sync")), console.error("[PaperForge] git fallback stderr:", d.stderr), - new R.Notice( + new C.Notice( o("runtime_health_sync_fail").replace( "{0}", "pip exit code " + d.exitCode @@ -3804,91 +3851,91 @@ var He = class extends R.PluginSettingTab { _preCheck(e) { var a; let t = this.app.vault.adapter.basePath, - { path: r, extraArgs: n = [] } = N( + { path: r, extraArgs: s = [] } = z( t, (a = this.plugin) == null ? void 0 : a.settings, void 0, void 0 ); - (0, K.execFile)(r, [...n, "--version"], { timeout: 8e3 }, (s, i) => { - let l = []; - l.push({ + (0, G.execFile)(r, [...s, "--version"], { timeout: 8e3 }, (n, i) => { + let c = []; + c.push({ label: "Python", - ok: !s, - detail: s ? o("check_python_fail") : i.trim(), + ok: !n, + detail: n ? o("check_python_fail") : i.trim(), }); - let c = !1, - d = process.env.HOME || process.env.USERPROFILE || St.homedir() || ""; + let l = !1, + d = process.env.HOME || process.env.USERPROFILE || Rt.homedir() || ""; if (process.platform === "darwin") - c = [ + l = [ "/Applications/Zotero.app", - X.join(d, "Applications", "Zotero.app"), + te.join(d, "Applications", "Zotero.app"), ].some((b) => { try { - return $.existsSync(b); - } catch (E) { + return V.existsSync(b); + } catch (w) { return !1; } }); else if (process.platform === "win32") { - let _ = process.env.ProgramFiles || "", + let f = process.env.ProgramFiles || "", b = process.env.LOCALAPPDATA || ""; - c = [ - X.join(_, "Zotero"), - X.join(_, "(x86)", "Zotero"), - X.join(b, "Programs", "Zotero"), - X.join(b, "Zotero"), - X.join(d, "AppData", "Local", "Programs", "Zotero"), + l = [ + te.join(f, "Zotero"), + te.join(f, "(x86)", "Zotero"), + te.join(b, "Programs", "Zotero"), + te.join(b, "Zotero"), + te.join(d, "AppData", "Local", "Programs", "Zotero"), ] .filter(Boolean) - .some((k) => { + .some((x) => { try { - return $.existsSync(k); - } catch (g) { + return V.existsSync(x); + } catch (m) { return !1; } }); } else - c = [ - X.join(d, ".local", "share", "zotero", "zotero"), + l = [ + te.join(d, ".local", "share", "zotero", "zotero"), "/usr/bin/zotero", "/usr/local/bin/zotero", ].some((b) => { try { - return $.existsSync(b); - } catch (E) { + return V.existsSync(b); + } catch (w) { return !1; } }); - let f = this.plugin.settings.zotero_data_dir; - if (!c && f) + let _ = this.plugin.settings.zotero_data_dir; + if (!l && _) try { - c = $.existsSync(f); - } catch (_) {} - l.push({ + l = V.existsSync(_); + } catch (f) {} + c.push({ label: "Zotero", - ok: c, - detail: c ? o("check_zotero_ok") : o("check_zotero_fail"), + ok: l, + detail: l ? o("check_zotero_ok") : o("check_zotero_fail"), }); let p = !1, - m = process.env.APPDATA || ""; + g = process.env.APPDATA || ""; (process.platform === "win32" && - m && - (p = Me(X.join(m, "Zotero", "Zotero", "Profiles"))), + g && + (p = $e(te.join(g, "Zotero", "Zotero", "Profiles"))), !p && process.platform === "darwin" && d && - (p = Me( - X.join(d, "Library", "Application Support", "Zotero", "Profiles") + (p = $e( + te.join(d, "Library", "Application Support", "Zotero", "Profiles") )), !p && process.platform !== "win32" && process.platform !== "darwin" && d && - (p = Me(X.join(d, ".zotero", "zotero", "Profiles"))), - !p && f && String(f).trim() && (p = Qe(f.trim())), - !p && d && (p = Qe(X.join(d, "Zotero"))), - l.push({ + (p = $e(te.join(d, ".zotero", "zotero", "Profiles"))), + !p && _ && String(_).trim() && (p = tt(_.trim())), + !p && d && (p = tt(te.join(d, "Zotero"))), + c.push({ label: "Better BibTeX", ok: p, detail: p ? o("check_bbt_ok") : o("check_bbt_fail"), @@ -3896,300 +3943,374 @@ var He = class extends R.PluginSettingTab { let y = { true: "\u2713", false: "\u2717" }; if (this._checkEl) { this._checkEl.setText( - l.map((b) => `${y[String(b.ok)]} ${b.label}: ${b.detail}`).join(` + c.map((b) => `${y[String(b.ok)]} ${b.label}: ${b.detail}`).join(` `) ); - let _ = l.some((b) => !b.ok); - this._checkEl.className = `paperforge-message msg-${_ ? "error" : "ok"}`; + let f = c.some((b) => !b.ok); + this._checkEl.className = `paperforge-message msg-${f ? "error" : "ok"}`; } - let x = l.filter((_) => !_.ok); - (x.length > 0 && - new R.Notice( - `[!!] \u672A\u901A\u8FC7: ${x.map((_) => _.label).join(", ")}`, + let E = c.filter((f) => !f.ok); + (E.length > 0 && + new C.Notice( + `[!!] \u672A\u901A\u8FC7: ${E.map((f) => f.label).join(", ")}`, 6e3 ), e()); }); } _renderMaintenanceTab(e) { + var d; e.createEl("h2", { text: o("tab_maintenance") || "\u7EF4\u62A4" }); - let t = this.app.vault.adapter.basePath, - r = e.createEl("div"), - n = (f, p) => - p === "retry_ocr" || p === "upgrade_legacy" - ? { cmd: ["-m", "paperforge", "ocr", "redo", ...f], timeout: 3e5 } - : p === "rebuild_result" - ? { - cmd: ["-m", "paperforge", "ocr", "rebuild", ...f], - timeout: 12e4, - } - : null, - a = null; + let r = (d = this.app.vault.adapter.basePath) != null ? d : "", + s = e.createEl("div"), + a = { active: "all" }, + n = null; try { - a = kt(t); - } catch (f) {} - let s = N(t, this.plugin.settings, $, K.execFileSync); - if (!s.path) { - r.createEl("p", { + n = Ct(r); + } catch (_) {} + let i = z(r, this.plugin.settings, V, G.execFileSync); + if (!i.path) { + s.createEl("p", { text: "\u26A0 Python \u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u5728\u300C\u5B89\u88C5\u300D\u6807\u7B7E\u9875\u914D\u7F6E\u3002", cls: "setting-item-description", }); return; } - let i = (f) => { - r.empty(); - let p = f.filter((_) => _.visible_in_maintenance); - if (p.length === 0) { - r.createEl("p", { - text: o("maintenance_all_good") || "\u2705 \u5168\u90E8\u6B63\u5E38", + let c = () => !!this.plugin._ocrProcess, + l = (_) => { + s.empty(); + let p = _.filter((b) => b.visible_in_maintenance), + g = s.createEl("div", { cls: "pf-maint-filters" }), + y = g.createEl("button", { + cls: "pf-maint-filter" + (a.active === "all" ? " active" : ""), + text: o("maintenance_filter_all") || "All", + }); + y.addEventListener("click", () => { + ((a.active = "all"), l(_)); }); - return; - } - let m = s.path, - y = s.extraArgs || [], - x = [ - { - key: "retry", - title: o("maintenance_group_retry") || "\u9700\u8981\u91CD\u8BD5", - items: [], - }, - { - key: "rebuild", - title: - o("maintenance_group_rebuild") || - "\u53EF\u91CD\u5EFA\u7ED3\u679C", - items: [], - }, - { - key: "legacy_optional", - title: - o("maintenance_group_legacy") || - "\u53EF\u5347\u7EA7\u65E7\u7ED3\u679C\uFF08\u53EF\u9009\uFF09", - items: [], - }, - ]; - for (let _ of p) { - let b = x.find((E) => E.key === _.display_group); - b && b.items.push(_); - } - for (let _ of x) { - if (_.items.length === 0) continue; - let b = _.key === "legacy_optional", - E = b ? r.createEl("details") : r.createEl("div"); - b - ? E.createEl("summary").createEl("strong", { - text: _.title + " (" + _.items.length + ")", - }) - : E.createEl("h3", { text: _.title + " (" + _.items.length + ")" }); - let k = new Map(); - for (let C of _.items) k.set(C.key, !1); - let g = E.createEl("div", { cls: "pf-maint-toolbar" }), - v = g.createEl("button", { text: "\u5168\u9009" }), - w = g.createEl("button", { text: "\u53D6\u6D88\u5168\u9009" }), - T = g.createEl("button", { - text: "\u25B6 \u6267\u884C\u5DF2\u9009", - cls: "mod-cta", - }), - P = g.createEl("span", { cls: "pf-maint-exec-label" }), - L = () => { - let C = _.items.filter((F) => k.get(F.key)).length; - P.setText("\u5DF2\u9009 " + C + " \u7BC7"); - }; - (L(), - v.addEventListener("click", () => { - for (let F of _.items) k.set(F.key, !0); - (L(), - E.querySelectorAll("input[type=checkbox].pf-maint-sel").forEach( - (F) => { - F.checked = !0; - } - )); - }), - w.addEventListener("click", () => { - for (let F of _.items) k.set(F.key, !1); - (L(), - E.querySelectorAll("input[type=checkbox].pf-maint-sel").forEach( - (F) => { - F.checked = !1; - } - )); - }), - T.addEventListener("click", () => { - let C = _.items.filter((F) => k.get(F.key)); - if (C.length === 0) { - new R.Notice( - "\u8BF7\u5148\u9009\u62E9\u8981\u5904\u7406\u7684\u8BBA\u6587\u3002" - ); + let E = g.createEl("button", { + cls: + "pf-maint-filter" + (a.active === "recommended" ? " active" : ""), + text: o("maintenance_filter_recommended") || "Recommended", + }); + E.addEventListener("click", () => { + ((a.active = "recommended"), l(_)); + }); + let f = + a.active === "recommended" + ? p.filter( + (b) => + b.display_group === "rebuild" || + b.display_action === "rebuild_result" + ) + : p; + if (f.length === 0) + s.createEl("p", { + text: "\u5F53\u524D\u7B5B\u9009\u6761\u4EF6\u4E0B\u65E0\u6570\u636E", + cls: "setting-item-description", + }); + else { + let b = i.path, + w = i.extraArgs || [], + x = s.createEl("div", { cls: "pf-maint-progress" }); + x.style.display = "none"; + let m = x.createEl("div", { cls: "paperforge-progress-track" }); + m.style.cssText = "flex:1;"; + let v = m.createEl("div", { cls: "paperforge-progress-seg done" }), + k = m.createEl("div", { cls: "paperforge-progress-seg pending" }), + R = x.createEl("span", { cls: "pf-maint-progress-text" }), + P = x.createEl("span", { cls: "pf-maint-progress-key" }), + O = x.createEl("button", { text: o("maintenance_stop") || "Stop" }); + ((O.className = "mod-warning"), + O.addEventListener("click", () => { + let F = this.plugin._ocrProcess; + (F && + (F.stdin && typeof F.stdin.write == "function" + ? F.stdin.write(`PAPERFORGE_STOP +`) + : typeof F.kill == "function" && F.kill("SIGINT")), + (this.plugin._ocrWasStopped = !0), + (O.disabled = !0), + (O.textContent = (o("maintenance_stop") || "Stop") + "\u2026")); + })); + let D = () => { + let F = this.plugin._ocrProgress; + if (!F || F.total === 0 || !this.plugin._ocrProcess) { + x.style.display = "none"; return; } - for (let F of C) { - let A = n([F.key], F.display_action); - A && - (0, K.execFile)( - m, - [...y, ...A.cmd], - { cwd: t, timeout: A.timeout, windowsHide: !0 }, - () => { - new R.Notice(F.display_label + " \u2014 " + F.key); - } - ); - } - })); - let z = E.createEl("div", { cls: "pf-maint-table-wrap" }).createEl( - "table", - { cls: "pf-maint-table" } - ), - q = z.createEl("thead"), - D = z.createEl("tbody"), - S = q.insertRow(); - [ - "", - "Key", - "Title", - "\u5EFA\u8BAE\u64CD\u4F5C", - "\u539F\u56E0", - "\u64CD\u4F5C", - ].forEach((C) => { - let F = document.createElement("th"); - ((F.textContent = C), S.appendChild(F)); - }); - let M = (C) => - C === "retry_ocr" - ? o("maintenance_btn_retry") || "\u91CD\u8BD5" - : C === "rebuild_result" - ? o("maintenance_btn_rebuild") || "\u91CD\u5EFA" - : C === "upgrade_legacy" - ? o("maintenance_btn_upgrade") || "\u5347\u7EA7" - : ""; - for (let C of _.items) { - let F = D.insertRow(), - A = F.insertCell(); - A.style.cssText = "padding:3px 4px;text-align:center;"; - let J = document.createElement("input"); - ((J.type = "checkbox"), - (J.className = "pf-maint-sel"), - (J.checked = k.get(C.key) || !1), - J.addEventListener("change", () => { - (k.set(C.key, J.checked), L()); + x.style.display = "flex"; + let Q = + F.total > 0 ? ((F.current / F.total) * 100).toFixed(1) : "0"; + ((v.style.width = `${Q}%`), + (v.style.minWidth = F.current > 0 ? "2px" : "0"), + F.current < F.total + ? ((k.style.display = ""), (k.style.flex = "1")) + : (k.style.display = "none"), + (R.textContent = ( + o("maintenance_progress_label") || "{current}/{total} papers" + ) + .replace("{current}", String(F.current)) + .replace("{total}", String(F.total))), + (P.textContent = F.key ? ` (${F.key})` : "")); + }; + D(); + let j = new Map(); + for (let F of f) j.set(F.key, !1); + let H = s.createEl("div", { cls: "pf-maint-table-wrap" }), + B = H.createEl("table", { cls: "pf-maint-table" }), + S = B.createEl("thead"), + N = B.createEl("tbody"), + U = S.insertRow(); + ["", "Paper", "Status Reason", "Actions"].forEach((F) => { + let Q = document.createElement("th"); + ((Q.textContent = F), U.appendChild(Q)); + }); + let $ = c(); + for (let F of f) { + let Q = N.insertRow(), + T = Q.insertCell(); + T.style.cssText = "padding:3px 4px;text-align:center;width:24px;"; + let I = document.createElement("input"); + ((I.type = "checkbox"), + (I.className = "pf-maint-sel"), + (I.checked = j.get(F.key) || !1), + I.addEventListener("change", () => { + (j.set(F.key, I.checked), Y()); + }), + T.appendChild(I)); + let se = Q.insertCell(); + se.style.cssText = "padding:3px 4px;"; + let A = se.createEl("div", { cls: "pf-maint-paper-info" }); + (A.createEl("div", { + cls: "pf-maint-paper-title", + text: F.title || F.key, }), - A.appendChild(J)); - let G = F.insertCell(); - ((G.style.cssText = - "padding:3px 4px;white-space:nowrap;font-size:11px;max-width:90px;overflow:hidden;text-overflow:ellipsis;"), - (G.textContent = C.key)); - let ee = F.insertCell(); - ((ee.style.cssText = - "padding:3px 4px;white-space:nowrap;max-width:220px;overflow:hidden;text-overflow:ellipsis;"), - (ee.textContent = C.title || C.key)); - let Q = F.insertCell(); - ((Q.style.cssText = "padding:3px 4px;white-space:nowrap;"), - (Q.textContent = C.display_label)); - let re = F.insertCell(); - ((re.style.cssText = - "padding:3px 4px;white-space:nowrap;max-width:160px;overflow:hidden;text-overflow:ellipsis;font-size:11px;color:var(--text-muted);"), - (re.textContent = C.display_reason || "")); - let Pe = F.insertCell(); - Pe.style.cssText = - "padding:3px 4px;text-align:center;white-space:nowrap;"; - let ye = document.createElement("button"); - ((ye.textContent = M(C.display_action)), - ye.textContent && - (ye.addEventListener("click", () => { - let B = n([C.key], C.display_action); - B && - (0, K.execFile)( - m, - [...y, ...B.cmd], - { cwd: t, timeout: B.timeout, windowsHide: !0 }, + A.createEl("div", { cls: "pf-maint-paper-key", text: F.key })); + let ee = Q.insertCell(); + ((ee.style.cssText = "padding:3px 4px;"), + ee.createEl("div", { + cls: "pf-maint-reason", + text: F.display_reason || "", + })); + let ie = Q.insertCell(); + ie.style.cssText = "padding:3px 4px;white-space:nowrap;"; + let be = ie.createEl("div", { cls: "pf-maint-actions" }); + if (F.can_rebuild) { + let Ee = be.createEl("button", { + cls: "pf-maint-action-btn rebuild", + text: o("maintenance_btn_rebuild") || "Rebuild", + }); + ($ && (Ee.disabled = !0), + Ee.addEventListener("click", () => { + let de = [...w, "-m", "paperforge", "ocr", "rebuild", F.key]; + (0, G.execFile)( + b, + de, + { cwd: r, timeout: 12e4, windowsHide: !0 }, () => { - new R.Notice(C.display_label + " \u2014 " + C.key); + new C.Notice( + (o("maintenance_btn_rebuild") || "Rebuild") + + " \u2014 " + + F.key + ); } ); + })); + } + if (F.can_redo) { + let Ee = be.createEl("button", { + cls: "pf-maint-action-btn redo", + text: o("ocr_maint_redo_btn") || "Redo", + }); + ($ && (Ee.disabled = !0), + Ee.addEventListener("click", () => { + let de = [...w, "-m", "paperforge", "ocr", "redo", F.key]; + (0, G.execFile)( + b, + de, + { cwd: r, timeout: 3e5, windowsHide: !0 }, + () => { + new C.Notice( + (o("ocr_maint_redo_btn") || "Redo OCR") + + " \u2014 " + + F.key + ); + } + ); + })); + } + } + let L = s.createEl("div", { cls: "pf-maint-batch-bar" }), + ne = L.createEl("span", { + cls: "pf-maint-batch-label", + text: "0 selected", + }), + Y = () => { + let F = f.filter((Q) => j.get(Q.key)).length; + ne.textContent = F + " selected"; + }, + ce = L.createEl("button", { + cls: "mod-cta", + text: o("maintenance_batch_rebuild") || "\u25B6 Rebuild selected", + }); + ce.disabled = $; + let re = L.createEl("button", { + cls: "mod-cta", + text: + o("maintenance_batch_redo") || "\u25B6 Full OCR redo selected", + }); + re.disabled = $; + let q = (F) => { + let Q = f.filter((A) => j.get(A.key)); + if (Q.length === 0) { + new C.Notice("Please select papers first."); + return; + } + let T = Q.map((A) => A.key); + ((this.plugin._ocrProgress = { + current: 0, + total: T.length, + key: "", + }), + (this.plugin._ocrBuffer = ""), + (this.plugin._ocrWasStopped = !1)); + let I = F === "rebuild" ? "OCR_REBUILD" : "OCR_REDO"; + ((ce.disabled = !0), + (re.disabled = !0), + Array.from(H.querySelectorAll(".pf-maint-action-btn")).forEach( + (A) => { + A.disabled = !0; + } + ), + Array.from(H.querySelectorAll(".pf-maint-sel")).forEach((A) => { + A.disabled = !0; }), - Pe.appendChild(ye))); + (y.disabled = !0), + (E.disabled = !0), + (O.disabled = !1), + (O.textContent = o("maintenance_stop") || "Stop")); + let se = this._callPython(["ocr", F, ...T], { + stream: !0, + onData: (A) => { + var Ee; + let ee = + typeof A == "string" + ? A + : Buffer.isBuffer(A) + ? A.toString("utf-8") + : String(A), + { events: ie, buffer: be } = ot( + ee, + (Ee = this.plugin._ocrBuffer) != null ? Ee : "" + ); + this.plugin._ocrBuffer = be; + for (let de of ie) + de.event === "START" + ? this.plugin._ocrProgress && + (this.plugin._ocrProgress.total = de.total || T.length) + : de.event === "PROGRESS" && + (this.plugin._ocrProgress = { + current: de.current || 0, + total: de.total || T.length, + key: de.key || "", + }); + D(); + }, + onError: (A) => { + ((this.plugin._ocrProcess = null), + new C.Notice("Batch error: " + (A.message || A)), + l(_)); + }, + onClose: (A) => { + (this.plugin._ocrWasStopped || A === 130 + ? ((this.plugin._ocrWasStopped = !1), + (this.plugin._ocrProcess = null), + D(), + new C.Notice("OCR batch stopped by user.")) + : A === 0 + ? (this.plugin._ocrProgress && + (this.plugin._ocrProgress.current = + this.plugin._ocrProgress.total), + (this.plugin._ocrProcess = null), + D(), + new C.Notice( + ( + o("maintenance_batch_complete") || + "Batch operation complete \u2014 {n} papers processed." + ).replace("{n}", String(T.length)) + )) + : ((this.plugin._ocrProcess = null), + D(), + new C.Notice( + "Batch operation finished with exit code " + A + ".", + 8e3 + )), + it(r, b, w, n) + .then((ee) => { + ((ee.changed || !n) && + ((n = { + manifest: {}, + papers: Object.fromEntries( + ee.data.map((ie) => [ie.key, ie]) + ), + cached_at: new Date().toISOString(), + }), + Ie(r, n)), + l(ee.data)); + }) + .catch(() => { + l(p); + })); + }, + }); + ((this.plugin._ocrProcess = se), D()); + }; + (ce.addEventListener("click", () => q("rebuild")), + re.addEventListener("click", () => q("redo")), + Y()); } - } - }; - if (a) { - let f = Object.values(a.papers); - i(f); + }; + if (n) { + let _ = Object.values(n.papers); + l(_); } else - r.createEl("p", { + s.createEl("p", { text: "\u6B63\u5728\u52A0\u8F7D OCR \u7EF4\u62A4\u6570\u636E\u2026", }); - (wt(t, s.path, s.extraArgs || [], a || null) - .then((f) => { - f.changed - ? (i(f.data), - Ve(t, { + it(r, i.path, i.extraArgs || [], n || null) + .then((_) => { + _.changed + ? (l(_.data), + Ie(r, { manifest: {}, - papers: Object.fromEntries(f.data.map((p) => [p.key, p])), + papers: Object.fromEntries(_.data.map((p) => [p.key, p])), cached_at: new Date().toISOString(), })) - : a || - (i(f.data), - Ve(t, { + : n || + (l(_.data), + Ie(r, { manifest: {}, - papers: Object.fromEntries(f.data.map((p) => [p.key, p])), + papers: Object.fromEntries(_.data.map((p) => [p.key, p])), cached_at: new Date().toISOString(), })); }) .catch(() => { - a || - (r.empty(), - r.createEl("p", { + n || + (s.empty(), + s.createEl("p", { text: "\u65E0\u6CD5\u52A0\u8F7D OCR \u6570\u636E\u3002\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5 paperforge \u5E76\u8FD0\u884C\u8FC7 OCR\u3002", cls: "setting-item-description", })); - }), - e.createEl("hr"), - e.createEl("h3", { text: "\u5168\u5C40\u64CD\u4F5C" })); - let l = e.createEl("div", { cls: "pf-maint-global" }); - (l - .createEl("button", { text: "\u91CD\u5EFA\u641C\u7D22\u7D22\u5F15" }) - .addEventListener("click", () => { - (new R.Notice("\u6B63\u5728\u91CD\u5EFA\u641C\u7D22\u7D22\u5F15\u2026"), - (0, K.execFile)( - s.path, - [ - ...(s.extraArgs || []), - "-m", - "paperforge", - "embed", - "build", - "--force", - ], - { cwd: t, timeout: 3e5, windowsHide: !0 }, - () => { - new R.Notice( - "\u641C\u7D22\u7D22\u5F15\u91CD\u5EFA\u5B8C\u6210\u3002" - ); - } - )); - }), - l - .createEl("button", { text: "\u91CD\u5EFA\u8BB0\u5FC6\u5E93" }) - .addEventListener("click", () => { - (new R.Notice("\u6B63\u5728\u91CD\u5EFA\u8BB0\u5FC6\u5E93\u2026"), - (0, K.execFile)( - s.path, - [...(s.extraArgs || []), "-m", "paperforge", "repair", "--fix"], - { cwd: t, timeout: 12e4, windowsHide: !0 }, - () => { - new R.Notice( - "\u8BB0\u5FC6\u5E93\u91CD\u5EFA\u5B8C\u6210\u3002" - ); - } - )); - })); + }); } _renderReleaseNotesTab(e) { (e.createEl("h2", { text: "\u66F4\u65B0\u4E0E\u624B\u518C" }), e.createEl("h3", { text: "\u7248\u672C\u66F4\u65B0\u8BB0\u5F55" })); - let t = Pt.default.versions || []; + let t = Ft.default.versions || []; for (let a of t) { - let s = e.createEl("div", { cls: "paperforge-release-card" }), - i = s.createEl("div", { cls: "paperforge-release-header" }); + let n = e.createEl("div", { cls: "paperforge-release-card" }), + i = n.createEl("div", { cls: "paperforge-release-header" }); if ( (i.createEl("strong", { text: `v${a.version} \u2014 ${a.title}` }), i.createEl("span", { @@ -4198,53 +4319,53 @@ var He = class extends R.PluginSettingTab { }), a.breaking_or_migration && a.breaking_or_migration.length > 0) ) { - let l = s.createEl("div", { cls: "paperforge-release-section" }); - l.createEl("div", { + let c = n.createEl("div", { cls: "paperforge-release-section" }); + c.createEl("div", { cls: "paperforge-release-label", text: "\u884C\u4E3A\u53D8\u66F4 / \u8FC1\u79FB\u6CE8\u610F", }); - for (let c of a.breaking_or_migration) - l.createEl("div", { + for (let l of a.breaking_or_migration) + c.createEl("div", { cls: "paperforge-release-item", - text: `\u2022 ${c}`, + text: `\u2022 ${l}`, }); } if (a.new_features && a.new_features.length > 0) { - let l = s.createEl("div", { cls: "paperforge-release-section" }); - l.createEl("div", { + let c = n.createEl("div", { cls: "paperforge-release-section" }); + c.createEl("div", { cls: "paperforge-release-label", text: "\u65B0\u529F\u80FD", }); - for (let c of a.new_features) - l.createEl("div", { + for (let l of a.new_features) + c.createEl("div", { cls: "paperforge-release-item", - text: `\u2022 ${c}`, + text: `\u2022 ${l}`, }); } if (a.fixes && a.fixes.length > 0) { - let l = s.createEl("div", { cls: "paperforge-release-section" }); - l.createEl("div", { + let c = n.createEl("div", { cls: "paperforge-release-section" }); + c.createEl("div", { cls: "paperforge-release-label", text: "\u4FEE\u590D", }); - for (let c of a.fixes) - l.createEl("div", { + for (let l of a.fixes) + c.createEl("div", { cls: "paperforge-release-item", - text: `\u2022 ${c}`, + text: `\u2022 ${l}`, }); } if (a.recommended_actions && a.recommended_actions.length > 0) { - let l = s.createEl("div", { + let c = n.createEl("div", { cls: "paperforge-release-section paperforge-release-recommended", }); - l.createEl("div", { + c.createEl("div", { cls: "paperforge-release-label", text: "\u5EFA\u8BAE\u64CD\u4F5C", }); - for (let c of a.recommended_actions) - l.createEl("div", { + for (let l of a.recommended_actions) + c.createEl("div", { cls: "paperforge-release-item paperforge-release-item-bold", - text: `\u2022 ${c}`, + text: `\u2022 ${l}`, }); } } @@ -4258,41 +4379,41 @@ var He = class extends R.PluginSettingTab { .setAttr("target", "_blank")); } }; -var I = require("obsidian"), - he = V(require("fs")), - Le = V(require("path")), - _e = require("child_process"); -var Ae = V(require("path")); -function Ct(u) { +var M = require("obsidian"), + ye = K(require("fs")), + je = K(require("path")), + ve = require("child_process"); +var Me = K(require("path")); +function Tt(u) { if (!u) return null; - let h = Ae.dirname(u); + let h = Me.dirname(u); for (;;) { - let e = Ae.basename(h); + let e = Me.basename(h); if (!e || e === ".") break; let t = e.match(/^([A-Z0-9]{8})(?:\s*-\s*.*)?$/i); if (t) return t[1]; - let r = Ae.dirname(h); + let r = Me.dirname(h); if (r === h) break; h = r; } return null; } -var Z = V(require("fs")), - me = V(require("path")); -function Oe(u) { - return ne(u).ocrDir; +var J = K(require("fs")), + ke = K(require("path")); +function Ne(u) { + return le(u).ocrDir; } -function Ht(u, h) { - let e = me.join(Oe(u), h, "versions", "manifest.json"); +function Ut(u, h) { + let e = ke.join(Ne(u), h, "versions", "manifest.json"); try { - if (!Z.existsSync(e)) return null; - let t = Z.readFileSync(e, "utf-8"), + if (!J.existsSync(e)) return null; + let t = J.readFileSync(e, "utf-8"), r = JSON.parse(t); if (r && typeof r == "object" && "versions" in r && "current" in r) { - let n = r, - a = n.versions, - s = n.current; - if (Array.isArray(a) && s && typeof s == "object" && "label" in s) + let s = r, + a = s.versions, + n = s.current; + if (Array.isArray(a) && n && typeof n == "object" && "label" in n) return r; } return null; @@ -4300,11 +4421,11 @@ function Ht(u, h) { return null; } } -function Kt(u) { - let h = Oe(u); +function qt(u) { + let h = Ne(u); try { - return Z.existsSync(h) - ? Z.readdirSync(h, { withFileTypes: !0 }) + return J.existsSync(h) + ? J.readdirSync(h, { withFileTypes: !0 }) .filter((e) => e.isDirectory()) .map((e) => e.name) : []; @@ -4312,19 +4433,19 @@ function Kt(u) { return []; } } -function nt(u) { - let h = Kt(u), +function lt(u) { + let h = qt(u), e = []; for (let t of h) { - let r = Ht(u, t); + let r = Ut(u, t); if (!r) continue; - let n = r.versions.map((s) => s.label), + let s = r.versions.map((n) => n.label), a = 0; - for (let s of n) { - let i = me.join(Oe(u), t, "versions", s, "fulltext.md"); + for (let n of s) { + let i = ke.join(Ne(u), t, "versions", n, "fulltext.md"); try { - Z.existsSync(i) && (a += Z.statSync(i).size); - } catch (l) {} + J.existsSync(i) && (a += J.statSync(i).size); + } catch (c) {} } e.push({ key: t, @@ -4336,65 +4457,65 @@ function nt(u) { } return (e.sort((t, r) => t.title.localeCompare(r.title)), e); } -function Rt(u, h, e) { - let t = Oe(u), - r = me.join(t, h, "versions", e, "fulltext.md"), - n = me.join(t, h, "render"), - a = me.join(n, "fulltext.md"); +function Bt(u, h, e) { + let t = Ne(u), + r = ke.join(t, h, "versions", e, "fulltext.md"), + s = ke.join(t, h, "render"), + a = ke.join(s, "fulltext.md"); try { - return Z.existsSync(r) - ? (Z.existsSync(n) || Z.mkdirSync(n, { recursive: !0 }), - Z.copyFileSync(r, a), + return J.existsSync(r) + ? (J.existsSync(s) || J.mkdirSync(s, { recursive: !0 }), + J.copyFileSync(r, a), !0) : !1; - } catch (s) { + } catch (n) { return !1; } } -function Tt(u, h, e, t) { +function At(u, h, e, t) { var p; - let r = Oe(u), - n = me.join(r, h, "versions", e, "fulltext.md"), - a = me.join(r, h, "versions", t, "fulltext.md"), - s = "", + let r = Ne(u), + s = ke.join(r, h, "versions", e, "fulltext.md"), + a = ke.join(r, h, "versions", t, "fulltext.md"), + n = "", i = ""; try { - Z.existsSync(n) && (s = Z.readFileSync(n, "utf-8")); - } catch (m) {} + J.existsSync(s) && (n = J.readFileSync(s, "utf-8")); + } catch (g) {} try { - Z.existsSync(a) && (i = Z.readFileSync(a, "utf-8")); - } catch (m) {} - let l = Ft(s), - c = Ft(i), - d = Math.max(l.length, c.length), - f = []; - for (let m = 0; m < d; m++) { - let y = m < l.length ? l[m] : "", - x = m < c.length ? c[m] : "", - _ = - (p = (y || x).split(` + J.existsSync(a) && (i = J.readFileSync(a, "utf-8")); + } catch (g) {} + let c = Dt(n), + l = Dt(i), + d = Math.max(c.length, l.length), + _ = []; + for (let g = 0; g < d; g++) { + let y = g < c.length ? c[g] : "", + E = g < l.length ? l[g] : "", + f = + (p = (y || E).split(` `)[0]) != null ? p : "", - b = _.startsWith("## ") ? _.replace(/^##\s+/, "") : "", - E = "unchanged"; - (!y && x - ? (E = "added") - : y && !x - ? (E = "removed") - : y !== x && (E = "changed"), - E !== "unchanged" && - f.push({ - paragraphIndex: m, + b = f.startsWith("## ") ? f.replace(/^##\s+/, "") : "", + w = "unchanged"; + (!y && E + ? (w = "added") + : y && !E + ? (w = "removed") + : y !== E && (w = "changed"), + w !== "unchanged" && + _.push({ + paragraphIndex: g, heading: b, - type: E, + type: w, oldText: y || void 0, - newText: x || void 0, + newText: E || void 0, })); } - return f; + return _; } -function Ft(u) { +function Dt(u) { let h = u.split(` `), e = [], @@ -4411,13 +4532,13 @@ function Ft(u) { ), (t = [r])); else if (r.trim() === "" && t.length > 0) { - let n = t + let s = t .join( ` ` ) .trim(); - n && (e.push(n), (t = [])); + s && (e.push(s), (t = [])); } else t.push(r); if (t.length > 0) { let r = t @@ -4430,7 +4551,7 @@ function Ft(u) { } return e; } -var we = class extends I.ItemView { +var Fe = class extends M.ItemView { constructor(e) { super(e); this._currentMode = null; @@ -4478,13 +4599,13 @@ var we = class extends I.ItemView { (this._ocrPrivacyShown = !1)); } getViewType() { - return ve; + return we; } getDisplayText() { return "PaperForge"; } getIcon() { - return Fe; + return De; } async onOpen() { (this._buildPanel(), @@ -4494,7 +4615,7 @@ var we = class extends I.ItemView { this._fetchVersion(), this._detectAndSwitch(), (this._onKeyDown = (e) => { - var t, r, n; + var t, r, s; if (e.key === "/" && !e.ctrlKey && !e.metaKey && !e.altKey) { let a = (r = (t = e.target) == null ? void 0 : t.tagName) == null @@ -4502,7 +4623,7 @@ var we = class extends I.ItemView { : r.toLowerCase(); a !== "input" && a !== "textarea" && - (e.preventDefault(), (n = this._searchInput) == null || n.focus()); + (e.preventDefault(), (s = this._searchInput) == null || s.focus()); } }), document.addEventListener("keydown", this._onKeyDown)); @@ -4544,12 +4665,12 @@ var we = class extends I.ItemView { cls: "paperforge-header-badge", text: "v\u2014", }))); - let n = t.createEl("button", { + let s = t.createEl("button", { cls: "paperforge-header-refresh", attr: { "aria-label": "Refresh" }, }); - ((n.innerHTML = "\u21BB"), - n.addEventListener("click", () => { + ((s.innerHTML = "\u21BB"), + s.addEventListener("click", () => { (this._invalidateIndex(), this._detectAndSwitch()); }), (this._messageEl = e.createEl("div", { @@ -4561,22 +4682,22 @@ var we = class extends I.ItemView { }))); } _fetchVersion() { - var s, i; + var n, i; let e = this.app.vault.adapter.basePath, t = this.app.plugins.plugins.paperforge, r = - ((s = t == null ? void 0 : t.manifest) == null ? void 0 : s.version) || + ((n = t == null ? void 0 : t.manifest) == null ? void 0 : n.version) || "?", - { path: n, extraArgs: a = [] } = N( + { path: s, extraArgs: a = [] } = z( e, (i = t == null ? void 0 : t.settings) != null ? i : null, void 0, void 0 ); - dt(n, r, e, 1e4, void 0).then((l) => { - if (l.status === "not-installed") return; - let c = l.pyVersion || ""; - ((this._paperforgeVersion = c.startsWith("v") ? c : "v" + c), + ft(s, r, e, 1e4, void 0).then((c) => { + if (c.status === "not-installed") return; + let l = c.pyVersion || ""; + ((this._paperforgeVersion = l.startsWith("v") ? l : "v" + l), this._versionBadge && this._versionBadge.setText(this._paperforgeVersion), this._driftBannerEl && @@ -4593,7 +4714,7 @@ var we = class extends I.ItemView { }); } _fetchStats(e) { - var s; + var n; if (!this._metricsEl) return; if (!e && !this._cachedStats) (this._metricsEl.empty(), @@ -4604,30 +4725,30 @@ var we = class extends I.ItemView { else if (e && !this._cachedStats) return; let t = this.app.vault.adapter.basePath, r = this.app.plugins.plugins.paperforge, - { path: n, extraArgs: a = [] } = N( + { path: s, extraArgs: a = [] } = z( t, - (s = r == null ? void 0 : r.settings) != null ? s : null, + (n = r == null ? void 0 : r.settings) != null ? n : null, void 0, void 0 ); - (0, _e.execFile)( - n, + (0, ve.execFile)( + s, [...a, "-m", "paperforge", "dashboard", "--json"], { cwd: t, timeout: 3e4 }, - (i, l) => { + (i, c) => { if (!i) try { - let c = JSON.parse(l); - if (c.ok && c.data) { - let d = this._normalizeDashboardData(c.data); + let l = JSON.parse(c); + if (l.ok && l.data) { + let d = this._normalizeDashboardData(l.data); ((this._cachedStats = d), this._metricsEl.empty(), this._renderStats(d), this._renderOcr(d), - (this._dashboardPermissions = c.data.permissions || {})); + (this._dashboardPermissions = l.data.permissions || {})); return; } - } catch (c) {} + } catch (l) {} this._fallbackFetchStats(e, t, r); } ); @@ -4635,22 +4756,22 @@ var we = class extends I.ItemView { _normalizeDashboardData(e) { let t = e.stats || {}, r = t.ocr_health || {}, - n = t.pdf_health || {}, + s = t.pdf_health || {}, a = e.ocr_version_state || {}, - s = (r.done || 0) + (r.pending || 0) + (r.failed || 0); + n = (r.done || 0) + (r.pending || 0) + (r.failed || 0); return { total_papers: t.papers || 0, formal_notes: t.papers || 0, exports: 0, bases: 0, ocr: { - total: s, + total: n, pending: r.pending || 0, processing: 0, done: r.done || 0, failed: r.failed || 0, }, - path_errors: (n.broken || 0) + (n.missing || 0), + path_errors: (s.broken || 0) + (s.missing || 0), ocr_version_state: { total_papers: a.total_papers || 0, derived_stale_count: a.derived_stale_count || 0, @@ -4659,86 +4780,86 @@ var we = class extends I.ItemView { }; } _fallbackFetchStats(e, t, r) { - var s, i, l; - let n = - ((s = r == null ? void 0 : r.settings) == null + var n, i, c; + let s = + ((n = r == null ? void 0 : r.settings) == null ? void 0 - : s.system_dir) || "System", - a = Le.join(t, n, "PaperForge", "indexes", "formal-library.json"); + : n.system_dir) || "System", + a = je.join(t, s, "PaperForge", "indexes", "formal-library.json"); try { - let c = he.readFileSync(a, "utf-8"), - d = JSON.parse(c), - f = d.items || [], + let l = ye.readFileSync(a, "utf-8"), + d = JSON.parse(l), + _ = d.items || [], p = {}, - m = { + g = { pdf_health: { healthy: 0, unhealthy: 0 }, ocr_health: { healthy: 0, unhealthy: 0 }, note_health: { healthy: 0, unhealthy: 0 }, asset_health: { healthy: 0, unhealthy: 0 }, }, y = 0, - x = 0, - _ = 0, - b = 0, E = 0, - k = 0; - for (let g of f) { - g.note_path && k++; - let v = g.lifecycle || "pdf_ready"; + f = 0, + b = 0, + w = 0, + x = 0; + for (let m of _) { + m.note_path && x++; + let v = m.lifecycle || "pdf_ready"; p[v] = (p[v] || 0) + 1; - let w = g.health || {}; + let k = m.health || {}; for (let P of [ "pdf_health", "ocr_health", "note_health", "asset_health", ]) - (w[P] || "healthy") === "healthy" ? m[P].healthy++ : m[P].unhealthy++; - let T = g.ocr_status || ""; + (k[P] || "healthy") === "healthy" ? g[P].healthy++ : g[P].unhealthy++; + let R = m.ocr_status || ""; (y++, - T === "done" - ? x++ - : T === "pending" - ? _++ - : T === "processing" || T === "queued" || T === "running" + R === "done" + ? E++ + : R === "pending" + ? f++ + : R === "processing" || R === "queued" || R === "running" ? b++ - : E++); + : w++); } ((this._cachedStats = { version: d.paperforge_version || ((i = this._cachedStats) == null ? void 0 : i.version) || "\u2014", - total_papers: f.length, - formal_notes: k, + total_papers: _.length, + formal_notes: x, exports: 0, bases: 0, - ocr: { total: y, pending: _, processing: b, done: x, failed: E }, + ocr: { total: y, pending: f, processing: b, done: E, failed: w }, path_errors: 0, lifecycle_level_counts: p, - health_aggregate: m, + health_aggregate: g, }), this._metricsEl.empty(), this._renderStats(this._cachedStats), this._renderOcr(this._cachedStats)); - } catch (c) { + } catch (l) { !e && !this._cachedStats && this._metricsEl.createEl("div", { cls: "paperforge-status-loading", text: "No index \u2014 trying CLI...", }); - let { path: d, extraArgs: f = [] } = N( + let { path: d, extraArgs: _ = [] } = z( t, - (l = r == null ? void 0 : r.settings) != null ? l : null, + (c = r == null ? void 0 : r.settings) != null ? c : null, void 0, void 0 ); - (0, _e.execFile)( + (0, ve.execFile)( d, - [...f, "-m", "paperforge", "status", "--json"], + [..._, "-m", "paperforge", "status", "--json"], { cwd: t, timeout: 3e4 }, - (p, m) => { + (p, g) => { if (p) { if (this._cachedStats) return; this._metricsEl.createEl("div", { @@ -4749,7 +4870,7 @@ Make sure paperforge is installed and in your PATH.`, return; } try { - let y = JSON.parse(m); + let y = JSON.parse(g); ((this._cachedStats = y), this._metricsEl.empty(), this._renderStats(y), @@ -4773,10 +4894,10 @@ Make sure paperforge is installed and in your PATH.`, } _buildMetricBar(e, t, r) { if (r <= 0) return; - let n = Math.min(100, (t / r) * 100); + let s = Math.min(100, (t / r) * 100); e.createEl("div", { cls: "paperforge-metric-progress" }).createEl("div", { cls: "paperforge-metric-progress-fill", - attr: { style: `width:${n.toFixed(1)}%` }, + attr: { style: `width:${s.toFixed(1)}%` }, }); } _loadIndex() { @@ -4787,11 +4908,11 @@ Make sure paperforge is installed and in your PATH.`, ((a = t == null ? void 0 : t.settings) == null ? void 0 : a.system_dir) || "System", - n = Le.join(e, r, "PaperForge", "indexes", "formal-library.json"); + s = je.join(e, r, "PaperForge", "indexes", "formal-library.json"); try { - let s = he.readFileSync(n, "utf-8"); - return JSON.parse(s); - } catch (s) { + let n = ye.readFileSync(s, "utf-8"); + return JSON.parse(n); + } catch (n) { return null; } } @@ -4805,18 +4926,18 @@ Make sure paperforge is installed and in your PATH.`, _findEntry(e) { if (!e) return null; let t = this._getCachedIndex().find((r) => r.zotero_key === e) || null; - return ot(this.app, t); + return dt(this.app, t); } _patchCachedEntry(e, t) { if (!e || !this._cachedItems) return; - let r = this._cachedItems.findIndex((n) => n.zotero_key === e); - r !== -1 && (this._cachedItems[r] = Ze(this._cachedItems[r], t)); + let r = this._cachedItems.findIndex((s) => s.zotero_key === e); + r !== -1 && (this._cachedItems[r] = qe(this._cachedItems[r], t)); } _filterByDomain(e) { return e ? this._getCachedIndex().filter((t) => t.domain === e) : []; } _renderStats(e) { - var s; + var n; if ( (this._versionBadge && this._versionBadge.setText( @@ -4831,7 +4952,7 @@ Make sure paperforge is installed and in your PATH.`, this._metricsEl.removeClass("paperforge-loading"); let t = e.total_papers || 0, r = e.formal_notes || 0, - n = [ + s = [ { value: t, label: "Papers", color: "var(--color-cyan)", barMax: 0 }, { value: r, @@ -4846,17 +4967,17 @@ Make sure paperforge is installed and in your PATH.`, barMax: 0, }, ]; - for (let i of n) { - let l = this._metricsEl.createEl("div", { + for (let i of s) { + let c = this._metricsEl.createEl("div", { cls: "paperforge-metric-card", }); - (l.style.setProperty("--metric-color", i.color), - l.createEl("div", { + (c.style.setProperty("--metric-color", i.color), + c.createEl("div", { cls: "paperforge-metric-value", - text: ((s = i.value) == null ? void 0 : s.toString()) || "\u2014", + text: ((n = i.value) == null ? void 0 : n.toString()) || "\u2014", }), - l.createEl("div", { cls: "paperforge-metric-label", text: i.label }), - i.barMax > 0 && this._buildMetricBar(l, i.value, i.barMax)); + c.createEl("div", { cls: "paperforge-metric-label", text: i.label }), + i.barMax > 0 && this._buildMetricBar(c, i.value, i.barMax)); } let a = e.ocr_version_state || {}; if ( @@ -4867,15 +4988,15 @@ Make sure paperforge is installed and in your PATH.`, (a.derived_stale_count > 0 && i.push(`${a.derived_stale_count} stale`), a.raw_upgradable_count > 0 && i.push(`${a.raw_upgradable_count} upgradable`)); - let l = this._metricsEl.createEl("div", { + let c = this._metricsEl.createEl("div", { cls: "paperforge-metric-card", }); - (l.style.setProperty("--metric-color", "var(--color-yellow)"), - l.createEl("div", { + (c.style.setProperty("--metric-color", "var(--color-yellow)"), + c.createEl("div", { cls: "paperforge-metric-value", text: i.join(", "), }), - l.createEl("div", { + c.createEl("div", { cls: "paperforge-metric-label", text: "OCR Version", })); @@ -4891,14 +5012,14 @@ Make sure paperforge is installed and in your PATH.`, } ((this._ocrSection.style.display = "block"), this._ocrEmpty && (this._ocrEmpty.style.display = "none")); - let n = t.done || 0, + let s = t.done || 0, a = t.pending || 0, - s = t.processing || 0, + n = t.processing || 0, i = t.failed || 0; if ( (this._ocrBadge && (this._ocrBadge.removeClass("active", "idle"), - s > 0 + n > 0 ? (this._ocrBadge.addClass("active"), this._ocrBadge.setText("Processing")) : a > 0 @@ -4909,43 +5030,43 @@ Make sure paperforge is installed and in your PATH.`, this._ocrTrack) ) { (this._ocrTrack.empty(), - s > 0 + n > 0 ? this._ocrTrack.addClass("paperforge-processing") : this._ocrTrack.removeClass("paperforge-processing")); - let l = [ + let c = [ { cls: "pending", count: a }, - { cls: "active", count: s }, - { cls: "done", count: n }, + { cls: "active", count: n }, + { cls: "done", count: s }, { cls: "failed", count: i }, ]; - for (let c of l) - if (c.count > 0) { - let d = ((c.count / r) * 100).toFixed(1); + for (let l of c) + if (l.count > 0) { + let d = ((l.count / r) * 100).toFixed(1); this._ocrTrack.createEl("div", { - cls: `paperforge-progress-seg ${c.cls}`, + cls: `paperforge-progress-seg ${l.cls}`, attr: { style: `width:${d}%` }, }); } } if (this._ocrCounts) { this._ocrCounts.empty(); - let l = [ + let c = [ { cls: "pending", value: a, label: "Pending" }, - { cls: "active", value: s, label: "Processing" }, - { cls: "done", value: n, label: "Done" }, + { cls: "active", value: n, label: "Processing" }, + { cls: "done", value: s, label: "Done" }, { cls: "failed", value: i, label: "Failed" }, ]; - for (let c of l) { + for (let l of c) { let d = this._ocrCounts.createEl("div", { cls: "paperforge-ocr-count", }); (d.createEl("div", { cls: "paperforge-ocr-count-value", - text: c.value.toString(), + text: l.value.toString(), }), d.createEl("div", { cls: "paperforge-ocr-count-label", - text: c.label, + text: l.label, })); } } @@ -4955,23 +5076,23 @@ Make sure paperforge is installed and in your PATH.`, this._renderSkeleton(e); return; } - let n = [ + let s = [ { key: "indexed", label: "Indexed" }, { key: "pdf_ready", label: "PDF Ready" }, { key: "fulltext_ready", label: "Fulltext Ready" }, { key: "deep_read_done", label: "Deep Read" }, ], a = e.createEl("div", { cls: "paperforge-lifecycle-stepper" }), - s = !1; - for (let i of n) { - let l = a.createEl("div", { cls: "step" }); - (l.createEl("div", { cls: "step-indicator" }), - l.createEl("div", { cls: "step-label", text: i.label }), + n = !1; + for (let i of s) { + let c = a.createEl("div", { cls: "step" }); + (c.createEl("div", { cls: "step-indicator" }), + c.createEl("div", { cls: "step-label", text: i.label }), i.key === r - ? (l.addClass("current"), (s = !0)) - : s - ? l.addClass("pending") - : l.addClass("completed")); + ? (c.addClass("current"), (n = !0)) + : n + ? c.addClass("pending") + : c.addClass("completed")); } } _renderHealthMatrix(e, t) { @@ -5009,23 +5130,23 @@ Make sure paperforge is installed and in your PATH.`, iconFail: "\u2717", }, ], - n = e.createEl("div", { cls: "paperforge-health-matrix" }); + s = e.createEl("div", { cls: "paperforge-health-matrix" }); for (let a of r) { - let s = t[a.key] || "healthy", - i = n.createEl("div", { cls: "paperforge-health-cell" }), - l, + let n = t[a.key] || "healthy", + i = s.createEl("div", { cls: "paperforge-health-cell" }), c, + l, d; - (s === "healthy" || s === "ok" - ? ((l = a.iconOk), (c = "ok"), (d = `${a.label}: OK`)) - : s === "warn" || s === "warning" || s === "degraded" - ? ((l = a.iconWarn), - (c = "warn"), + (n === "healthy" || n === "ok" + ? ((c = a.iconOk), (l = "ok"), (d = `${a.label}: OK`)) + : n === "warn" || n === "warning" || n === "degraded" + ? ((c = a.iconWarn), + (l = "warn"), (d = `${a.label}: Needs Attention`)) - : ((l = a.iconFail), (c = "fail"), (d = `${a.label}: Failed`)), - i.addClass(c), + : ((c = a.iconFail), (l = "fail"), (d = `${a.label}: Failed`)), + i.addClass(l), i.setAttribute("title", d), - i.createEl("div", { cls: "paperforge-health-cell-icon", text: l }), + i.createEl("div", { cls: "paperforge-health-cell-icon", text: c }), i.createEl("div", { cls: "paperforge-health-cell-label", text: a.label, @@ -5037,22 +5158,22 @@ Make sure paperforge is installed and in your PATH.`, this._renderSkeleton(e); return; } - let n = e.createEl("div", { cls: "paperforge-maturity-gauge" }), - a = n.createEl("div", { cls: "gauge-track" }), - s = 4, - i = Math.max(1, Math.min(s, Math.round(t))); - for (let l = 1; l <= s; l++) { - let c = a.createEl("div", { cls: "gauge-segment" }); - l <= i && (c.addClass("filled"), c.addClass(`level-${l}`)); + let s = e.createEl("div", { cls: "paperforge-maturity-gauge" }), + a = s.createEl("div", { cls: "gauge-track" }), + n = 4, + i = Math.max(1, Math.min(n, Math.round(t))); + for (let c = 1; c <= n; c++) { + let l = a.createEl("div", { cls: "gauge-segment" }); + c <= i && (l.addClass("filled"), l.addClass(`level-${c}`)); } if ( - (n.createEl("div", { cls: "gauge-level", text: `Level ${i} / ${s}` }), - i < s && r) + (s.createEl("div", { cls: "gauge-level", text: `Level ${i} / ${n}` }), + i < n && r) ) { - let l = typeof r == "string" ? [r] : r; - if (l.length > 0) { - let c = n.createEl("ul", { cls: "gauge-blockers" }); - for (let d of l) c.createEl("li", { text: d }); + let c = typeof r == "string" ? [r] : r; + if (c.length > 0) { + let l = s.createEl("ul", { cls: "gauge-blockers" }); + for (let d of c) l.createEl("li", { text: d }); } } } @@ -5071,27 +5192,27 @@ Make sure paperforge is installed and in your PATH.`, }, { key: "deep_read_done", label: "Deep Read", cls: "stage-deep-read" }, ], - n = e.createEl("div", { cls: "paperforge-bar-chart" }), - a = Math.max(1, ...r.map((s) => t[s.key] || 0)); - for (let s of r) { - let i = t[s.key] || 0, - l = (i / a) * 100, - c = n.createEl("div", { cls: "bar-row" }); - (c.createEl("div", { cls: "bar-label", text: s.label }), - c + s = e.createEl("div", { cls: "paperforge-bar-chart" }), + a = Math.max(1, ...r.map((n) => t[n.key] || 0)); + for (let n of r) { + let i = t[n.key] || 0, + c = (i / a) * 100, + l = s.createEl("div", { cls: "bar-row" }); + (l.createEl("div", { cls: "bar-label", text: n.label }), + l .createEl("div", { cls: "bar-track" }) .createEl("div", { - cls: `bar-fill ${s.cls}`, - attr: { style: `width:${l.toFixed(1)}%` }, + cls: `bar-fill ${n.cls}`, + attr: { style: `width:${c.toFixed(1)}%` }, }), - c.createEl("div", { cls: "bar-count", text: i.toString() })); + l.createEl("div", { cls: "bar-count", text: i.toString() })); } } _invalidateIndex() { this._cachedItems = null; } _extractZoteroKeyFromPath(e) { - return Ct(e); + return Tt(e); } _resolveModeForFile(e) { if (!e) return { mode: "global", filePath: null, key: null, domain: null }; @@ -5106,25 +5227,25 @@ Make sure paperforge is installed and in your PATH.`, }; if (t === "md") { let a = this.app.metadataCache.getFileCache(e), - s = a && a.frontmatter && a.frontmatter.zotero_key; - if (s) return { mode: "paper", filePath: r, key: s, domain: null }; + n = a && a.frontmatter && a.frontmatter.zotero_key; + if (n) return { mode: "paper", filePath: r, key: n, domain: null }; } if (t === "pdf") { let a = this._getCachedIndex(); - for (let s of a) { - let i = (s.pdf_path || "").match(/\[\[([^\]]+)\]\]/); - if ((i ? i[1] : s.pdf_path) === r) + for (let n of a) { + let i = (n.pdf_path || "").match(/\[\[([^\]]+)\]\]/); + if ((i ? i[1] : n.pdf_path) === r) return { mode: "paper", filePath: r, - key: s.zotero_key, + key: n.zotero_key, domain: null, }; } } - let n = this._extractZoteroKeyFromPath(r); - return n - ? { mode: "paper", filePath: r, key: n, domain: null } + let s = this._extractZoteroKeyFromPath(r); + return s + ? { mode: "paper", filePath: r, key: s, domain: null } : { mode: "global", filePath: r, key: null, domain: null }; } _detectAndSwitch() { @@ -5166,7 +5287,7 @@ Make sure paperforge is installed and in your PATH.`, } } _renderGlobalMode() { - var J, G, ee, Q, re, Pe, ye; + var ne, Y, ce, re, q, F, Q; if (!this._contentEl) return; let e = this._contentEl.createEl("div", { cls: "paperforge-global-view" }); ((this._driftBannerEl = e.createEl("div", { @@ -5175,34 +5296,34 @@ Make sure paperforge is installed and in your PATH.`, (this._driftBannerEl.style.display = "none")); let t = this._getCachedIndex(), r = t.length, - n = 0, + s = 0, a = 0, - s = 0; - for (let B of t) - (B.has_pdf && n++, - B.ocr_status === "done" && a++, - B.deep_reading_status === "done" && s++); + n = 0; + for (let T of t) + (T.has_pdf && s++, + T.ocr_status === "done" && a++, + T.deep_reading_status === "done" && n++); let i = e.createEl("div", { cls: "paperforge-library-snapshot" }); i.createEl("div", { cls: "paperforge-section-label", text: "Library Snapshot", }); - let l = i.createEl("div", { cls: "paperforge-snapshot-pills" }), - c = [ + let c = i.createEl("div", { cls: "paperforge-snapshot-pills" }), + l = [ { value: r, label: "papers" }, - { value: n, label: "PDFs ready" }, + { value: s, label: "PDFs ready" }, { value: a, label: "OCR done" }, - { value: s, label: "deep-read done" }, + { value: n, label: "deep-read done" }, ]; - for (let B of c) { - let j = l.createEl("div", { cls: "paperforge-snapshot-pill" }); - (j.createEl("span", { + for (let T of l) { + let I = c.createEl("div", { cls: "paperforge-snapshot-pill" }); + (I.createEl("span", { cls: "paperforge-snapshot-value", - text: String(B.value), + text: String(T.value), }), - j.createEl("span", { + I.createEl("span", { cls: "paperforge-snapshot-label", - text: " " + B.label, + text: " " + T.label, })); } let d = e.createEl("div", { cls: "paperforge-system-status" }); @@ -5210,222 +5331,223 @@ Make sure paperforge is installed and in your PATH.`, cls: "paperforge-section-label", text: "System Status", }); - let f = d.createEl("div", { cls: "paperforge-status-grid" }), + let _ = d.createEl("div", { cls: "paperforge-status-grid" }), p = this.app.plugins.plugins.paperforge, - m = - ((J = p == null ? void 0 : p.manifest) == null ? void 0 : J.version) || - "?", + g = + ((ne = p == null ? void 0 : p.manifest) == null + ? void 0 + : ne.version) || "?", y = this._paperforgeVersion; if (!y) try { - let B = this.app.vault.adapter.basePath, - { path: j, extraArgs: ie = [] } = N( - B, - (G = p == null ? void 0 : p.settings) != null ? G : null, + let T = this.app.vault.adapter.basePath, + { path: I, extraArgs: se = [] } = z( + T, + (Y = p == null ? void 0 : p.settings) != null ? Y : null, void 0, void 0 ), - Y = (0, _e.execFileSync)( - j, - [...ie, "-c", "import paperforge; print(paperforge.__version__)"], - { cwd: B, timeout: 5e3, encoding: "utf-8", windowsHide: !0 } + A = (0, ve.execFileSync)( + I, + [...se, "-c", "import paperforge; print(paperforge.__version__)"], + { cwd: T, timeout: 5e3, encoding: "utf-8", windowsHide: !0 } ).trim(); - Y && - ((y = Y.startsWith("v") ? Y : "v" + Y), + A && + ((y = A.startsWith("v") ? A : "v" + A), (this._paperforgeVersion = y)); - } catch (B) {} + } catch (T) {} y = y || "\u2014"; - let x = y === "v" + m; + let E = y === "v" + g; this._renderSystemStatusRow( - f, + _, "Runtime", - x ? "healthy" : "mismatch", - x ? "v" + m : "plugin v" + m + " \u2260 CLI " + y + E ? "healthy" : "mismatch", + E ? "v" + g : "plugin v" + g + " \u2260 CLI " + y ); - let _ = this._loadIndex(), - b = _ && _.items && _.items.length > 0; + let f = this._loadIndex(), + b = f && f.items && f.items.length > 0; this._renderSystemStatusRow( - f, + _, "Index", b ? "healthy" : "missing", - b ? _.items.length + " entries" : "formal-library.json not found" + b ? f.items.length + " entries" : "formal-library.json not found" ); - let E = - ((ee = p == null ? void 0 : p.settings) == null + let w = + ((ce = p == null ? void 0 : p.settings) == null ? void 0 - : ee.system_dir) || "System", - k = this.app.vault.adapter.basePath, - g = !1, + : ce.system_dir) || "System", + x = this.app.vault.adapter.basePath, + m = !1, v = "No exports found"; try { - let B = Le.join(k, E, "PaperForge", "exports"); - if (he.existsSync(B)) { - let j = he.readdirSync(B).filter((ie) => ie.endsWith(".json")); - ((g = j.length > 0), - (v = g ? j.length + " export(s)" : "No JSON exports")); + let T = je.join(x, w, "PaperForge", "exports"); + if (ye.existsSync(T)) { + let I = ye.readdirSync(T).filter((se) => se.endsWith(".json")); + ((m = I.length > 0), + (v = m ? I.length + " export(s)" : "No JSON exports")); } - } catch (B) {} + } catch (T) {} this._renderSystemStatusRow( - f, + _, "Zotero Export", - g ? "healthy" : "missing", + m ? "healthy" : "missing", v ); - let w = !!( - (Q = p == null ? void 0 : p.settings) != null && Q.paddleocr_api_key + let k = !!( + (re = p == null ? void 0 : p.settings) != null && re.paddleocr_api_key ); - if (!w) + if (!k) try { - let B = - ((re = p == null ? void 0 : p.settings) == null + let T = + ((q = p == null ? void 0 : p.settings) == null ? void 0 - : re.system_dir) || "System", - j = Le.join(k, B, "PaperForge", ".env"); - if (he.existsSync(j)) { - let Y = he - .readFileSync(j, "utf-8") + : q.system_dir) || "System", + I = je.join(x, T, "PaperForge", ".env"); + if (ye.existsSync(I)) { + let A = ye + .readFileSync(I, "utf-8") .match(/^PADDLEOCR_API_TOKEN\s*=\s*(.+)$/m); - w = !!(Y && Y[1] && Y[1].trim()); + k = !!(A && A[1] && A[1].trim()); } - } catch (B) {} - (w || - (w = !!( + } catch (T) {} + (k || + (k = !!( process.env.PADDLEOCR_API_TOKEN || process.env.PADDLEOCR_API_KEY || process.env.OCR_TOKEN )), this._renderSystemStatusRow( - f, + _, "OCR Token", - w ? "configured" : "missing", - w ? "Configured" : "Not set" + k ? "configured" : "missing", + k ? "Configured" : "Not set" )); - let T = !1, + let R = !1, P = "", - L = this.app.vault.adapter.basePath, - O = Te(L); - ((T = ft(L)), + O = this.app.vault.adapter.basePath, + D = Ae(O); + ((R = vt(O)), (P = - (O && ((Pe = O.summary) == null ? void 0 : Pe.reason)) || - (O && ((ye = O.summary) == null ? void 0 : ye.status)) || + (D && ((F = D.summary) == null ? void 0 : F.reason)) || + (D && ((Q = D.summary) == null ? void 0 : Q.status)) || "Unknown"), this._renderSystemStatusRow( - f, + _, "Memory Layer", - T ? "healthy" : "fail", + R ? "healthy" : "fail", P )); - let z = !x && y !== "\u2014"; - if (z || !b || !g || !w) { - let B = e.createEl("div", { cls: "paperforge-issue-summary" }); - B.createEl("div", { + let j = !E && y !== "\u2014"; + if (j || !b || !m || !k) { + let T = e.createEl("div", { cls: "paperforge-issue-summary" }); + T.createEl("div", { cls: "paperforge-section-label", text: "\u9700\u8981\u5904\u7406", }); - let j = B.createEl("div", { cls: "paperforge-issue-list" }); - (z && - j.createEl("div", { + let I = T.createEl("div", { cls: "paperforge-issue-list" }); + (j && + I.createEl("div", { cls: "paperforge-issue-item", text: "Runtime version mismatch", }), b || - j.createEl("div", { + I.createEl("div", { cls: "paperforge-issue-item", text: "Index missing or corrupted", }), - g || - j.createEl("div", { + m || + I.createEl("div", { cls: "paperforge-issue-item", text: "No Zotero export found", }), - w || - j.createEl("div", { + k || + I.createEl("div", { cls: "paperforge-issue-item", text: "PaddleOCR API key not configured", })); - let ie = B.createEl("div", { cls: "paperforge-issue-actions" }), - Y = ie.createEl("button", { cls: "paperforge-contextual-btn" }); - (Y.createEl("span", { text: "Run Doctor" }), - Y.addEventListener("click", () => { - let Ce = se.find((We) => We.id === "paperforge-doctor"); - Ce && this._runAction(Ce, Y); + let se = T.createEl("div", { cls: "paperforge-issue-actions" }), + A = se.createEl("button", { cls: "paperforge-contextual-btn" }); + (A.createEl("span", { text: "Run Doctor" }), + A.addEventListener("click", () => { + let ie = oe.find((be) => be.id === "paperforge-doctor"); + ie && this._runAction(ie, A); })); - let fe = ie.createEl("button", { cls: "paperforge-contextual-btn" }); - (fe.createEl("span", { text: "Repair Issues" }), - fe.addEventListener("click", () => { - let Ce = se.find((We) => We.id === "paperforge-repair"); - Ce && this._runAction(Ce, fe); + let ee = se.createEl("button", { cls: "paperforge-contextual-btn" }); + (ee.createEl("span", { text: "Repair Issues" }), + ee.addEventListener("click", () => { + let ie = oe.find((be) => be.id === "paperforge-repair"); + ie && this._runAction(ie, ee); })); } - let D = e.createEl("div", { cls: "paperforge-global-actions" }); - D.createEl("div", { + let B = e.createEl("div", { cls: "paperforge-global-actions" }); + B.createEl("div", { cls: "paperforge-section-label", text: "Start Working", }); - let S = D.createEl("div", { cls: "paperforge-global-actions-row" }), - M = S.createEl("button", { cls: "paperforge-contextual-btn primary" }); - (M.createEl("span", { + let S = B.createEl("div", { cls: "paperforge-global-actions-row" }), + N = S.createEl("button", { cls: "paperforge-contextual-btn primary" }); + (N.createEl("span", { cls: "paperforge-contextual-btn-icon", text: "\u{1F4C1}", }), - M.createEl("span", { text: "Open Literature Hub" }), - M.addEventListener("click", () => { - var ie; - let B = - ((ie = p == null ? void 0 : p.settings) == null + N.createEl("span", { text: "Open Literature Hub" }), + N.addEventListener("click", () => { + var se; + let T = + ((se = p == null ? void 0 : p.settings) == null ? void 0 - : ie.base_dir) || "Bases", - j = this.app.vault.getAbstractFileByPath(B); - if (j) { - let Y = null; + : se.base_dir) || "Bases", + I = this.app.vault.getAbstractFileByPath(T); + if (I) { + let A = null; if ( - (j.children && - (Y = j.children.find((fe) => fe.extension === "base")), - Y) + (I.children && + (A = I.children.find((ee) => ee.extension === "base")), + A) ) { - let fe = this.app.workspace.getLeaf(!1); - fe && fe.openFile(Y); - } else new I.Notice("[!!] No .base file found in " + B, 6e3); - } else new I.Notice("[!!] Base directory not found: " + B, 6e3); + let ee = this.app.workspace.getLeaf(!1); + ee && ee.openFile(A); + } else new M.Notice("[!!] No .base file found in " + T, 6e3); + } else new M.Notice("[!!] Base directory not found: " + T, 6e3); })); - let C = S.createEl("button", { cls: "paperforge-contextual-btn" }); - (C.createEl("span", { + let U = S.createEl("button", { cls: "paperforge-contextual-btn" }); + (U.createEl("span", { cls: "paperforge-contextual-btn-icon", text: "\u21BB", }), - C.createEl("span", { text: "Sync Library" }), - C.addEventListener("click", () => { - let B = se.find((j) => j.id === "paperforge-sync"); - B && this._runAction(B, C); + U.createEl("span", { text: "Sync Library" }), + U.addEventListener("click", () => { + let T = oe.find((I) => I.id === "paperforge-sync"); + T && this._runAction(T, U); })); - let F = S.createEl("button", { cls: "paperforge-contextual-btn" }); - (F.createEl("span", { + let $ = S.createEl("button", { cls: "paperforge-contextual-btn" }); + ($.createEl("span", { cls: "paperforge-contextual-btn-icon", text: "\u229E", }), - F.createEl("span", { text: "Run OCR" }), - F.addEventListener("click", () => { - let B = se.find((j) => j.id === "paperforge-ocr"); - B && this._runAction(B, F); + $.createEl("span", { text: "Run OCR" }), + $.addEventListener("click", () => { + let T = oe.find((I) => I.id === "paperforge-ocr"); + T && this._runAction(T, $); })); - let A = S.createEl("button", { cls: "paperforge-contextual-btn warn" }); - (A.createEl("span", { + let L = S.createEl("button", { cls: "paperforge-contextual-btn warn" }); + (L.createEl("span", { cls: "paperforge-contextual-btn-icon", text: "\u21BA", }), - A.createEl("span", { text: "Redo OCR" }), - A.addEventListener("click", () => { - let B = se.find((j) => j.id === "paperforge-ocr-redo"); - B && this._runAction(B, A); + L.createEl("span", { text: "Redo OCR" }), + L.addEventListener("click", () => { + let T = oe.find((I) => I.id === "paperforge-ocr-redo"); + T && this._runAction(T, L); })); } - _renderSystemStatusRow(e, t, r, n) { + _renderSystemStatusRow(e, t, r, s) { let a = e.createEl("div", { cls: "paperforge-status-row" }); (a .createEl("span", { cls: "paperforge-status-dot" }) .addClass(r === "healthy" || r === "configured" ? "ok" : "fail"), a.createEl("span", { cls: "paperforge-status-label", text: t }), - a.createEl("span", { cls: "paperforge-status-detail", text: n || "" })); + a.createEl("span", { cls: "paperforge-status-detail", text: s || "" })); } _renderPaperMode() { let e = this._currentPaperEntry, @@ -5443,29 +5565,29 @@ Make sure paperforge is installed and in your PATH.`, return; } let r = this._contentEl.createEl("div", { cls: "paperforge-paper-view" }), - n = r.createEl("div", { cls: "paperforge-paper-header" }); - n.createEl("div", { + s = r.createEl("div", { cls: "paperforge-paper-header" }); + s.createEl("div", { cls: "paperforge-paper-title pf-copy", text: e.title || "Untitled", }).addEventListener("click", () => { (navigator.clipboard.writeText(e.title || ""), - new I.Notice("Title copied")); + new M.Notice("Title copied")); }); - let s = n.createEl("div", { cls: "paperforge-paper-meta" }); + let n = s.createEl("div", { cls: "paperforge-paper-meta" }); (e.authors && e.authors.length > 0 && - s.createEl("span", { + n.createEl("span", { cls: "paperforge-paper-authors", text: e.authors.join(", "), }), e.year && - s.createEl("span", { + n.createEl("span", { cls: "paperforge-paper-year", text: String(e.year), })); let i = r.createEl("div", { cls: "paperforge-status-strip" }), - l = i.createEl("div", { cls: "paperforge-status-strip-left" }), - c = i.createEl("div", { cls: "paperforge-status-strip-right" }), + c = i.createEl("div", { cls: "paperforge-status-strip-left" }), + l = i.createEl("div", { cls: "paperforge-status-strip-right" }), d = [ { key: "pdf", label: "PDF", ok: e.has_pdf === !0 }, { @@ -5486,31 +5608,31 @@ Make sure paperforge is installed and in your PATH.`, }, ]; for (let p of d) { - let m = l.createEl("span", { cls: "paperforge-status-pill" }), + let g = c.createEl("span", { cls: "paperforge-status-pill" }), y = "pending"; (p.ok ? (y = "ok") : p.fail ? (y = "fail") : p.pending && (y = "pending"), - m.addClass(y)); - let x = p.ok ? "\u2713" : p.fail ? "\u2717" : "\u25CB"; - (m.createEl("span", { cls: "paperforge-status-pill-icon", text: x }), - m.createEl("span", { text: " " + p.label })); + g.addClass(y)); + let E = p.ok ? "\u2713" : p.fail ? "\u2717" : "\u25CB"; + (g.createEl("span", { cls: "paperforge-status-pill-icon", text: E }), + g.createEl("span", { text: " " + p.label })); } if (e.pdf_path) { - let p = c.createEl("button", { cls: "paperforge-contextual-btn" }); + let p = l.createEl("button", { cls: "paperforge-contextual-btn" }); (p.createEl("span", { cls: "paperforge-contextual-btn-icon", text: "\u{1F4C4}", }), p.createEl("span", { text: "\u6253\u5F00 PDF" }), p.addEventListener("click", () => { - let m = e.pdf_path.match(/\[\[([^\]]+)\]\]/), - y = m ? m[1] : e.pdf_path; + let g = e.pdf_path.match(/\[\[([^\]]+)\]\]/), + y = g ? g[1] : e.pdf_path; this.app.vault.getAbstractFileByPath(y) ? this.app.workspace.openLinkText(y, "") - : new I.Notice("[!!] PDF not found: " + y, 6e3); + : new M.Notice("[!!] PDF not found: " + y, 6e3); })); } if (e.fulltext_path) { - let p = c.createEl("button", { cls: "paperforge-contextual-btn" }); + let p = l.createEl("button", { cls: "paperforge-contextual-btn" }); (p.createEl("span", { cls: "paperforge-contextual-btn-icon", text: "\u{1F4DD}", @@ -5518,10 +5640,10 @@ Make sure paperforge is installed and in your PATH.`, p.createEl("span", { text: "\u6253\u5F00\u5168\u6587" }), p.addEventListener("click", () => this._openFulltext(e.fulltext_path))); } - let f = c.createEl("button", { cls: "paperforge-contextual-btn" }); + let _ = l.createEl("button", { cls: "paperforge-contextual-btn" }); if ( - (f.createEl("span", { text: o("version_panel_title") }), - f.addEventListener("click", () => { + (_.createEl("span", { text: o("version_panel_title") }), + _.addEventListener("click", () => { this._switchToVersionMode(t); }), this._renderPaperOverviewCard(r, e), @@ -5546,7 +5668,7 @@ Make sure paperforge is installed and in your PATH.`, } ); let a = r.createEl("div", { cls: "paperforge-paper-overview-body" }), - s = a.createEl("div", { + n = a.createEl("div", { cls: "paperforge-paper-overview-excerpt", text: "\u52A0\u8F7D\u4E2D...", }); @@ -5555,77 +5677,77 @@ Make sure paperforge is installed and in your PATH.`, i ? this.app.vault .read(i) - .then((l) => { - let c = this._extractOverviewFromNote(l); - if (c) { - let d = c.length > 200 ? c.slice(0, 200) + "..." : c; - if ((s.setText(d), c.length > 200)) { - let f = a.createEl("div", { + .then((c) => { + let l = this._extractOverviewFromNote(c); + if (l) { + let d = l.length > 200 ? l.slice(0, 200) + "..." : l; + if ((n.setText(d), l.length > 200)) { + let _ = a.createEl("div", { cls: "paperforge-expand-container", }), - p = f.createEl("button", { + p = _.createEl("button", { cls: "paperforge-expand-icon", title: "\u5C55\u5F00/\u6536\u8D77", }); p.innerHTML = ''; - let m = !1; - f.addEventListener("click", () => { - (s.setText(m ? d : c), - (p.innerHTML = m + let g = !1; + _.addEventListener("click", () => { + (n.setText(g ? d : l), + (p.innerHTML = g ? '' : ''), - (m = !m)); + (g = !g)); }); } } else - s.setText( + n.setText( "\u5C1A\u672A\u751F\u6210\u6587\u7AE0\u6982\u89C8\u3002\u8FD0\u884C /pf-deep \u5F00\u59CB\u7CBE\u8BFB\u3002" ); }) .catch(() => { - s.setText("\u65E0\u6CD5\u8BFB\u53D6\u7B14\u8BB0\u5185\u5BB9"); + n.setText("\u65E0\u6CD5\u8BFB\u53D6\u7B14\u8BB0\u5185\u5BB9"); }) - : s.setText("\u7B14\u8BB0\u6587\u4EF6\u4E0D\u5B58\u5728"); - } else s.setText("\u5C1A\u672A\u751F\u6210\u6587\u7AE0\u6982\u89C8"); + : n.setText("\u7B14\u8BB0\u6587\u4EF6\u4E0D\u5B58\u5728"); + } else n.setText("\u5C1A\u672A\u751F\u6210\u6587\u7AE0\u6982\u89C8"); } _extractOverviewFromNote(e) { if (!e) return null; let t = e.indexOf("## \u{1F50D} \u7CBE\u8BFB"); if (t === -1) return null; let r = e.slice(t), - n = [ + s = [ "**\u4E00\u53E5\u8BDD\u603B\u89C8:**", "**\u4E00\u53E5\u8BDD\u603B\u89C8**", "**\u6587\u7AE0\u6458\u8981:**", "**\u6587\u7AE0\u6458\u8981**", ]; - for (let i of n) { - let l = r.indexOf(i); - if (l !== -1) { - let c = r.slice(l + i.length), + for (let i of s) { + let c = r.indexOf(i); + if (c !== -1) { + let l = r.slice(c + i.length), d = ["**5 Cs", "**Figure", "**\u8BC1\u636E", "### Pass 2", "## "], - f = c.length; + _ = l.length; for (let y of d) { - let x = c.indexOf(y); - x !== -1 && x < f && (f = x); + let E = l.indexOf(y); + E !== -1 && E < _ && (_ = E); } - let p = c.indexOf(` + let p = l.indexOf(` `); - p !== -1 && p < f && (f = p); - let m = c.slice(0, f).trim(); + p !== -1 && p < _ && (_ = p); + let g = l.slice(0, _).trim(); return ( - m.startsWith("**") && (m = m.slice(2)), - m.endsWith("**") && (m = m.slice(0, -2)), - m || null + g.startsWith("**") && (g = g.slice(2)), + g.endsWith("**") && (g = g.slice(0, -2)), + g || null ); } } let a = r.indexOf(` `); if (a === -1) return null; - let s = r + let n = r .slice(a + 1) .split( ` @@ -5633,26 +5755,26 @@ Make sure paperforge is installed and in your PATH.`, ` )[0] .trim(); - return !s || s.startsWith("###") || s.startsWith("##") + return !n || n.startsWith("###") || n.startsWith("##") ? null - : s.length > 300 - ? s.slice(0, 300) + "..." - : s; + : n.length > 300 + ? n.slice(0, 300) + "..." + : n; } _renderRecentDiscussionCard(e, t) { let r = e.createEl("div", { cls: "paperforge-discussion-card" }); if (((r.style.display = "none"), !t.note_path)) return; - let n = t.note_path.lastIndexOf("/"), - s = (n !== -1 ? t.note_path.substring(0, n) : ".") + "/ai/discussion.md"; + let s = t.note_path.lastIndexOf("/"), + n = (s !== -1 ? t.note_path.substring(0, s) : ".") + "/ai/discussion.md"; this.app.vault.adapter - .exists(s) + .exists(n) .then((i) => { - if (i) return this.app.vault.adapter.read(s); + if (i) return this.app.vault.adapter.read(n); }) .then(async (i) => { if (!i) return; - let l = this._parseDiscussionMD(i); - if (!l || l.length === 0) return; + let c = this._parseDiscussionMD(i); + if (!c || c.length === 0) return; ((r.style.display = "block"), r .createEl("div", { cls: "paperforge-discussion-header" }) @@ -5660,85 +5782,85 @@ Make sure paperforge is installed and in your PATH.`, cls: "paperforge-discussion-title", text: "\u6700\u8FD1\u8BA8\u8BBA", })); - for (let f of l) { + for (let _ of c) { let p = r.createEl("div", { cls: "paperforge-discussion-item" }), - m = p.createEl("div", { cls: "paperforge-discussion-q" }); - (m.createEl("span", { + g = p.createEl("div", { cls: "paperforge-discussion-q" }); + (g.createEl("span", { cls: "paperforge-discussion-q-label", text: "\u63D0\u95EE\uFF1A", }), - m.createEl("span", { + g.createEl("span", { cls: "paperforge-discussion-q-text", - text: f.question, + text: _.question, })); let y = p.createEl("div", { cls: "paperforge-discussion-a" }), - x = !1; + E = !1; if ( - (f.answer && - f.answer.length > 500 && - ((x = !0), y.classList.add("paperforge-discussion-a-collapsed")), - await I.MarkdownRenderer.render( + (_.answer && + _.answer.length > 500 && + ((E = !0), y.classList.add("paperforge-discussion-a-collapsed")), + await M.MarkdownRenderer.render( this.app, - f.answer || "", + _.answer || "", y, - s, + n, this ), - x) + E) ) { - let _ = !1; + let f = !1; ((p.style.cursor = "pointer"), p.addEventListener("click", () => { - ((_ = !_), - y.classList.toggle("paperforge-discussion-a-collapsed", !_), - y.classList.toggle("paperforge-discussion-a-expanded", _)); + ((f = !f), + y.classList.toggle("paperforge-discussion-a-collapsed", !f), + y.classList.toggle("paperforge-discussion-a-expanded", f)); })); } } r.createEl("a", { cls: "paperforge-discussion-viewall", text: "\u67E5\u770B\u5168\u90E8\u8BA8\u8BBA \u2192", - }).addEventListener("click", (f) => { - (f.preventDefault(), - this.app.vault.getAbstractFileByPath(s) - ? this.app.workspace.openLinkText(s, "") - : new I.Notice( + }).addEventListener("click", (_) => { + (_.preventDefault(), + this.app.vault.getAbstractFileByPath(n) + ? this.app.workspace.openLinkText(n, "") + : new M.Notice( "\u8BA8\u8BBA\u6587\u4EF6\u5C1A\u672A\u751F\u6210" )); }); }) .catch((i) => { - console.error("PaperForge: discussion.md read error", s, i.message); + console.error("PaperForge: discussion.md read error", n, i.message); }); } _parseDiscussionMD(e) { let t = e.split(/\n## /).slice(1); if (t.length === 0) return null; let r = t[t.length - 1], - n = [], + s = [], a = r.split(/\*\*\u95EE\u9898:\*\*/).slice(1); - for (let s of a) { - let i = s.match(/\*\*\u89E3\u7B54:\*\*/); + for (let n of a) { + let i = n.match(/\*\*\u89E3\u7B54:\*\*/); if (!i) continue; - let l = s.substring(0, i.index).trim(), - c = s.substring(i.index + 3 + 4).trim(); - n.push({ question: l, answer: c }); + let c = n.substring(0, i.index).trim(), + l = n.substring(i.index + 3 + 4).trim(); + s.push({ question: c, answer: l }); } - return n.slice(-3); + return s.slice(-3); } _renderPaperTechnicalDetails(e, t) { let r = this._currentPaperKey, - n = e.createEl("div", { cls: "paperforge-technical-details" }), - a = n.createEl("button", { cls: "paperforge-technical-details-toggle" }), - s = n.createEl("div", { cls: "paperforge-technical-details-body" }); - ((s.style.display = "none"), + s = e.createEl("div", { cls: "paperforge-technical-details" }), + a = s.createEl("button", { cls: "paperforge-technical-details-toggle" }), + n = s.createEl("div", { cls: "paperforge-technical-details-body" }); + ((n.style.display = "none"), this._techDetailsExpanded - ? ((s.style.display = "block"), + ? ((n.style.display = "block"), a.setText("\u6280\u672F\u8BE6\u60C5 \u25BE")) : a.setText("\u6280\u672F\u8BE6\u60C5 \u25B8"), a.addEventListener("click", () => { - let p = s.style.display !== "none"; - ((s.style.display = p ? "none" : "block"), + let p = n.style.display !== "none"; + ((n.style.display = p ? "none" : "block"), a.setText( p ? "\u6280\u672F\u8BE6\u60C5 \u25B8" @@ -5746,8 +5868,8 @@ Make sure paperforge is installed and in your PATH.`, ), (this._techDetailsExpanded = !p)); })); - let i = s.createEl("div", { cls: "paperforge-workflow-toggles" }), - l = [ + let i = n.createEl("div", { cls: "paperforge-workflow-toggles" }), + c = [ { key: "do_ocr", label: "OCR", hint: "\u52A0\u5165 OCR" }, { key: "analyze", @@ -5755,67 +5877,67 @@ Make sure paperforge is installed and in your PATH.`, hint: "\u6807\u8BB0\u7CBE\u8BFB", }, ]; - for (let p of l) { - let m = i.createEl("label", { cls: "paperforge-workflow-toggle" }), - y = m.createEl("input", { + for (let p of c) { + let g = i.createEl("label", { cls: "paperforge-workflow-toggle" }), + y = g.createEl("input", { type: "checkbox", cls: "paperforge-workflow-checkbox", }); ((y.checked = t[p.key] === !0), - m.createEl("span", { + g.createEl("span", { cls: "paperforge-workflow-toggle-label", text: p.label, }), - m.createEl("span", { + g.createEl("span", { cls: "paperforge-workflow-toggle-hint", text: p.hint, }), y.addEventListener("change", async () => { - let x = t.note_path + let E = t.note_path ? this.app.vault.getAbstractFileByPath(t.note_path) : null; - if (!x) { - new I.Notice("[!!] Note file not found", 6e3); + if (!E) { + new M.Notice("[!!] Note file not found", 6e3); return; } - let _ = y.checked; - (await this.app.fileManager.processFrontMatter(x, (b) => { - b[p.key] = _; + let f = y.checked; + (await this.app.fileManager.processFrontMatter(E, (b) => { + b[p.key] = f; }), - this._patchCachedEntry(r, { [p.key]: _ }), - (this._currentPaperEntry = Ze(this._currentPaperEntry, { - [p.key]: _, + this._patchCachedEntry(r, { [p.key]: f }), + (this._currentPaperEntry = qe(this._currentPaperEntry, { + [p.key]: f, }))); })); } - let c = t.health || {}, + let l = t.health || {}, d = [ - ["PDF Health", c.pdf_health || "\u2014"], + ["PDF Health", l.pdf_health || "\u2014"], ["OCR Status", t.ocr_status || "\u2014"], - ["Asset Health", c.asset_health || "\u2014"], + ["Asset Health", l.asset_health || "\u2014"], ["Note Path", t.note_path || "\u2014"], ["Fulltext Path", t.fulltext_path || "\u2014"], ], - f = new Set(["Note Path", "Fulltext Path", "Key"]); - for (let [p, m] of d) { - let y = s.createEl("div", { cls: "paperforge-technical-row" }); + _ = new Set(["Note Path", "Fulltext Path", "Key"]); + for (let [p, g] of d) { + let y = n.createEl("div", { cls: "paperforge-technical-row" }); y.createEl("span", { cls: "paperforge-technical-label", text: p }); - let x = y.createEl("span", { + let E = y.createEl("span", { cls: "paperforge-technical-value", - text: String(m), + text: String(g), }); - f.has(p) && - m && - m !== "\u2014" && - (x.addClass("pf-copy"), - x.addEventListener("click", () => { - (navigator.clipboard.writeText(m), new I.Notice(p + " copied")); + _.has(p) && + g && + g !== "\u2014" && + (E.addClass("pf-copy"), + E.addEventListener("click", () => { + (navigator.clipboard.writeText(g), new M.Notice(p + " copied")); })); } } _renderNextStepCard(e, t, r) { - var l, c; - let n = t.next_step || "ready", + var c, l; + let s = t.next_step || "ready", a = { sync: { label: "Sync Needed", @@ -5854,46 +5976,46 @@ Make sure paperforge is installed and in your PATH.`, icon: "\u2713", }, }, - s = a[n] || a.ready, + n = a[s] || a.ready, i = e.createEl("div", { cls: "paperforge-next-step-card" }); if ( - (n === "ready" && i.addClass("ready"), + (s === "ready" && i.addClass("ready"), i.createEl("div", { cls: "paperforge-next-step-label", text: "Recommended Next Step", }), - i.createEl("div", { cls: "paperforge-next-step-text", text: s.text }), - s.cmd && s.cmd !== "ready") + i.createEl("div", { cls: "paperforge-next-step-text", text: n.text }), + n.cmd && n.cmd !== "ready") ) { let d = i.createEl("button", { cls: "paperforge-next-step-trigger" }); - (d.createEl("span", { text: s.icon + " " + s.label }), + (d.createEl("span", { text: n.icon + " " + n.label }), d.addEventListener("click", () => { - let f = se.find((p) => p.cmd === s.cmd); - f && this._runAction(f, d); + let _ = oe.find((p) => p.cmd === n.cmd); + _ && this._runAction(_, d); })); - } else if (n === "/pf-deep") { + } else if (s === "/pf-deep") { let d = i.createEl("button", { cls: "paperforge-next-step-trigger" }); (d.createEl("span", { text: "\u{1F4CB} " + o("copy_pf_deep_cmd") }), d.addEventListener("click", () => { - let x = "/pf-deep " + r; + let E = "/pf-deep " + r; navigator.clipboard - .writeText(x) + .writeText(E) .then(() => { (d.setText("\u2713 " + o("copied")), - new I.Notice(x + " copied")); + new M.Notice(E + " copied")); }) .catch(() => { - new I.Notice("[!!] Clipboard write failed", 6e3); + new M.Notice("[!!] Clipboard write failed", 6e3); }); })); - let f = - ((c = - (l = this.app.plugins.plugins.paperforge) == null + let _ = + ((l = + (c = this.app.plugins.plugins.paperforge) == null ? void 0 - : l.settings) == null + : c.settings) == null ? void 0 - : c.agent_platform) || "opencode", - m = + : l.agent_platform) || "opencode", + g = { opencode: "OpenCode", claude: "Claude Code", @@ -5903,25 +6025,25 @@ Make sure paperforge is installed and in your PATH.`, codex: "Codex", gemini: "Gemini CLI", cline: "Cline", - }[f] || f; + }[_] || _; i.createEl("div", { cls: "paperforge-agent-platform-label" }).setText( - o("run_in_agent").replace("{0}", m) + o("run_in_agent").replace("{0}", g) ); } else - n === "ready" && + s === "ready" && i .createEl("button", { cls: "paperforge-next-step-trigger" }) - .createEl("span", { text: "\u2713 " + s.label }); + .createEl("span", { text: "\u2713 " + n.label }); } _openFulltext(e) { if (!e) { - new I.Notice("[!!] No fulltext path available for this paper", 6e3); + new M.Notice("[!!] No fulltext path available for this paper", 6e3); return; } let t = this.app.vault.getAbstractFileByPath(e); t ? this.app.workspace.openLinkText(t.path, "") - : new I.Notice("[!!] Fulltext file not found: " + e, 6e3); + : new M.Notice("[!!] Fulltext file not found: " + e, 6e3); } _renderCollectionMode() { let e = this._currentDomain || "Unknown", @@ -5934,138 +6056,138 @@ Make sure paperforge is installed and in your PATH.`, let r = this._contentEl.createEl("div", { cls: "paperforge-collection-view", }), - n = t.length, + s = t.length, a = 0, - s = 0, + n = 0, i = 0, - l = 0, c = 0, + l = 0, d = 0, - f = 0; - for (let g of t) { - (g.has_pdf && a++, - g.ocr_status === "done" && s++, - g.ocr_status === "done" && g.analyze === !0 && i++, - g.deep_reading_status === "done" && l++); - let v = g.ocr_status || ""; + _ = 0; + for (let m of t) { + (m.has_pdf && a++, + m.ocr_status === "done" && n++, + m.ocr_status === "done" && m.analyze === !0 && i++, + m.deep_reading_status === "done" && c++); + let v = m.ocr_status || ""; v === "pending" || v === "queued" - ? c++ + ? l++ : v === "processing" ? d++ : (v === "failed" || v === "blocked" || v === "done_incomplete" || v === "nopdf") && - f++; + _++; } r.createEl("div", { cls: "paperforge-collection-header" }).createEl("div", { cls: "paperforge-collection-title", text: e, }); - let m = r.createEl("div", { cls: "paperforge-workflow-overview" }); - m.createEl("div", { + let g = r.createEl("div", { cls: "paperforge-workflow-overview" }); + g.createEl("div", { cls: "paperforge-section-label", text: "Workflow Overview", }); - let y = m.createEl("div", { cls: "paperforge-workflow-funnel" }), - x = [ - { value: n, label: "Total" }, + let y = g.createEl("div", { cls: "paperforge-workflow-funnel" }), + E = [ + { value: s, label: "Total" }, { value: a, label: "PDF Ready" }, - { value: s, label: "OCR Done" }, - { value: l, label: "Deep Read" }, + { value: n, label: "OCR Done" }, + { value: c, label: "Deep Read" }, ]; - for (let g = 0; g < x.length; g++) { + for (let m = 0; m < E.length; m++) { let v = y.createEl("div", { cls: "paperforge-workflow-stage" }); (v.createEl("div", { cls: "paperforge-workflow-stage-value", - text: String(x[g].value), + text: String(E[m].value), }), v.createEl("div", { cls: "paperforge-workflow-stage-label", - text: x[g].label, + text: E[m].label, }), - g < x.length - 1 && + m < E.length - 1 && y.createEl("div", { cls: "paperforge-workflow-arrow", text: "\u2192", })); } - if (c + d + s + f > 0) { - let g = r.createEl("div", { cls: "paperforge-ocr-section" }), - v = g.createEl("div", { cls: "paperforge-collection-ocr-header" }); + if (l + d + n + _ > 0) { + let m = r.createEl("div", { cls: "paperforge-ocr-section" }), + v = m.createEl("div", { cls: "paperforge-collection-ocr-header" }); v.createEl("h4", { cls: "paperforge-ocr-title", text: "OCR Pipeline" }); - let w = v.createEl("span", { cls: "paperforge-ocr-badge idle" }); + let k = v.createEl("span", { cls: "paperforge-ocr-badge idle" }); d > 0 - ? (w.addClass("active"), w.setText("Processing")) - : c > 0 - ? w.setText("Pending") - : (w.addClass("idle"), w.setText("Idle")); - let T = g.createEl("div", { cls: "paperforge-progress-track" }); - d > 0 && T.addClass("paperforge-processing"); - let P = c + d + s + f, - L = [ - { cls: "pending", count: c }, + ? (k.addClass("active"), k.setText("Processing")) + : l > 0 + ? k.setText("Pending") + : (k.addClass("idle"), k.setText("Idle")); + let R = m.createEl("div", { cls: "paperforge-progress-track" }); + d > 0 && R.addClass("paperforge-processing"); + let P = l + d + n + _, + O = [ + { cls: "pending", count: l }, { cls: "active", count: d }, - { cls: "done", count: s }, - { cls: "failed", count: f }, + { cls: "done", count: n }, + { cls: "failed", count: _ }, ]; - for (let q of L) - if (q.count > 0) { - let D = ((q.count / P) * 100).toFixed(1); - T.createEl("div", { - cls: `paperforge-progress-seg ${q.cls}`, - attr: { style: `width:${D}%` }, + for (let H of O) + if (H.count > 0) { + let B = ((H.count / P) * 100).toFixed(1); + R.createEl("div", { + cls: `paperforge-progress-seg ${H.cls}`, + attr: { style: `width:${B}%` }, }); } - let O = g.createEl("div", { cls: "paperforge-ocr-counts" }), - z = [ - { cls: "pending", value: c, label: "Pending" }, + let D = m.createEl("div", { cls: "paperforge-ocr-counts" }), + j = [ + { cls: "pending", value: l, label: "Pending" }, { cls: "active", value: d, label: "Processing" }, - { cls: "done", value: s, label: "Done" }, - { cls: "failed", value: f, label: "Attention" }, + { cls: "done", value: n, label: "Done" }, + { cls: "failed", value: _, label: "Attention" }, ]; - for (let q of z) { - let D = O.createEl("div", { cls: "paperforge-ocr-count" }); - (D.createEl("div", { + for (let H of j) { + let B = D.createEl("div", { cls: "paperforge-ocr-count" }); + (B.createEl("div", { cls: "paperforge-ocr-count-value", - text: q.value.toString(), + text: H.value.toString(), }), - D.createEl("div", { + B.createEl("div", { cls: "paperforge-ocr-count-label", - text: q.label, + text: H.label, })); } } - let _ = r.createEl("div", { cls: "paperforge-collection-actions" }), - b = _.createEl("button", { cls: "paperforge-contextual-btn primary" }); + let f = r.createEl("div", { cls: "paperforge-collection-actions" }), + b = f.createEl("button", { cls: "paperforge-contextual-btn primary" }); (b.createEl("span", { cls: "paperforge-contextual-btn-icon", text: "\u229E", }), b.createEl("span", { text: "Run OCR" }), b.addEventListener("click", () => { - let g = se.find((v) => v.id === "paperforge-ocr"); - g && this._runAction(g, b); + let m = oe.find((v) => v.id === "paperforge-ocr"); + m && this._runAction(m, b); })); - let E = _.createEl("button", { cls: "paperforge-contextual-btn" }); - (E.createEl("span", { + let w = f.createEl("button", { cls: "paperforge-contextual-btn" }); + (w.createEl("span", { cls: "paperforge-contextual-btn-icon", text: "\u21BB", }), - E.createEl("span", { text: "Sync Library" }), - E.addEventListener("click", () => { - let g = se.find((v) => v.id === "paperforge-sync"); - g && this._runAction(g, E); + w.createEl("span", { text: "Sync Library" }), + w.addEventListener("click", () => { + let m = oe.find((v) => v.id === "paperforge-sync"); + m && this._runAction(m, w); })); - let k = _.createEl("button", { cls: "paperforge-contextual-btn warn" }); - (k.createEl("span", { + let x = f.createEl("button", { cls: "paperforge-contextual-btn warn" }); + (x.createEl("span", { cls: "paperforge-contextual-btn-icon", text: "\u21BA", }), - k.createEl("span", { text: "Redo OCR" }), - k.addEventListener("click", () => { - let g = se.find((v) => v.id === "paperforge-ocr-redo"); - g && this._runAction(g, k); + x.createEl("span", { text: "Redo OCR" }), + x.addEventListener("click", () => { + let m = oe.find((v) => v.id === "paperforge-ocr-redo"); + m && this._runAction(m, x); }), this.renderSearchSection(r)); } @@ -6102,12 +6224,12 @@ Make sure paperforge is installed and in your PATH.`, } _switchToVersionMode(e) { let r = this.app.vault.adapter.basePath, - n = typeof r == "string" ? r : ""; - if (!n) { - new I.Notice("Cannot determine vault path"); + s = typeof r == "string" ? r : ""; + if (!s) { + new M.Notice("Cannot determine vault path"); return; } - ((this._versionPapers = nt(n)), + ((this._versionPapers = lt(s)), (this._versionFilter = ""), (this._currentMode = "versions"), (this._currentFilePath = null), @@ -6124,8 +6246,8 @@ Make sure paperforge is installed and in your PATH.`, cls: "paperforge-version-panel", }), r = this.app.vault.adapter.basePath, - n = typeof r == "string" ? r : ""; - if (!n) { + s = typeof r == "string" ? r : ""; + if (!s) { e.createEl("div", { cls: "paperforge-status-error", text: "Could not determine vault path", @@ -6133,61 +6255,61 @@ Make sure paperforge is installed and in your PATH.`, return; } (!this._versionPapers || this._versionPapers.length === 0) && - (this._versionPapers = nt(n)); + (this._versionPapers = lt(s)); let a = e.createEl("div", { cls: "paperforge-version-left" }), - s = e.createEl("div", { cls: "paperforge-version-right" }), + n = e.createEl("div", { cls: "paperforge-version-right" }), i = a.createEl("input", { cls: "paperforge-version-filter", attr: { type: "text", placeholder: o("version_filter_placeholder") }, }); i.value = this._versionFilter; - let l = a.createEl("div", { cls: "paperforge-version-paper-list" }), - c = () => { - l.empty(); + let c = a.createEl("div", { cls: "paperforge-version-paper-list" }), + l = () => { + c.empty(); let b = this._versionFilter.toLowerCase(), - E = this._versionPapers + w = this._versionPapers ? this._versionPapers.filter( - (g) => + (m) => !b || - g.key.toLowerCase().includes(b) || - g.title.toLowerCase().includes(b) + m.key.toLowerCase().includes(b) || + m.title.toLowerCase().includes(b) ) : []; - if (E.length === 0) { - l.createEl("div", { + if (w.length === 0) { + c.createEl("div", { cls: "paperforge-meta", text: o("version_no_backups"), }); return; } - let k = l.createEl("div", { + let x = c.createEl("div", { cls: "paperforge-meta", - text: o("version_papers_count").replace("{n}", String(E.length)), + text: o("version_papers_count").replace("{n}", String(w.length)), }); - for (let g of E) { - let v = l.createEl("div", { cls: "paperforge-version-paper-item" }), - w = v.createEl("span", { + for (let m of w) { + let v = c.createEl("div", { cls: "paperforge-version-paper-item" }), + k = v.createEl("span", { cls: "paperforge-version-paper-title", - text: g.title, + text: m.title, }), - T = v.createEl("span", { + R = v.createEl("span", { cls: "paperforge-version-paper-versions", - text: g.versions.map((P) => P.label).join(" "), + text: m.versions.map((P) => P.label).join(" "), }); v.addEventListener("click", () => { - (l + (c .querySelectorAll(".paperforge-version-paper-item.selected") .forEach((P) => P.removeClass("selected")), v.addClass("selected"), - f(g)); + _(m)); }); } }; i.addEventListener("input", () => { - ((this._versionFilter = i.value), c()); + ((this._versionFilter = i.value), l()); }); - let d = s.createEl("div", { cls: "paperforge-version-timeline-area" }), - f = (b) => { + let d = n.createEl("div", { cls: "paperforge-version-timeline-area" }), + _ = (b) => { if ( (d.empty(), d @@ -6201,119 +6323,115 @@ Make sure paperforge is installed and in your PATH.`, }); return; } - let k = d.createEl("div", { cls: "paperforge-version-timeline" }); - for (let g of b.versions) { - let v = g.label === b.currentLabel, - w = k.createEl("div", { + let x = d.createEl("div", { cls: "paperforge-version-timeline" }); + for (let m of b.versions) { + let v = m.label === b.currentLabel, + k = x.createEl("div", { cls: "paperforge-version-entry" + (v ? " paperforge-version-current" : ""), }), - T = w.createEl("div", { cls: "paperforge-version-dot" }), - P = w.createEl("div", { cls: "paperforge-version-content" }), - L = P.createEl("div", { cls: "paperforge-version-label-row" }); - (L.createEl("span", { + R = k.createEl("div", { cls: "paperforge-version-dot" }), + P = k.createEl("div", { cls: "paperforge-version-content" }), + O = P.createEl("div", { cls: "paperforge-version-label-row" }); + (O.createEl("span", { cls: "paperforge-version-label", - text: g.label, + text: m.label, }), v && - L.createEl("span", { + O.createEl("span", { cls: "paperforge-version-current-tag", text: o("version_current"), })); - let O = g.created_at ? g.created_at.slice(0, 10) : ""; + let D = m.created_at ? m.created_at.slice(0, 10) : ""; P.createEl("div", { cls: "paperforge-meta", - text: O + " \u2014 " + g.source, + text: D + " \u2014 " + m.source, }); - let z = g.fulltext_size - ? g.fulltext_size > 1024 - ? (g.fulltext_size / 1024).toFixed(0) + "KB" - : g.fulltext_size + "B" + let j = m.fulltext_size + ? m.fulltext_size > 1024 + ? (m.fulltext_size / 1024).toFixed(0) + "KB" + : m.fulltext_size + "B" : ""; - z && P.createEl("div", { cls: "paperforge-meta", text: z }); - let q = P.createEl("div", { cls: "paperforge-version-actions" }); - (q - .createEl("button", { - cls: "pf-btn-primary", - text: o("version_restore_btn"), - }) - .addEventListener("click", () => { - Rt(n, b.key, g.label) - ? new I.Notice( - o("version_restore_done").replace("{label}", g.label) - ) - : new I.Notice("Restore failed", 6e3); - }), + j && P.createEl("div", { cls: "paperforge-meta", text: j }); + let H = P.createEl("div", { cls: "paperforge-version-actions" }); + (H.createEl("button", { + cls: "pf-btn-primary", + text: o("version_restore_btn"), + }).addEventListener("click", () => { + Bt(s, b.key, m.label) + ? new M.Notice( + o("version_restore_done").replace("{label}", m.label) + ) + : new M.Notice("Restore failed", 6e3); + }), b.versions.length > 1 && !v && - q - .createEl("button", { - cls: "pf-btn-secondary", - text: o("version_compare_btn"), - }) - .addEventListener("click", () => { - m(b, g.label, b.currentLabel); - })); + H.createEl("button", { + cls: "pf-btn-secondary", + text: o("version_compare_btn"), + }).addEventListener("click", () => { + g(b, m.label, b.currentLabel); + })); } }, - p = s.createEl("div", { cls: "paperforge-version-compare" }); + p = n.createEl("div", { cls: "paperforge-version-compare" }); p.style.display = "none"; - let m = (b, E, k) => { - let g = Tt(n, b.key, E, k); + let g = (b, w, x) => { + let m = At(s, b.key, w, x); ((p.style.display = "block"), p.empty()); let v = p.createEl("div", { cls: "paperforge-version-compare-header" }); if ( (v.createEl("span", { cls: "pf-title", text: o("version_compare_title") - .replace("{vA}", E) - .replace("{vB}", k), + .replace("{vA}", w) + .replace("{vB}", x), }), v.createEl("span", { cls: "paperforge-meta", text: o("version_compare_paragraphs").replace( "{n}", - String(g.length) + String(m.length) ), }), - g.length === 0) + m.length === 0) ) { p.createEl("div", { cls: "paperforge-meta", text: "No changes" }); return; } - let w = p.createEl("div", { cls: "paperforge-version-diff-list" }); - for (let T of g) { - let P = w.createEl("div", { cls: "paperforge-version-diff-row" }), - L = - T.type === "added" ? "[+]" : T.type === "removed" ? "[-]" : "[~]", - O = T.heading || "paragraph " + (T.paragraphIndex + 1); + let k = p.createEl("div", { cls: "paperforge-version-diff-list" }); + for (let R of m) { + let P = k.createEl("div", { cls: "paperforge-version-diff-row" }), + O = + R.type === "added" ? "[+]" : R.type === "removed" ? "[-]" : "[~]", + D = R.heading || "paragraph " + (R.paragraphIndex + 1); (P.createEl("span", { cls: "paperforge-version-diff-label", - text: L + " " + O, + text: O + " " + D, }), - T.oldText && + R.oldText && P.createEl("pre", { cls: "paperforge-version-diff-old", - text: T.oldText.slice(0, 200), + text: R.oldText.slice(0, 200), }), - T.newText && + R.newText && P.createEl("pre", { cls: "paperforge-version-diff-new", - text: T.newText.slice(0, 200), + text: R.newText.slice(0, 200), })); } }, y = e.createEl("div", { cls: "paperforge-version-actions-bar" }), - x = y.createEl("button", { + E = y.createEl("button", { cls: "pf-btn-primary", text: o("version_restore_selected"), }), - _ = y.createEl("button", { + f = y.createEl("button", { cls: "pf-btn-secondary", text: o("version_clear_old").replace("{size}", ""), }); - c(); + l(); } renderSearchSection(e) { ((this._searchContainer = e.createEl("div", { @@ -6325,7 +6443,7 @@ Make sure paperforge is installed and in your PATH.`, let r = this._searchContainer.createEl("div", { cls: "paperforge-search-input-row", }), - n = r.createEl("span", { cls: "paperforge-search-mode", text: "M" }); + s = r.createEl("span", { cls: "paperforge-search-mode", text: "M" }); ((this._searchInput = r.createEl("input", { cls: "paperforge-search-input", attr: { @@ -6338,20 +6456,20 @@ Make sure paperforge is installed and in your PATH.`, })), (this._searchInput.placeholder = o("retrieval_search_placeholder")), this._searchInput.addEventListener("input", () => { - var s; - let a = ((s = this._searchInput) == null ? void 0 : s.value) || ""; + var n; + let a = ((n = this._searchInput) == null ? void 0 : n.value) || ""; if ( (a.startsWith("@") && !a.startsWith("@ ") ? ((this._searchMode = "@"), - n.setText("@"), - n.addClass("deep"), + s.setText("@"), + s.addClass("deep"), this._searchInput && (this._searchInput.placeholder = o( "retrieval_search_placeholder_deep" ))) : ((this._searchMode = "M"), - n.setText("M"), - n.removeClass("deep"), + s.setText("M"), + s.removeClass("deep"), this._searchInput && (this._searchInput.placeholder = o( "retrieval_search_placeholder" @@ -6371,7 +6489,7 @@ Make sure paperforge is installed and in your PATH.`, }, 200)); }), this._searchInput.addEventListener("keydown", (a) => { - var s, i; + var n, i; if (a.key === "Escape") { (a.preventDefault(), this._searchInput && @@ -6385,27 +6503,27 @@ Make sure paperforge is installed and in your PATH.`, if (a.key === "ArrowDown" || a.key === "ArrowUp") { if ( this._searchState !== "results" || - !((s = this._searchResults) != null && s.length) + !((n = this._searchResults) != null && n.length) ) return; a.preventDefault(); - let l = this._searchResults.length; + let c = this._searchResults.length; a.key === "ArrowDown" ? (this._searchActiveIndex = Math.min( this._searchActiveIndex + 1, - l - 1 + c - 1 )) : (this._searchActiveIndex = Math.max( this._searchActiveIndex - 1, -1 )); - let c = + let l = (i = this._searchResultsEl) == null ? void 0 : i.querySelectorAll(".paperforge-search-result-card"); - c && - c.forEach((d, f) => { - f === this._searchActiveIndex + l && + l.forEach((d, _) => { + _ === this._searchActiveIndex ? (d.setAttribute("aria-selected", "true"), d.classList.add("active")) : (d.setAttribute("aria-selected", "false"), @@ -6417,10 +6535,10 @@ Make sure paperforge is installed and in your PATH.`, (a.preventDefault(), this._searchTimer && (clearTimeout(this._searchTimer), (this._searchTimer = void 0))); - let l = this._searchMode; + let c = this._searchMode; ((this._searchMode = "@"), this.executeSearch(), - (this._searchMode = l)); + (this._searchMode = c)); return; } a.key === "Enter" && @@ -6502,10 +6620,10 @@ Make sure paperforge is installed and in your PATH.`, text: o("retrieval_open_vector_settings"), }); (r.addEventListener("click", () => { - let n = this.app.setting; - if (n && typeof n == "object") { - let a = n.openTab; - typeof a == "function" && a.call(n, "paperforge"); + let s = this.app.setting; + if (s && typeof s == "object") { + let a = s.openTab; + typeof a == "function" && a.call(s, "paperforge"); } }), setTimeout(() => { @@ -6528,16 +6646,16 @@ Make sure paperforge is installed and in your PATH.`, text: o("retrieval_backend_unavailable_desc"), })); let r = t.createEl("div", { cls: "paperforge-search-state-actions" }), - n = r.createEl("button", { + s = r.createEl("button", { cls: "pf-btn-primary", text: o("retrieval_run_doctor"), }); - (n.addEventListener("click", () => { - let s = this.app.vault.adapter.basePath; - if (typeof s == "string") { - let { path: i, extraArgs: l = [] } = N(s, null, void 0, void 0); - (0, _e.spawn)(i, [...l, "-m", "paperforge", "doctor"], { - cwd: s, + (s.addEventListener("click", () => { + let n = this.app.vault.adapter.basePath; + if (typeof n == "string") { + let { path: i, extraArgs: c = [] } = z(n, null, void 0, void 0); + (0, ve.spawn)(i, [...c, "-m", "paperforge", "doctor"], { + cwd: n, stdio: "inherit", }); } @@ -6551,7 +6669,7 @@ Make sure paperforge is installed and in your PATH.`, this.executeSearch(); }), setTimeout(() => { - n.focus(); + s.focus(); }, 100)); break; } @@ -6600,10 +6718,10 @@ Make sure paperforge is installed and in your PATH.`, text: o("retrieval_rebuild_vectors"), }); (r.addEventListener("click", () => { - let n = this.app.setting; - if (n && typeof n == "object") { - let a = n.openTab; - typeof a == "function" && a.call(n, "paperforge"); + let s = this.app.setting; + if (s && typeof s == "object") { + let a = s.openTab; + typeof a == "function" && a.call(s, "paperforge"); } }), setTimeout(() => { @@ -6633,85 +6751,85 @@ Make sure paperforge is installed and in your PATH.`, let t = this._searchMode === "@" || e.startsWith("@"), r = t ? e.replace(/^@\s*/, "").trim() : e; if (!r) return; - let n = t ? "retrieve" : "search"; + let s = t ? "retrieve" : "search"; ((this._searchState = "searching"), (this._searchResults = null), (this._searchActiveIndex = -1), this._renderSearchState()); let a = this.app.vault.adapter, - s = ""; + n = ""; if (a && typeof a == "object" && "basePath" in a) { - let x = a.basePath; - s = typeof x == "string" ? x : ""; + let E = a.basePath; + n = typeof E == "string" ? E : ""; } - if (!s) { + if (!n) { ((this._searchState = "backend-unavailable"), this._renderSearchState()); return; } let i = null, - c = this.app.plugins; - if (c && typeof c == "object" && "plugins" in c) { - let x = c.plugins; - if (x && typeof x == "object" && "paperforge" in x) { - let _ = x.paperforge; - _ && typeof _ == "object" && "settings" in _ && (i = _.settings); + l = this.app.plugins; + if (l && typeof l == "object" && "plugins" in l) { + let E = l.plugins; + if (E && typeof E == "object" && "paperforge" in E) { + let f = E.paperforge; + f && typeof f == "object" && "settings" in f && (i = f.settings); } } - let { path: d, extraArgs: f = [] } = N(s, i, void 0, void 0), - p = n === "retrieve" ? ["--deep"] : [], - m = (0, _e.spawn)( + let { path: d, extraArgs: _ = [] } = z(n, i, void 0, void 0), + p = s === "retrieve" ? ["--deep"] : [], + g = (0, ve.spawn)( d, - [...f, "-m", "paperforge", "--vault", s, n, r, ...p, "--json"], - { cwd: s, timeout: 3e4 } + [..._, "-m", "paperforge", "--vault", n, s, r, ...p, "--json"], + { cwd: n, timeout: 3e4 } ), y = []; - (m.stdout.on("data", (x) => { - y.push(x.toString("utf-8")); + (g.stdout.on("data", (E) => { + y.push(E.toString("utf-8")); }), - m.stderr.on("data", () => {}), - m.on("close", (x) => { - if (x !== 0) { - let g = Xe(String(x)); - ((this._searchState = this._mapErrorToSearchState(g.type)), + g.stderr.on("data", () => {}), + g.on("close", (E) => { + if (E !== 0) { + let m = Qe(String(E)); + ((this._searchState = this._mapErrorToSearchState(m.type)), this._renderSearchState()); return; } - let _ = y.join(""), - b = _.indexOf("{"), - E = _.lastIndexOf("}"), - k = ""; - if (b !== -1 && E > b) k = _.slice(b, E + 1); + let f = y.join(""), + b = f.indexOf("{"), + w = f.lastIndexOf("}"), + x = ""; + if (b !== -1 && w > b) x = f.slice(b, w + 1); else { - let g = _.indexOf("["), - v = _.lastIndexOf("]"); - g !== -1 && v > g && (k = _.slice(g, v + 1)); + let m = f.indexOf("["), + v = f.lastIndexOf("]"); + m !== -1 && v > m && (x = f.slice(m, v + 1)); } - if (!k) { + if (!x) { ((this._searchState = "internal-error"), this._renderSearchState()); return; } try { - let g = JSON.parse(k), + let m = JSON.parse(x), v = []; - if (g && typeof g == "object" && "data" in g) { - let w = g.data; - if (w && typeof w == "object") { - let T = w; - "matches" in T && Array.isArray(T.matches) && (v = T.matches); + if (m && typeof m == "object" && "data" in m) { + let k = m.data; + if (k && typeof k == "object") { + let R = k; + "matches" in R && Array.isArray(R.matches) && (v = R.matches); } } ((this._searchResults = v), (this._searchState = v.length > 0 ? "results" : "empty"), this._renderSearchState()); - } catch (g) { - let v = g instanceof Error ? g.message : String(g); + } catch (m) { + let v = m instanceof Error ? m.message : String(m); ((this._searchState = "internal-error"), this._renderSearchState()); } }), - m.on("error", (x) => { - let _ = x.code; - if (typeof _ == "string") { - let b = Xe(_); + g.on("error", (E) => { + let f = E.code; + if (typeof f == "string") { + let b = Qe(f); this._searchState = this._mapErrorToSearchState(b.type); } else this._searchState = "backend-unavailable"; this._renderSearchState(); @@ -6764,87 +6882,87 @@ Make sure paperforge is installed and in your PATH.`, text: t ? "@" : "M", })); for (let a = 0; a < e.length; a++) { - let s = e[a]; - if (!s || typeof s != "object") continue; - let i = s, - l = a === this._searchActiveIndex, - c = this._searchResultsEl.createEl("div", { + let n = e[a]; + if (!n || typeof n != "object") continue; + let i = n, + c = a === this._searchActiveIndex, + l = this._searchResultsEl.createEl("div", { cls: "paperforge-search-result-card", attr: { role: "option", tabindex: "0", - "aria-selected": l ? "true" : "false", + "aria-selected": c ? "true" : "false", "aria-posinset": String(a + 1), "aria-setsize": String(e.length), }, }); - l && c.addClass("active"); + c && l.addClass("active"); let d = typeof i.title == "string" ? i.title : typeof i.file_name == "string" ? i.file_name : "(untitled)"; - c.createEl("div", { cls: "paperforge-search-result-title", text: d }); - let f = typeof i.zotero_key == "string" ? i.zotero_key : "", + l.createEl("div", { cls: "paperforge-search-result-title", text: d }); + let _ = typeof i.zotero_key == "string" ? i.zotero_key : "", p = typeof i.main_note_path == "string" && i.main_note_path ? i.main_note_path : null, - m = typeof i.note_path == "string" && i.note_path ? i.note_path : null, - y = p || m; - if (!y && f) { + g = typeof i.note_path == "string" && i.note_path ? i.note_path : null, + y = p || g; + if (!y && _) { let b = this._getCachedIndex().find( - (E) => - E !== null && - typeof E == "object" && - "zotero_key" in E && - E.zotero_key === f + (w) => + w !== null && + typeof w == "object" && + "zotero_key" in w && + w.zotero_key === _ ); if (b && typeof b == "object") { - let E = b; + let w = b; y = - typeof E.main_note_path == "string" && E.main_note_path - ? E.main_note_path - : typeof E.note_path == "string" && E.note_path - ? E.note_path + typeof w.main_note_path == "string" && w.main_note_path + ? w.main_note_path + : typeof w.note_path == "string" && w.note_path + ? w.note_path : null; } } (y - ? c.addEventListener("click", (_) => { - let b = _.ctrlKey || _.metaKey; + ? l.addEventListener("click", (f) => { + let b = f.ctrlKey || f.metaKey; this.app.workspace.openLinkText(y, "", b); }) - : c.addEventListener("click", () => { - new I.Notice("[!!] Note not found: " + (f || "unknown"), 6e3); + : l.addEventListener("click", () => { + new M.Notice("[!!] Note not found: " + (_ || "unknown"), 6e3); }), - c.addEventListener("keydown", (_) => { - if (_.key === "Enter" && y) { - _.preventDefault(); - let b = _.ctrlKey || _.metaKey; + l.addEventListener("keydown", (f) => { + if (f.key === "Enter" && y) { + f.preventDefault(); + let b = f.ctrlKey || f.metaKey; this.app.workspace.openLinkText(y, "", b); } })); - let x = c.createEl("div", { cls: "paperforge-search-result-meta" }); + let E = l.createEl("div", { cls: "paperforge-search-result-meta" }); if ( (typeof i.first_author == "string" && i.first_author && - x.createEl("span", { + E.createEl("span", { cls: "paperforge-search-result-author", text: i.first_author, }), typeof i.journal == "string" && i.journal && - x.createEl("span", { + E.createEl("span", { cls: "paperforge-search-result-journal", text: i.journal, }), i.score !== void 0) ) { - let _ = i.score, - b = typeof _ == "number" ? _.toFixed(3) : String(_); - x.createEl("span", { + let f = i.score, + b = typeof f == "number" ? f.toFixed(3) : String(f); + E.createEl("span", { cls: "paperforge-search-result-score", text: "Score: " + b, }); @@ -6852,23 +6970,23 @@ Make sure paperforge is installed and in your PATH.`, if ( (typeof i.domain == "string" && i.domain && - c.createEl("span", { + l.createEl("span", { cls: "paperforge-search-result-tag", text: i.domain, }), typeof i.abstract == "string" && i.abstract) ) { - let _ = i.abstract; - c.createEl("div", { + let f = i.abstract; + l.createEl("div", { cls: "paperforge-search-result-abstract", - text: _.length > 200 ? _.slice(0, 200) + "..." : _, + text: f.length > 200 ? f.slice(0, 200) + "..." : f, }); } if (t && typeof i.text == "string" && i.text) { - let _ = i.text; - c.createEl("div", { + let f = i.text; + l.createEl("div", { cls: "paperforge-search-result-source", - text: _.length > 300 ? _.slice(0, 300) + "..." : _, + text: f.length > 300 ? f.slice(0, 300) + "..." : f, }); } } @@ -6882,9 +7000,9 @@ Make sure paperforge is installed and in your PATH.`, })); } _runAction(e, t) { - var p, m; + var p, g; if (e.disabled) { - new I.Notice( + new M.Notice( `[i] ${e.disabledMsg || "This action is not yet available."}`, 6e3 ); @@ -6894,25 +7012,25 @@ Make sure paperforge is installed and in your PATH.`, t.addClass("running"); let r = this.app.vault.adapter.basePath; this._showMessage("Processing...", "running"); - let n = Array.isArray(e.args) ? [...e.args] : []; + let s = Array.isArray(e.args) ? [...e.args] : []; if (e.needsKey) { let y = this.app.workspace.getActiveFile(), - x = null; + E = null; if (y) { - let _ = this.app.metadataCache.getFileCache(y); + let f = this.app.metadataCache.getFileCache(y); if ( - (_ && _.frontmatter && _.frontmatter.zotero_key - ? (x = _.frontmatter.zotero_key) - : (x = this._extractZoteroKeyFromPath(y.path)), - x) + (f && f.frontmatter && f.frontmatter.zotero_key + ? (E = f.frontmatter.zotero_key) + : (E = this._extractZoteroKeyFromPath(y.path)), + E) ) - n = [...n, x]; - else if (_ && _.frontmatter) { + s = [...s, E]; + else if (f && f.frontmatter) { (this._showMessage( "[!!] No zotero_key in active note frontmatter", "error" ), - new I.Notice( + new M.Notice( "[!!] Open a paper note with a zotero_key in its frontmatter first", 6e3 ), @@ -6920,7 +7038,7 @@ Make sure paperforge is installed and in your PATH.`, return; } else { (this._showMessage("[!!] No frontmatter in active note", "error"), - new I.Notice( + new M.Notice( "[!!] The active note has no frontmatter with a zotero_key", 6e3 ), @@ -6929,7 +7047,7 @@ Make sure paperforge is installed and in your PATH.`, } } else { (this._showMessage("[!!] No active note open", "error"), - new I.Notice( + new M.Notice( "[!!] Open a paper note with a zotero_key in its frontmatter first", 6e3 ), @@ -6937,91 +7055,91 @@ Make sure paperforge is installed and in your PATH.`, return; } } - e.needsFilter && (n = [...n, "--all"]); + e.needsFilter && (s = [...s, "--all"]); let a = e.needsFilter ? 6e4 : e.needsKey ? 3e4 : 6e5, - { path: s, extraArgs: i = [] } = N( + { path: n, extraArgs: i = [] } = z( r, - (m = + (g = (p = this.app.plugins.plugins.paperforge) == null ? void 0 : p.settings) != null - ? m + ? g : null, void 0, void 0 ), - l = (0, _e.spawn)(s, [...i, "-m", "paperforge", e.cmd, ...n], { + c = (0, ve.spawn)(n, [...i, "-m", "paperforge", e.cmd, ...s], { cwd: r, timeout: a, }), - c = [], + l = [], d = Date.now(), - f = setInterval(() => this._fetchStats(!0), 4e3); - (l.stdout.on("data", (y) => { - let x = y + _ = setInterval(() => this._fetchStats(!0), 4e3); + (c.stdout.on("data", (y) => { + let E = y .toString("utf-8") .split( ` ` ) .filter(Boolean); - for (let _ of x) { - let b = _.trim(); + for (let f of E) { + let b = f.trim(); b && - (c.push(b), + (l.push(b), this._showMessage( - c.slice(-8).join(` + l.slice(-8).join(` `), "running" )); } }), - l.stderr.on("data", (y) => { - let x = y + c.stderr.on("data", (y) => { + let E = y .toString("utf-8") .split( ` ` ) .filter(Boolean); - for (let _ of x) { - if (_.includes("\r") || _.includes("%") || _.includes("\u2588")) + for (let f of E) { + if (f.includes("\r") || f.includes("%") || f.includes("\u2588")) continue; - let b = _.trim(); + let b = f.trim(); b && !b.match(/^\d+%|^\|/) && - (c.push(b), + (l.push(b), this._showMessage( - c.slice(-8).join(` + l.slice(-8).join(` `), "running" )); } }), - l.on("close", (y) => { - (clearInterval(f), t.removeClass("running")); - let x = ((Date.now() - d) / 1e3).toFixed(1); + c.on("close", (y) => { + (clearInterval(_), t.removeClass("running")); + let E = ((Date.now() - d) / 1e3).toFixed(1); if (y !== 0) { - let _ = c.slice(-3).join(" | ") || "exit code " + y; + let f = l.slice(-3).join(" | ") || "exit code " + y; (e.cmd === "repair" || e.cmd === "ocr") && y === 1 - ? (this._showMessage("[WARN] " + _, "running"), - new I.Notice("[WARN] " + e.cmd + " partial: " + _, 8e3), + ? (this._showMessage("[WARN] " + f, "running"), + new M.Notice("[WARN] " + e.cmd + " partial: " + f, 8e3), this._fetchStats(!0)) - : (this._showMessage("[!!] " + _, "error"), - new I.Notice("[!!] " + e.cmd + " failed: " + _, 8e3)); + : (this._showMessage("[!!] " + f, "error"), + new M.Notice("[!!] " + e.cmd + " failed: " + f, 8e3)); } else if (e.needsKey || e.needsFilter) { - let _ = c.join(` + let f = l.join(` `); - if (_.trim()) + if (f.trim()) try { - (JSON.parse(_), + (JSON.parse(f), navigator.clipboard - .writeText(_) + .writeText(f) .then(() => { - let b = `${x}s \u2014 ${_.length} chars copied`; + let b = `${E}s \u2014 ${f.length} chars copied`; (this._showMessage("[OK] " + e.title + ": " + b, "ok"), - new I.Notice( - "[OK] " + e.okMsg + " \u2014 " + _.length + " chars" + new M.Notice( + "[OK] " + e.okMsg + " \u2014 " + f.length + " chars" )); }) .catch((b) => { @@ -7029,11 +7147,11 @@ Make sure paperforge is installed and in your PATH.`, "[!!] Clipboard write failed: " + b.message, "error" ), - new I.Notice("[!!] Clipboard error", 6e3)); + new M.Notice("[!!] Clipboard error", 6e3)); })); } catch (b) { (this._showMessage("[!!] Invalid JSON from " + e.title, "error"), - new I.Notice( + new M.Notice( "[!!] " + e.title + " returned invalid JSON: " + @@ -7043,33 +7161,33 @@ Make sure paperforge is installed and in your PATH.`, } else (this._showMessage("[!!] No output from context command", "error"), - new I.Notice("[!!] Context command returned empty output", 8e3)); + new M.Notice("[!!] Context command returned empty output", 8e3)); this._fetchStats(!0); } else { let b = - c.filter((k) => k.match(/updated \d+/)).pop() || - c[c.length - 1] || + l.filter((x) => x.match(/updated \d+/)).pop() || + l[l.length - 1] || "", - E = `${x}s \u2014 ${b}`; - (this._showMessage("[OK] " + e.title + ": " + E, "ok"), - new I.Notice("[OK] " + e.okMsg), + w = `${E}s \u2014 ${b}`; + (this._showMessage("[OK] " + e.title + ": " + w, "ok"), + new M.Notice("[OK] " + e.okMsg), this._contentEl && this._contentEl.removeClass("switching"), (this._cachedStats = null)); try { this._fetchStats(!1); - } catch (k) { - console.log("[PF] fetchStats error:", k); + } catch (x) { + console.log("[PF] fetchStats error:", x); } (console.log("[PF] close cmd=" + e.cmd + " id=" + e.id), e.cmd === "sync" && - ze(this.app, this.app.plugins.plugins.paperforge, r)); + Ke(this.app, this.app.plugins.plugins.paperforge, r)); } }), - l.on("error", (y) => { + c.on("error", (y) => { (t.removeClass("running"), this._contentEl && this._contentEl.removeClass("switching"), this._showMessage("[!!] " + y.message, "error"), - new I.Notice("[!!] Cannot start: " + y.message, 8e3)); + new M.Notice("[!!] Cannot start: " + y.message, 8e3)); })); } _showMessage(e, t) { @@ -7128,9 +7246,9 @@ Make sure paperforge is installed and in your PATH.`, (this._leafChangeTimer && clearTimeout(this._leafChangeTimer), (this._leafChangeTimer = setTimeout(() => { let r = this._resolveModeForFile(this.app.workspace.getActiveFile()), - n = r.mode, + s = r.mode, a = r.filePath; - (this._currentMode === n && this._currentFilePath === a) || + (this._currentMode === s && this._currentFilePath === a) || this._detectAndSwitch(); }, 300))); }); @@ -7144,18 +7262,18 @@ Make sure paperforge is installed and in your PATH.`, this._modeSubscribers.push({ event: "modify", ref: t }); } static async open(e) { - let t = e.app.workspace.getLeavesOfType(ve); + let t = e.app.workspace.getLeavesOfType(we); if (t.length > 0) { e.app.workspace.revealLeaf(t[0]); return; } let r = e.app.workspace.getRightLeaf(!1); r && - (await r.setViewState({ type: ve, active: !0 }), + (await r.setViewState({ type: we, active: !0 }), e.app.workspace.revealLeaf(r)); } }; -var Ke = class extends U.Plugin { +var Ue = class extends X.Plugin { constructor() { super(...arguments); this._lastExportMtime = 0; @@ -7171,73 +7289,73 @@ var Ke = class extends U.Plugin { async onload() { (await this.loadSettings(), this.saveSettings(), - lt(this.app), - this.registerView(ve, (t) => new we(t))); + ut(this.app), + this.registerView(we, (t) => new Fe(t))); try { - (0, U.addIcon)(Fe, it); + (0, X.addIcon)(De, pt); } catch (t) {} - (this.addRibbonIcon(Fe, "PaperForge Dashboard", () => we.open(this)), - se.find((t) => t.id === "paperforge-ocr-redo") && + (this.addRibbonIcon(De, "PaperForge Dashboard", () => Fe.open(this)), + oe.find((t) => t.id === "paperforge-ocr-redo") && this.addRibbonIcon("reset", "PaperForge: Redo OCR", () => { let t = this.app.vault.adapter.basePath; - new U.Notice("PaperForge: Redo OCR starting..."); - let { path: r, extraArgs: n } = N(t, this.settings, void 0, void 0); - (0, pe.execFile)( + new X.Notice("PaperForge: Redo OCR starting..."); + let { path: r, extraArgs: s } = z(t, this.settings, void 0, void 0); + (0, fe.execFile)( r, - [...n, "-m", "paperforge", "ocr", "redo"], + [...s, "-m", "paperforge", "ocr", "redo"], { cwd: t, timeout: 6e5 }, - (a, s, i) => { + (a, n, i) => { if (a) { - new U.Notice("PaperForge: Redo OCR failed"); + new X.Notice("PaperForge: Redo OCR failed"); return; } - new U.Notice("PaperForge: Redo OCR done"); + new X.Notice("PaperForge: Redo OCR done"); } ); }), - this.addSettingTab(new He(this.app, this)), + this.addSettingTab(new Ze(this.app, this)), this.addCommand({ id: "paperforge-status-panel", name: `PaperForge: ${o("guide_open")}`, - callback: () => we.open(this), + callback: () => Fe.open(this), })); - for (let t of se) + for (let t of oe) this.addCommand({ id: t.id, name: `PaperForge: ${t.title}`, callback: () => { if (t.disabled) { - new U.Notice( + new X.Notice( `[i] ${t.disabledMsg || "This action is not yet available."}`, 6e3 ); return; } let r = this.app.vault.adapter.basePath; - new U.Notice(`PaperForge: running ${t.cmd}...`); - let { path: n, extraArgs: a = [] } = N( + new X.Notice(`PaperForge: running ${t.cmd}...`); + let { path: s, extraArgs: a = [] } = z( r, this.settings, void 0, void 0 ), - s = Array.isArray(t.args) ? [...t.args] : []; - (0, pe.execFile)( - n, - [...a, "-m", "paperforge", t.cmd, ...s], + n = Array.isArray(t.args) ? [...t.args] : []; + (0, fe.execFile)( + s, + [...a, "-m", "paperforge", t.cmd, ...n], { cwd: r, timeout: 3e5 }, - (i, l, c) => { + (i, c, l) => { if (i) { - new U.Notice( - `[!!] ${t.cmd} failed: ${(c || i.message).slice(0, 120)}`, + new X.Notice( + `[!!] ${t.cmd} failed: ${(l || i.message).slice(0, 120)}`, 8e3 ); return; } - new U.Notice( + new X.Notice( `[OK] ${ t.okMsg || - l + c .trim() .split( ` @@ -7260,17 +7378,17 @@ var Ke = class extends U.Plugin { _firstLaunchSnapshotMigration() { let e = this.app.vault.adapter.basePath; if (!e) return; - let r = ne(e).memoryStatePath; - if (!W.existsSync(r)) { - let n = N(e, this.settings, void 0, void 0); + let r = le(e).memoryStatePath; + if (!Z.existsSync(r)) { + let s = z(e, this.settings, void 0, void 0); [ ["runtime-health", "--json"], ["memory", "status", "--json"], ["embed", "status", "--json"], - ].forEach((s) => { - let i = [...n.extraArgs, "-m", "paperforge", "--vault", e, ...s]; - (0, pe.execFile)( - n.path, + ].forEach((n) => { + let i = [...s.extraArgs, "-m", "paperforge", "--vault", e, ...n]; + (0, fe.execFile)( + s.path, i, { cwd: e, timeout: 6e4, windowsHide: !0 }, () => {} @@ -7280,48 +7398,48 @@ var Ke = class extends U.Plugin { } _autoUpdate() { let e = this.app.vault.adapter.basePath, - { path: t, extraArgs: r = [] } = N(e, this.settings, void 0, void 0), - n = this.manifest.version, - a = `paperforge==${n}`, - s = `git+https://github.com/LLLin000/PaperForge.git@${n}`, - i = (l, c) => { - (0, pe.spawn)(t, [...r, "-m", "pip", "install", "--upgrade", l], { + { path: t, extraArgs: r = [] } = z(e, this.settings, void 0, void 0), + s = this.manifest.version, + a = `paperforge==${s}`, + n = `git+https://github.com/LLLin000/PaperForge.git@${s}`, + i = (c, l) => { + (0, fe.spawn)(t, [...r, "-m", "pip", "install", "--upgrade", c], { cwd: e, timeout: 12e4, - env: be(), - }).on("close", (f) => c(f === 0)); + env: Se(), + }).on("close", (_) => l(_ === 0)); }; - (0, pe.execFile)( + (0, fe.execFile)( t, [...r, "-c", "import paperforge; print(paperforge.__version__)"], { cwd: e, timeout: 1e4 }, - (l, c) => { + (c, l) => { let d = (p) => { (console.log( - `[PaperForge] Auto-update: trying PyPI (paperforge==${n})` + `[PaperForge] Auto-update: trying PyPI (paperforge==${s})` ), - i(a, (m) => { - if (m) { + i(a, (g) => { + if (g) { (console.log("[PaperForge] Auto-update: installed via PyPI"), - new U.Notice(`[OK] PaperForge CLI ${p}`, 5e3)); + new X.Notice(`[OK] PaperForge CLI ${p}`, 5e3)); return; } (console.warn( "[PaperForge] Auto-update: PyPI failed, falling back to git..." ), - i(s, (y) => { + i(n, (y) => { y && (console.log("[PaperForge] Auto-update: installed via git"), - new U.Notice(`[OK] PaperForge CLI ${p} (via git)`, 5e3)); + new X.Notice(`[OK] PaperForge CLI ${p} (via git)`, 5e3)); })); })); }; - if (l) { + if (c) { d("installed"); return; } - let f = c.trim(); - f !== n && d(`${f} -> ${n}`); + let _ = l.trim(); + _ !== s && d(`${_} -> ${s}`); } ); } @@ -7333,16 +7451,16 @@ var Ke = class extends U.Plugin { } _checkExports(e) { if (this._autoSyncRunning) return; - let t = ne(e).exportsDir; - if (!W.existsSync(t)) return; + let t = le(e).exportsDir; + if (!Z.existsSync(t)) return; let r = 0; try { - W.readdirSync(t).forEach((n) => { - if (!n.endsWith(".json")) return; - let a = W.statSync(Se.join(t, n)); + Z.readdirSync(t).forEach((s) => { + if (!s.endsWith(".json")) return; + let a = Z.statSync(Te.join(t, s)); a.mtimeMs > r && (r = a.mtimeMs); }); - } catch (n) { + } catch (s) { return; } r > this._lastExportMtime && @@ -7351,51 +7469,51 @@ var Ke = class extends U.Plugin { _autoSync(e) { if (this._autoSyncRunning) return; this._autoSyncRunning = !0; - let t = N(e, this.settings, void 0, void 0); + let t = z(e, this.settings, void 0, void 0); if (!t.path) { this._autoSyncRunning = !1; return; } let r = `"${t.path}" -m paperforge --vault "${e}" sync`; - (0, pe.exec)(r, { timeout: 12e4, encoding: "utf-8" }, (n, a, s) => { + (0, fe.exec)(r, { timeout: 12e4, encoding: "utf-8" }, (s, a, n) => { ((this._autoSyncRunning = !1), (this._memoryStatusText = null), - n || (this._lastSyncTime = new Date().toLocaleTimeString())); + s || (this._lastSyncTime = new Date().toLocaleTimeString())); try { - let i = ne(e).exportsDir, - l = 0; - (W.readdirSync(i).forEach((c) => { - c.endsWith(".json") && - (l = Math.max(l, W.statSync(Se.join(i, c)).mtimeMs)); + let i = le(e).exportsDir, + c = 0; + (Z.readdirSync(i).forEach((l) => { + l.endsWith(".json") && + (c = Math.max(c, Z.statSync(Te.join(i, l)).mtimeMs)); }), - (this._lastExportMtime = l)); + (this._lastExportMtime = c)); } catch (i) {} }); } _checkOcr(e) { if (this._autoSyncRunning) return; - let t = ne(e).ocrDir; - if (W.existsSync(t)) + let t = le(e).ocrDir; + if (Z.existsSync(t)) try { - W.readdirSync(t, { withFileTypes: !0 }).forEach((r) => { + Z.readdirSync(t, { withFileTypes: !0 }).forEach((r) => { if (!r.isDirectory()) return; - let n = Se.join(t, r.name, "meta.json"); - if (!W.existsSync(n)) return; - let a = W.statSync(n), - s = this._lastOcrMtimes[r.name] || 0; + let s = Te.join(t, r.name, "meta.json"); + if (!Z.existsSync(s)) return; + let a = Z.statSync(s), + n = this._lastOcrMtimes[r.name] || 0; if ( - a.mtimeMs <= s || + a.mtimeMs <= n || ((this._lastOcrMtimes[r.name] = a.mtimeMs), this._autoSyncRunning) ) return; this._autoSyncRunning = !0; - let i = N(e, this.settings, void 0, void 0); + let i = z(e, this.settings, void 0, void 0); if (!i.path) { this._autoSyncRunning = !1; return; } - let l = `"${i.path}" -m paperforge --vault "${e}" sync`; - (0, pe.exec)(l, { timeout: 3e4, encoding: "utf-8" }, () => { + let c = `"${i.path}" -m paperforge --vault "${e}" sync`; + (0, fe.exec)(c, { timeout: 3e4, encoding: "utf-8" }, () => { ((this._autoSyncRunning = !1), (this._memoryStatusText = null)); }); }); @@ -7403,7 +7521,7 @@ var Ke = class extends U.Plugin { } readPaperforgeJson() { let e = this.app.vault.adapter.basePath, - t = Se.join(e, "paperforge.json"), + t = Te.join(e, "paperforge.json"), r = { system_dir: "System", resources_dir: "Resources", @@ -7411,22 +7529,22 @@ var Ke = class extends U.Plugin { base_dir: "Bases", }; try { - if (!W.existsSync(t)) return r; - let n = W.readFileSync(t, "utf-8"), - a = JSON.parse(n), - s = a.vault_config || {}; + if (!Z.existsSync(t)) return r; + let s = Z.readFileSync(t, "utf-8"), + a = JSON.parse(s), + n = a.vault_config || {}; return { - system_dir: s.system_dir || a.system_dir || r.system_dir, - resources_dir: s.resources_dir || a.resources_dir || r.resources_dir, + system_dir: n.system_dir || a.system_dir || r.system_dir, + resources_dir: n.resources_dir || a.resources_dir || r.resources_dir, literature_dir: - s.literature_dir || a.literature_dir || r.literature_dir, - base_dir: s.base_dir || a.base_dir || r.base_dir, + n.literature_dir || a.literature_dir || r.literature_dir, + base_dir: n.base_dir || a.base_dir || r.base_dir, }; - } catch (n) { + } catch (s) { return ( console.warn( "PaperForge: Failed to read paperforge.json, using defaults", - n + s ), r ); @@ -7434,47 +7552,47 @@ var Ke = class extends U.Plugin { } savePaperforgeJson(e) { let t = this.app.vault.adapter.basePath, - r = Se.join(t, "paperforge.json"), - n = {}; + r = Te.join(t, "paperforge.json"), + s = {}; try { - W.existsSync(r) && (n = JSON.parse(W.readFileSync(r, "utf-8"))); - } catch (s) { - console.warn("PaperForge: Failed to read paperforge.json for update", s); + Z.existsSync(r) && (s = JSON.parse(Z.readFileSync(r, "utf-8"))); + } catch (n) { + console.warn("PaperForge: Failed to read paperforge.json for update", n); } - (!n.vault_config || typeof n.vault_config != "object") && - (n.vault_config = {}); + (!s.vault_config || typeof s.vault_config != "object") && + (s.vault_config = {}); let a = ["system_dir", "resources_dir", "literature_dir", "base_dir"]; - for (let s of a) e[s] !== void 0 && (n.vault_config[s] = e[s]); - n.schema_version || (n.schema_version = "2"); - for (let s of a) delete n[s]; + for (let n of a) e[n] !== void 0 && (s.vault_config[n] = e[n]); + s.schema_version || (s.schema_version = "2"); + for (let n of a) delete s[n]; try { if ( - (W.writeFileSync(r, JSON.stringify(n, null, 2), "utf-8"), this.settings) + (Z.writeFileSync(r, JSON.stringify(s, null, 2), "utf-8"), this.settings) ) { - let s = this.readPaperforgeJson(); - ((this.settings.system_dir = s.system_dir), - (this.settings.resources_dir = s.resources_dir), - (this.settings.literature_dir = s.literature_dir), - (this.settings.base_dir = s.base_dir)); + let n = this.readPaperforgeJson(); + ((this.settings.system_dir = n.system_dir), + (this.settings.resources_dir = n.resources_dir), + (this.settings.literature_dir = n.literature_dir), + (this.settings.base_dir = n.base_dir)); } - } catch (s) { - (console.error("PaperForge: Failed to write paperforge.json", s), - new U.Notice( + } catch (n) { + (console.error("PaperForge: Failed to write paperforge.json", n), + new X.Notice( "PaperForge: Failed to save configuration to paperforge.json" )); } } onunload() { (this._pollTimer && clearInterval(this._pollTimer), - this.app.workspace.detachLeavesOfType(ve)); + this.app.workspace.detachLeavesOfType(we)); } async loadSettings() { - ((this.settings = Object.assign({}, Re, await this.loadData())), + ((this.settings = Object.assign({}, Be, await this.loadData())), this.settings.features && - Re.features && + Be.features && (this.settings.features = Object.assign( {}, - Re.features, + Be.features, this.settings.features || {} )), this.settings.frozen_skills || (this.settings.frozen_skills = {})); @@ -7487,7 +7605,7 @@ var Ke = class extends U.Plugin { this.settings.python_path && this.settings.python_path.trim()) ) { let t = this.settings.python_path.trim(); - W.existsSync(t) + Z.existsSync(t) ? (this.settings._python_path_stale = !1) : (console.warn( `PaperForge: Saved python_path "${t}" no longer exists - showing stale warning` @@ -7497,56 +7615,56 @@ var Ke = class extends U.Plugin { } async saveSettings() { let e = {}; - for (let t of Object.keys(Re)) + for (let t of Object.keys(Be)) t in this.settings && (e[t] = this.settings[t]); await this.saveData(e); } _checkReleaseNotes() { let e = this.manifest.version; if (this.settings.last_seen_version === e) return; - let a = (Je().versions || []).find((i) => i.version === e); - class s extends U.Modal { - constructor(l, c) { - (super(l), (this._entry = c)); + let a = (Xe().versions || []).find((i) => i.version === e); + class n extends X.Modal { + constructor(c, l) { + (super(c), (this._entry = l)); } onOpen() { - let { contentEl: l } = this; + let { contentEl: c } = this; if ( - (l.createEl("h2", { + (c.createEl("h2", { text: `PaperForge v${e} \u66F4\u65B0\u8BF4\u660E`, }), this._entry) ) { if ( - (l.createEl("p", { + (c.createEl("p", { text: this._entry.title, cls: "paperforge-modal-subtitle", }), this._entry.breaking_or_migration && this._entry.breaking_or_migration.length > 0) ) { - l.createEl("h4", { + c.createEl("h4", { text: "\u884C\u4E3A\u53D8\u66F4 / \u8FC1\u79FB\u6CE8\u610F", }); - for (let c of this._entry.breaking_or_migration) - l.createEl("p", { - text: `\u2022 ${c}`, + for (let l of this._entry.breaking_or_migration) + c.createEl("p", { + text: `\u2022 ${l}`, cls: "paperforge-modal-item", }); } if (this._entry.new_features && this._entry.new_features.length > 0) { - l.createEl("h4", { text: "\u65B0\u529F\u80FD" }); - for (let c of this._entry.new_features) - l.createEl("p", { - text: `\u2022 ${c}`, + c.createEl("h4", { text: "\u65B0\u529F\u80FD" }); + for (let l of this._entry.new_features) + c.createEl("p", { + text: `\u2022 ${l}`, cls: "paperforge-modal-item", }); } if (this._entry.fixes && this._entry.fixes.length > 0) { - l.createEl("h4", { text: "\u4FEE\u590D" }); - for (let c of this._entry.fixes) - l.createEl("p", { - text: `\u2022 ${c}`, + c.createEl("h4", { text: "\u4FEE\u590D" }); + for (let l of this._entry.fixes) + c.createEl("p", { + text: `\u2022 ${l}`, cls: "paperforge-modal-item", }); } @@ -7554,26 +7672,26 @@ var Ke = class extends U.Plugin { this._entry.recommended_actions && this._entry.recommended_actions.length > 0 ) { - let c = l.createEl("div", { + let l = c.createEl("div", { cls: "paperforge-release-recommended", }); - (c.createEl("h4", { text: "\u5EFA\u8BAE\u64CD\u4F5C", cls: "" }), - (c.style.marginBottom = "8px")); + (l.createEl("h4", { text: "\u5EFA\u8BAE\u64CD\u4F5C", cls: "" }), + (l.style.marginBottom = "8px")); for (let d of this._entry.recommended_actions) - c.createEl("p", { + l.createEl("p", { text: `\u2022 ${d}`, cls: "paperforge-release-item-bold", }); } } else - l.createEl("p", { + c.createEl("p", { text: "\u7248\u672C\u5DF2\u66F4\u65B0\u81F3 v" + e + "\uFF0C\u8BF7\u524D\u5F80\u8BBE\u7F6E \u2192 \u66F4\u65B0\u4E0E\u624B\u518C \u67E5\u770B\u5B8C\u6574\u66F4\u65B0\u8BB0\u5F55\u3002", }); - new U.Setting(l).addButton((c) => - c + new X.Setting(c).addButton((l) => + l .setButtonText("\u77E5\u9053\u4E86") .setCta() .onClick(() => { @@ -7582,11 +7700,11 @@ var Ke = class extends U.Plugin { ); } onClose() { - let { contentEl: l } = this; - l.empty(); + let { contentEl: c } = this; + c.empty(); } } - (new s(this.app, a).open(), + (new n(this.app, a).open(), (this.settings.last_seen_version = e), this.saveSettings()); } diff --git a/paperforge/plugin/src/i18n.ts b/paperforge/plugin/src/i18n.ts index 2266d447..0483f3cc 100644 --- a/paperforge/plugin/src/i18n.ts +++ b/paperforge/plugin/src/i18n.ts @@ -268,6 +268,13 @@ const LANG: Record> = { maintenance_refresh_spinning: "Updating…", maintenance_all_good: "✅ All good — no action needed", maintenance_n_pending: "{n} need attention", + maintenance_filter_all: "All", + maintenance_filter_recommended: "Recommended", + maintenance_batch_rebuild: "▶ Rebuild selected", + maintenance_batch_redo: "▶ Full OCR redo selected", + maintenance_stop: "Stop", + maintenance_batch_complete: "Batch operation complete — {n} papers processed.", + maintenance_progress_label: "{current}/{total} papers", version_panel_title: "Version History", version_panel_back: "Back", version_filter_placeholder: "Filter papers...", @@ -559,6 +566,7 @@ const LANG: Record> = { "这类论文通常表示版式复杂或信号偏弱,PaperForge 目前没有高置信度的维护建议。", ocr_maint_all_papers: "全部论文", ocr_maint_rebuild_btn: "重建结果", + ocr_maint_redo_btn: "重新 OCR", maintenance_group_retry: "需要重试", maintenance_group_rebuild: "可重建结果", maintenance_group_legacy: "可升级旧结果(可选)", @@ -568,6 +576,13 @@ const LANG: Record> = { maintenance_refresh_spinning: "正在更新…", maintenance_all_good: "✅ 全部正常", maintenance_n_pending: "{n} 篇需要处理", + maintenance_filter_all: "全部", + maintenance_filter_recommended: "建议处理", + maintenance_batch_rebuild: "▶ 重建已选", + maintenance_batch_redo: "▶ 全部重跑 OCR", + maintenance_stop: "停止", + maintenance_batch_complete: "批量操作完成 — 处理了 {n} 篇论文。", + maintenance_progress_label: "{current}/{total} 篇", version_panel_title: "版本历史", version_panel_back: "返回", version_filter_placeholder: "搜索论文...", diff --git a/paperforge/plugin/src/services/progress-parser.ts b/paperforge/plugin/src/services/progress-parser.ts new file mode 100644 index 00000000..7704111a --- /dev/null +++ b/paperforge/plugin/src/services/progress-parser.ts @@ -0,0 +1,109 @@ +/** + * Shared progress token parser — handles EMBED, OCR_REBUILD, and OCR_REDO + * tokens across arbitrary stdout chunks. + * + * Token formats: + * {PREFIX}_START:{total} + * {PREFIX}_PROGRESS:{current}:{total}:{key} + * {PREFIX}_DONE + * + * PREFIX in: EMBED, OCR_REBUILD, OCR_REDO + * + * This is the stable plugin boundary contract from the CLI backend. + */ + +export type ProgressEventType = "START" | "PROGRESS" | "DONE"; + +export interface ProgressEvent { + prefix: string; + event: ProgressEventType; + total?: number; + current?: number; + key?: string; +} + +const KNOWN_PREFIXES = ["EMBED", "OCR_REBUILD", "OCR_REDO"]; + +/** + * Parse an arbitrarily chunked stdout stream for progress tokens. + * + * @param chunk Raw text from the latest stdout data event. + * @param buffer Accumulated incomplete line from a previous call (empty string initially). + * @returns Parsed events and any leftover line fragment to pass to the next call. + */ +export function processProgressChunk( + chunk: string, + buffer: string, +): { events: ProgressEvent[]; buffer: string } { + const full = buffer + chunk; + const lines = full.split("\n"); + // The last element may be an incomplete line — hold for next chunk. + const incomplete = lines.pop() ?? ""; + + const events: ProgressEvent[] = []; + + for (const line of lines) { + for (const prefix of KNOWN_PREFIXES) { + const pLen = prefix.length; + + if (line.startsWith(prefix + "_START:")) { + const total = parseInt(line.slice(pLen + 7) /* "_START:".length */, 10) || 0; + events.push({ prefix, event: "START", total }); + break; + } + + if (line.startsWith(prefix + "_PROGRESS:")) { + const rest = line.slice(pLen + 10); /* "_PROGRESS:".length */ + const parts = rest.split(":"); + events.push({ + prefix, + event: "PROGRESS", + current: parseInt(parts[0], 10) || 0, + total: parseInt(parts[1], 10) || 0, + key: parts[2] ?? "", + }); + break; + } + + if (line === prefix + "_DONE" || line.startsWith(prefix + "_DONE:")) { + events.push({ prefix, event: "DONE" }); + break; + } + } + } + + return { events, buffer: incomplete }; +} + +/** + * Parse a single complete line for a progress token. + * Useful when text is already newline-aligned (e.g. execFile stdout). + */ +export function parseProgressLine(line: string): ProgressEvent | null { + for (const prefix of KNOWN_PREFIXES) { + const pLen = prefix.length; + + if (line.startsWith(prefix + "_START:")) { + const total = parseInt(line.slice(pLen + 7), 10) || 0; + return { prefix, event: "START", total }; + } + + if (line.startsWith(prefix + "_PROGRESS:")) { + const rest = line.slice(pLen + 10); + const parts = rest.split(":"); + return { + prefix, + event: "PROGRESS", + current: parseInt(parts[0], 10) || 0, + total: parseInt(parts[1], 10) || 0, + key: parts[2] ?? "", + }; + } + + if (line === prefix + "_DONE" || line.startsWith(prefix + "_DONE:")) { + return { prefix, event: "DONE" }; + } + } + + return null; +} diff --git a/paperforge/plugin/src/settings.ts b/paperforge/plugin/src/settings.ts index 5dc2c224..3310a622 100644 --- a/paperforge/plugin/src/settings.ts +++ b/paperforge/plugin/src/settings.ts @@ -40,6 +40,8 @@ import { writeMaintenanceCache, refreshMaintenanceData, } from "./services/ocr-maintenance-ui"; +import { processProgressChunk } from "./services/progress-parser"; + // ── Interface ── @@ -56,6 +58,11 @@ interface ISettingPlugin { _embedProcess?: unknown; _embedProgress?: { current: number; total: number; key: string }; _embedStderr?: string; + _embedBuffer?: string; + _ocrProcess?: unknown; + _ocrProgress?: { current: number; total: number; key: string }; + _ocrBuffer?: string; + _ocrWasStopped?: boolean; _embedPollInterval?: ReturnType | null; _embedPolling?: boolean; } @@ -1260,16 +1267,19 @@ export class PaperForgeSettingTab extends PluginSettingTab { : Buffer.isBuffer(data) ? data.toString("utf-8") : String(data); - const lines = text.split("\n"); - for (const line of lines) { - if (line.startsWith("EMBED_START:")) { - this.plugin._embedProgress!.total = - parseInt(line.split(":")[1]) || 0; - } else if (line.startsWith("EMBED_PROGRESS:")) { - const parts = line.split(":"); - this.plugin._embedProgress!.current = parseInt(parts[1]) || 0; - this.plugin._embedProgress!.key = parts[3] || ""; - } else if (line.startsWith("EMBED_DONE")) { + // Use shared parser — inline buffer reset on each build + const { events, buffer } = processProgressChunk( + text, + this.plugin._embedBuffer ?? "", + ); + this.plugin._embedBuffer = buffer; + for (const ev of events) { + if (ev.event === "START") { + this.plugin._embedProgress!.total = ev.total || 0; + } else if (ev.event === "PROGRESS") { + this.plugin._embedProgress!.current = ev.current || 0; + this.plugin._embedProgress!.key = ev.key || ""; + } else if (ev.event === "DONE") { this.plugin._embedProcess = null; this.plugin._embedProgress!.current = this.plugin._embedProgress!.total; @@ -1956,23 +1966,14 @@ export class PaperForgeSettingTab extends PluginSettingTab { text: t("tab_maintenance") || "维护", }); - const vaultPath = (this.app.vault.adapter as any).basePath as string; + // vault path — DataAdapter.basePath is undocumented but stable + const adapter = this.app.vault + .adapter as unknown as { basePath?: string }; + const vaultPath = adapter.basePath ?? ""; const statusEl = containerEl.createEl("div"); - // ── Helpers ── - const buildActionArgs = (keys: string[], action: string) => { - if (action === "retry_ocr" || action === "upgrade_legacy") - return { - cmd: ["-m", "paperforge", "ocr", "redo", ...keys], - timeout: 300000, - }; - if (action === "rebuild_result") - return { - cmd: ["-m", "paperforge", "ocr", "rebuild", ...keys], - timeout: 120000, - }; - return null; - }; + // Filter state + const filterState = { active: "all" as "all" | "recommended" }; // ── Phase 1: Read cache ── let cache: MaintenanceCache | null = null; @@ -1983,7 +1984,8 @@ export class PaperForgeSettingTab extends PluginSettingTab { // ── Phase 2: Try manifest refresh ── const py = resolvePythonExecutable( vaultPath, - this.plugin.settings as any, + // PaperForgeSettings — no cast needed, ISettingPlugin has it + this.plugin.settings, fs, execFileSync ); @@ -1995,223 +1997,475 @@ export class PaperForgeSettingTab extends PluginSettingTab { return; } - // Shared table rendering function - const renderTable = (papers: MaintenanceDisplayRow[]) => { - statusEl.empty(); + const isBatchRunning = () => !!this.plugin._ocrProcess; - const visible = papers.filter((p) => p.visible_in_maintenance); + const renderTable = (papers: MaintenanceDisplayRow[]) => { + statusEl.empty(); + const allVisible = papers.filter((p) => p.visible_in_maintenance); + + // Filter tabs — render before empty check so user can always switch back + const filterRow = statusEl.createEl("div", { + cls: "pf-maint-filters", + }); + + const allTab = filterRow.createEl("button", { + cls: + "pf-maint-filter" + + (filterState.active === "all" ? " active" : ""), + text: t("maintenance_filter_all") || "All", + }); + allTab.addEventListener("click", () => { + filterState.active = "all"; + renderTable(papers); + }); + + const recTab = filterRow.createEl("button", { + cls: + "pf-maint-filter" + + (filterState.active === "recommended" ? " active" : ""), + text: t("maintenance_filter_recommended") || "Recommended", + }); + recTab.addEventListener("click", () => { + filterState.active = "recommended"; + renderTable(papers); + }); + + // Recommended = items with rebuild display_group or rebuild_result display_action + const visible = + filterState.active === "recommended" + ? allVisible.filter( + (p) => + p.display_group === "rebuild" || + p.display_action === "rebuild_result", + ) + : allVisible; + + // If the active filter yields nothing, show a message and skip the table/progress if (visible.length === 0) { statusEl.createEl("p", { - text: t("maintenance_all_good") || "✅ 全部正常", + text: "当前筛选条件下无数据", + cls: "setting-item-description", }); - return; - } + } else { + const pyPath = py.path; + const pyExtra = (py.extraArgs || []) as string[]; - const pyPath = py.path; - const pyExtra = (py.extraArgs || []) as string[]; - // Group by display_group - const groups: { - key: string; - title: string; - items: MaintenanceDisplayRow[]; - }[] = [ - { - key: "retry", - title: t("maintenance_group_retry") || "需要重试", - items: [], - }, - { - key: "rebuild", - title: t("maintenance_group_rebuild") || "可重建结果", - items: [], - }, - { - key: "legacy_optional", - title: t("maintenance_group_legacy") || "可升级旧结果(可选)", - items: [], - }, - ]; - for (const p of visible) { - const g = groups.find((g) => g.key === p.display_group); - if (g) g.items.push(p); - } + // ── Progress bar (if batch running) — mutable DOM refs, no full re-render ── + const progressContainer = statusEl.createEl("div", { + cls: "pf-maint-progress", + }); + progressContainer.style.display = "none"; - for (const group of groups) { - if (group.items.length === 0) continue; + const track = progressContainer.createEl("div", { + cls: "paperforge-progress-track", + }); + track.style.cssText = "flex:1;"; + const doneSeg = track.createEl("div", { + cls: "paperforge-progress-seg done", + }); + const pendingSeg = track.createEl("div", { + cls: "paperforge-progress-seg pending", + }); + const label = progressContainer.createEl("span", { + cls: "pf-maint-progress-text", + }); + const keyLabel = progressContainer.createEl("span", { + cls: "pf-maint-progress-key", + }); - const isLegacy = group.key === "legacy_optional"; - const section = isLegacy - ? statusEl.createEl("details") - : statusEl.createEl("div"); + // Stop button — cooperative: stdin control line, fallback to SIGINT + const stopBtn = progressContainer.createEl("button", { + text: t("maintenance_stop") || "Stop", + }); + stopBtn.className = "mod-warning"; + stopBtn.addEventListener("click", () => { + const child = this.plugin._ocrProcess as unknown as { + stdin?: { write: (_: string) => boolean }; + kill?: (_: string) => void; + }; + if (child) { + // Prefer stdin control line (cooperative — backend finishes current paper) + if (child.stdin && typeof child.stdin.write === "function") { + child.stdin.write("PAPERFORGE_STOP\n"); + } else if (typeof child.kill === "function") { + child.kill("SIGINT"); + } + } + // Flag, don't null — onClose handles cleanup + this.plugin._ocrWasStopped = true; + stopBtn.disabled = true; + stopBtn.textContent = (t("maintenance_stop") || "Stop") + "…"; + }); - if (isLegacy) { - const summary = (section as HTMLDetailsElement).createEl("summary"); - summary.createEl("strong", { - text: group.title + " (" + group.items.length + ")", - }); + // In-place DOM update — called from runBatch start + onData events + const updateProgress = () => { + const prog = this.plugin._ocrProgress; + if (!prog || prog.total === 0 || !this.plugin._ocrProcess) { + progressContainer.style.display = "none"; + return; + } + progressContainer.style.display = "flex"; + + const pct = + prog.total > 0 + ? ((prog.current / prog.total) * 100).toFixed(1) + : "0"; + + doneSeg.style.width = `${pct}%`; + doneSeg.style.minWidth = prog.current > 0 ? "2px" : "0"; + + if (prog.current < prog.total) { + pendingSeg.style.display = ""; + pendingSeg.style.flex = "1"; } else { - section.createEl("h3", { - text: group.title + " (" + group.items.length + ")", - }); + pendingSeg.style.display = "none"; } - // Selection state per row - const selState = new Map(); - for (const p of group.items) selState.set(p.key, false); + label.textContent = ( + t("maintenance_progress_label") || + "{current}/{total} papers" + ) + .replace("{current}", String(prog.current)) + .replace("{total}", String(prog.total)); - // Toolbar - const toolbar = section.createEl("div", { cls: "pf-maint-toolbar" }); - const selAllBtn = toolbar.createEl("button", { text: "全选" }); - const deselAllBtn = toolbar.createEl("button", { text: "取消全选" }); - const execBtn = toolbar.createEl("button", { - text: "▶ 执行已选", - cls: "mod-cta", + keyLabel.textContent = prog.key ? ` (${prog.key})` : ""; + }; + + updateProgress(); + + // Selection state + const selState = new Map(); + for (const p of visible) selState.set(p.key, false); + + // ── Table ── + const wrapper = statusEl.createEl("div", { + cls: "pf-maint-table-wrap", + }); + const table = wrapper.createEl("table", { cls: "pf-maint-table" }); + const thead = table.createEl("thead"); + const tbody = table.createEl("tbody"); + const headerRow = thead.insertRow(); + ["", "Paper", "Status Reason", "Actions"].forEach((h) => { + const th = document.createElement("th"); + th.textContent = h; + headerRow.appendChild(th); + }); + + const isBusy = isBatchRunning(); + + for (const p of visible) { + const tr = tbody.insertRow(); + + // Checkbox + const selTd = tr.insertCell(); + selTd.style.cssText = + "padding:3px 4px;text-align:center;width:24px;"; + const cb = document.createElement("input"); + cb.type = "checkbox"; + cb.className = "pf-maint-sel"; + cb.checked = selState.get(p.key) || false; + cb.addEventListener("change", () => { + selState.set(p.key, cb.checked); + updateBatchLabel(); }); - const execLabel = toolbar.createEl("span", { - cls: "pf-maint-exec-label", + selTd.appendChild(cb); + + // Paper info (title + key) + const infoTd = tr.insertCell(); + infoTd.style.cssText = "padding:3px 4px;"; + const infoDiv = infoTd.createEl("div", { + cls: "pf-maint-paper-info", + }); + infoDiv.createEl("div", { + cls: "pf-maint-paper-title", + text: p.title || p.key, + }); + infoDiv.createEl("div", { + cls: "pf-maint-paper-key", + text: p.key, }); - const updateExecLabel = () => { - const n = group.items.filter((p) => selState.get(p.key)).length; - execLabel.setText("已选 " + n + " 篇"); - }; - updateExecLabel(); + // Status reason + const reasonTd = tr.insertCell(); + reasonTd.style.cssText = "padding:3px 4px;"; + reasonTd.createEl("div", { + cls: "pf-maint-reason", + text: p.display_reason || "", + }); - selAllBtn.addEventListener("click", () => { - for (const p of group.items) selState.set(p.key, true); - updateExecLabel(); - // Update checkboxes - const cbs = section.querySelectorAll( - "input[type=checkbox].pf-maint-sel" - ); - cbs.forEach((cb) => { - (cb as HTMLInputElement).checked = true; + // Action buttons — [Rebuild] [Redo] + const actionTd = tr.insertCell(); + actionTd.style.cssText = + "padding:3px 4px;white-space:nowrap;"; + const actionDiv = actionTd.createEl("div", { + cls: "pf-maint-actions", + }); + + if (p.can_rebuild) { + const rebuildBtn = actionDiv.createEl("button", { + cls: "pf-maint-action-btn rebuild", + text: t("maintenance_btn_rebuild") || "Rebuild", }); - }); - deselAllBtn.addEventListener("click", () => { - for (const p of group.items) selState.set(p.key, false); - updateExecLabel(); - const cbs = section.querySelectorAll( - "input[type=checkbox].pf-maint-sel" - ); - cbs.forEach((cb) => { - (cb as HTMLInputElement).checked = false; - }); - }); - - execBtn.addEventListener("click", () => { - const selected = group.items.filter((p) => selState.get(p.key)); - if (selected.length === 0) { - new Notice("请先选择要处理的论文。"); - return; - } - for (const p of selected) { - const args = buildActionArgs([p.key], p.display_action); - if (!args) continue; + if (isBusy) rebuildBtn.disabled = true; + rebuildBtn.addEventListener("click", () => { + const args = [ + ...pyExtra, + "-m", + "paperforge", + "ocr", + "rebuild", + p.key, + ]; execFile( pyPath, - [...pyExtra, ...args.cmd], - { cwd: vaultPath, timeout: args.timeout, windowsHide: true }, + args, + { + cwd: vaultPath, + timeout: 120000, + windowsHide: true, + }, () => { - new Notice(p.display_label + " — " + p.key); + new Notice( + (t("maintenance_btn_rebuild") || "Rebuild") + + " — " + + p.key + ); } ); - } - }); - - // Table - const wrapper = section.createEl("div", { cls: "pf-maint-table-wrap" }); - const table = wrapper.createEl("table", { cls: "pf-maint-table" }); - const thead = table.createEl("thead"); - const tbody = table.createEl("tbody"); - const headerRow = thead.insertRow(); - ["", "Key", "Title", "建议操作", "原因", "操作"].forEach((h) => { - const th = document.createElement("th"); - th.textContent = h; - headerRow.appendChild(th); - }); - - const btnText = (action: string) => { - if (action === "retry_ocr") - return t("maintenance_btn_retry") || "重试"; - if (action === "rebuild_result") - return t("maintenance_btn_rebuild") || "重建"; - if (action === "upgrade_legacy") - return t("maintenance_btn_upgrade") || "升级"; - return ""; - }; - - for (const p of group.items) { - const tr = tbody.insertRow(); - - // Checkbox - const selTd = tr.insertCell(); - selTd.style.cssText = "padding:3px 4px;text-align:center;"; - const cb = document.createElement("input"); - cb.type = "checkbox"; - cb.className = "pf-maint-sel"; - cb.checked = selState.get(p.key) || false; - cb.addEventListener("change", () => { - selState.set(p.key, cb.checked); - updateExecLabel(); }); - selTd.appendChild(cb); + } - // Key - const keyTd = tr.insertCell(); - keyTd.style.cssText = - "padding:3px 4px;white-space:nowrap;font-size:11px;max-width:90px;overflow:hidden;text-overflow:ellipsis;"; - keyTd.textContent = p.key; - - // Title - const titleTd = tr.insertCell(); - titleTd.style.cssText = - "padding:3px 4px;white-space:nowrap;max-width:220px;overflow:hidden;text-overflow:ellipsis;"; - titleTd.textContent = p.title || p.key; - - // Display label (建议操作) - const labelTd = tr.insertCell(); - labelTd.style.cssText = "padding:3px 4px;white-space:nowrap;"; - labelTd.textContent = p.display_label; - - // Reason - const reasonTd = tr.insertCell(); - reasonTd.style.cssText = - "padding:3px 4px;white-space:nowrap;max-width:160px;overflow:hidden;text-overflow:ellipsis;font-size:11px;color:var(--text-muted);"; - reasonTd.textContent = p.display_reason || ""; - - // Action button - const actionTd = tr.insertCell(); - actionTd.style.cssText = - "padding:3px 4px;text-align:center;white-space:nowrap;"; - const actionBtn = document.createElement("button"); - actionBtn.textContent = btnText(p.display_action); - if (actionBtn.textContent) { - actionBtn.addEventListener("click", () => { - const args = buildActionArgs([p.key], p.display_action); - if (!args) return; - execFile( - pyPath, - [...pyExtra, ...args.cmd], - { cwd: vaultPath, timeout: args.timeout, windowsHide: true }, - () => { - new Notice(p.display_label + " — " + p.key); - } - ); - }); - actionTd.appendChild(actionBtn); - } + if (p.can_redo) { + const redoBtn = actionDiv.createEl("button", { + cls: "pf-maint-action-btn redo", + text: t("ocr_maint_redo_btn") || "Redo", + }); + if (isBusy) redoBtn.disabled = true; + redoBtn.addEventListener("click", () => { + const args = [ + ...pyExtra, + "-m", + "paperforge", + "ocr", + "redo", + p.key, + ]; + execFile( + pyPath, + args, + { + cwd: vaultPath, + timeout: 300000, + windowsHide: true, + }, + () => { + new Notice( + (t("ocr_maint_redo_btn") || "Redo OCR") + + " — " + + p.key + ); + } + ); + }); } } + + // ── Batch action bar ── + const batchBar = statusEl.createEl("div", { + cls: "pf-maint-batch-bar", + }); + const batchLabel = batchBar.createEl("span", { + cls: "pf-maint-batch-label", + text: "0 selected", + }); + + const updateBatchLabel = () => { + const n = visible.filter((p) => selState.get(p.key)).length; + batchLabel.textContent = n + " selected"; + }; + + const rebuildBatchBtn = batchBar.createEl("button", { + cls: "mod-cta", + text: + t("maintenance_batch_rebuild") || "▶ Rebuild selected", + }); + rebuildBatchBtn.disabled = isBusy; + + const redoBatchBtn = batchBar.createEl("button", { + cls: "mod-cta", + text: + t("maintenance_batch_redo") || "▶ Full OCR redo selected", + }); + redoBatchBtn.disabled = isBusy; + + const runBatch = (action: "rebuild" | "redo") => { + const selected = visible.filter((p) => + selState.get(p.key) + ); + if (selected.length === 0) { + new Notice("Please select papers first."); + return; + } + const keys = selected.map((p) => p.key); + this.plugin._ocrProgress = { + current: 0, + total: keys.length, + key: "", + }; + this.plugin._ocrBuffer = ""; + this.plugin._ocrWasStopped = false; + + const prefix = + action === "rebuild" ? "OCR_REBUILD" : "OCR_REDO"; + + // Disable batch + per-row action buttons during the batch + rebuildBatchBtn.disabled = true; + redoBatchBtn.disabled = true; + Array.from( + wrapper.querySelectorAll(".pf-maint-action-btn"), + ).forEach((btn) => { + btn.disabled = true; + }); + // Also disable checkboxes to prevent selection changes during batch + Array.from( + wrapper.querySelectorAll(".pf-maint-sel"), + ).forEach((cb) => { + cb.disabled = true; + }); + // Disable filter tabs to prevent filter switch mid-batch + allTab.disabled = true; + recTab.disabled = true; + stopBtn.disabled = false; + stopBtn.textContent = t("maintenance_stop") || "Stop"; + + const child = this._callPython( + ["ocr", action, ...keys], + { + stream: true, + onData: (data: unknown) => { + const text = + typeof data === "string" + ? data + : Buffer.isBuffer(data) + ? data.toString("utf-8") + : String(data); + // Shared parser with chunk buffer + const { events, buffer } = processProgressChunk( + text, + this.plugin._ocrBuffer ?? "", + ); + this.plugin._ocrBuffer = buffer; + for (const ev of events) { + if (ev.event === "START") { + if (this.plugin._ocrProgress) { + this.plugin._ocrProgress.total = + ev.total || keys.length; + } + } else if (ev.event === "PROGRESS") { + this.plugin._ocrProgress = { + current: ev.current || 0, + total: ev.total || keys.length, + key: ev.key || "", + }; + } + // DONE handled in onClose + } + // In-place DOM update — no full re-render + updateProgress(); + }, + onError: (err: Error) => { + this.plugin._ocrProcess = null; + new Notice( + "Batch error: " + (err.message || err), + ); + renderTable(papers); + }, + onClose: (code: number | null) => { + // code 130 = SIGINT caught cooperatively by the backend + if (this.plugin._ocrWasStopped || code === 130) { + this.plugin._ocrWasStopped = false; + // Leave progress as-is; no finalize + this.plugin._ocrProcess = null; + updateProgress(); + new Notice("OCR batch stopped by user."); + } else if (code === 0) { + // Finalize progress to show completion even if no tokens came through + if (this.plugin._ocrProgress) { + this.plugin._ocrProgress.current = + this.plugin._ocrProgress.total; + } + this.plugin._ocrProcess = null; + updateProgress(); + new Notice( + ( + t("maintenance_batch_complete") || + "Batch operation complete — {n} papers processed." + ).replace("{n}", String(keys.length)), + ); + } else { + this.plugin._ocrProcess = null; + updateProgress(); + new Notice( + "Batch operation finished with exit code " + + code + + ".", + 8000, + ); + } + // Full refresh + refreshMaintenanceData( + vaultPath, + pyPath, + pyExtra, + cache, + ) + .then((result) => { + if (result.changed || !cache) { + cache = { + manifest: {}, + papers: Object.fromEntries( + result.data.map( + (p: MaintenanceDisplayRow) => [p.key, p], + ), + ), + cached_at: new Date().toISOString(), + }; + writeMaintenanceCache(vaultPath, cache); + } + renderTable(result.data); + }) + .catch(() => { + renderTable(allVisible); + }); + }, + }, + ); + this.plugin._ocrProcess = child; + updateProgress(); + }; + + rebuildBatchBtn.addEventListener("click", () => + runBatch("rebuild") + ); + redoBatchBtn.addEventListener("click", () => + runBatch("redo") + ); + + updateBatchLabel(); + } // end else (visible non-empty) }; // ── Phase 1: Show cache immediately ── if (cache) { - const papers = Object.values(cache.papers) as MaintenanceDisplayRow[]; + const papers = Object.values( + cache.papers + ) as MaintenanceDisplayRow[]; renderTable(papers); } else { - statusEl.createEl("p", { text: "正在加载 OCR 维护数据…" }); + statusEl.createEl("p", { + text: "正在加载 OCR 维护数据…", + }); } // ── Phase 2: Background refresh ── @@ -2226,14 +2480,22 @@ export class PaperForgeSettingTab extends PluginSettingTab { renderTable(result.data); writeMaintenanceCache(vaultPath, { manifest: {}, - papers: Object.fromEntries(result.data.map((p) => [p.key, p])), + papers: Object.fromEntries( + result.data.map( + (p: MaintenanceDisplayRow) => [p.key, p] + ) + ), cached_at: new Date().toISOString(), }); } else if (!cache) { renderTable(result.data); writeMaintenanceCache(vaultPath, { manifest: {}, - papers: Object.fromEntries(result.data.map((p) => [p.key, p])), + papers: Object.fromEntries( + result.data.map( + (p: MaintenanceDisplayRow) => [p.key, p] + ) + ), cached_at: new Date().toISOString(), }); } @@ -2242,55 +2504,12 @@ export class PaperForgeSettingTab extends PluginSettingTab { if (!cache) { statusEl.empty(); statusEl.createEl("p", { - text: "无法加载 OCR 数据。请确保已安装 paperforge 并运行过 OCR。", + text: + "无法加载 OCR 数据。请确保已安装 paperforge 并运行过 OCR。", cls: "setting-item-description", }); } }); - - // ── Global operations ── - containerEl.createEl("hr"); - containerEl.createEl("h3", { text: "全局操作" }); - const globalActions = containerEl.createEl("div", { - cls: "pf-maint-global", - }); - - const rebuildIndexBtn = globalActions.createEl("button", { - text: "重建搜索索引", - }); - rebuildIndexBtn.addEventListener("click", () => { - new Notice("正在重建搜索索引…"); - execFile( - py.path, - [ - ...(py.extraArgs || []), - "-m", - "paperforge", - "embed", - "build", - "--force", - ], - { cwd: vaultPath, timeout: 300000, windowsHide: true }, - () => { - new Notice("搜索索引重建完成。"); - } - ); - }); - - const rebuildMemBtn = globalActions.createEl("button", { - text: "重建记忆库", - }); - rebuildMemBtn.addEventListener("click", () => { - new Notice("正在重建记忆库…"); - execFile( - py.path, - [...(py.extraArgs || []), "-m", "paperforge", "repair", "--fix"], - { cwd: vaultPath, timeout: 120000, windowsHide: true }, - () => { - new Notice("记忆库重建完成。"); - } - ); - }); } _renderReleaseNotesTab(containerEl: HTMLElement) { diff --git a/paperforge/plugin/styles.css b/paperforge/plugin/styles.css index ec320952..422f387c 100644 --- a/paperforge/plugin/styles.css +++ b/paperforge/plugin/styles.css @@ -2684,6 +2684,166 @@ border-color: var(--background-modifier-border-hover); } +/* ── Maintenance filter tabs ── */ +.pf-maint-filters { + display: flex; + gap: 4px; + margin-bottom: 12px; + padding: 2px; + background: var(--background-secondary); + border-radius: 6px; + width: fit-content; +} +.pf-maint-filter { + padding: 4px 14px; + font-size: 12px; + font-weight: 500; + color: var(--text-muted); + background: transparent; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background 0.15s, color 0.15s; +} +.pf-maint-filter:hover { + color: var(--text-normal); + background: var(--background-modifier-hover); +} +.pf-maint-filter.active { + color: var(--text-normal); + background: var(--background-primary); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); +} + +/* ── OCR batch progress bar ── */ +.pf-maint-progress { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + margin-bottom: 12px; + background: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 8px; +} +.pf-maint-progress .paperforge-progress-track { + flex: 1; +} +.pf-maint-progress-text { + font-size: 11px; + color: var(--text-muted); + white-space: nowrap; +} +.pf-maint-progress-key { + font-size: 10px; + color: var(--text-faint); + font-family: var(--font-monospace); +} + +/* ── Batch action bar ── */ +.pf-maint-batch-bar { + display: flex; + align-items: center; + gap: 8px; + margin-top: 10px; + padding: 8px 0; +} +.pf-maint-batch-bar button { + padding: 5px 14px; + font-size: 12px; + font-weight: 500; + border-radius: 6px; + cursor: pointer; + transition: opacity 0.15s; +} +.pf-maint-batch-bar button.mod-cta { + background: var(--interactive-accent); + color: var(--text-on-accent); + border: none; +} +.pf-maint-batch-bar button.mod-cta:hover:not(:disabled) { + opacity: 0.85; +} +.pf-maint-batch-bar button:disabled { + opacity: 0.4; + cursor: default; +} +.pf-maint-batch-bar .mod-warning { + background: var(--background-modifier-error); + color: var(--text-on-accent); + border: none; +} +.pf-maint-batch-label { + font-size: 11px; + color: var(--text-muted); + margin-left: auto; +} + +/* ── Per-row action buttons ── */ +.pf-maint-actions { + display: flex; + gap: 4px; + white-space: nowrap; +} +.pf-maint-action-btn { + padding: 2px 8px; + font-size: 11px; + font-weight: 500; + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + background: var(--background-secondary); + color: var(--text-normal); + cursor: pointer; + transition: background 0.15s; +} +.pf-maint-action-btn:hover:not(:disabled) { + background: var(--background-modifier-hover); +} +.pf-maint-action-btn:disabled { + opacity: 0.4; + cursor: default; +} +.pf-maint-action-btn.rebuild { + color: var(--interactive-accent); +} +.pf-maint-action-btn.redo { + color: var(--text-warning); +} + +/* ── Paper info cell ── */ +.pf-maint-paper-info { + display: flex; + flex-direction: column; + gap: 1px; +} +.pf-maint-paper-title { + font-size: 12px; + font-weight: 500; + color: var(--text-normal); + line-height: 1.3; + max-width: 240px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.pf-maint-paper-key { + font-size: 10px; + color: var(--text-faint); + font-family: var(--font-monospace); + line-height: 1.3; +} + +/* ── Status reason cell ── */ +.pf-maint-reason { + font-size: 11px; + color: var(--text-muted); + line-height: 1.35; + max-width: 180px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + /* ========================================================================== SECTION 41 — Global Settings Tab Polish (Vercel-inspired) ========================================================================== */ diff --git a/paperforge/plugin/tests/progress-parser.test.ts b/paperforge/plugin/tests/progress-parser.test.ts new file mode 100644 index 00000000..61c0ede0 --- /dev/null +++ b/paperforge/plugin/tests/progress-parser.test.ts @@ -0,0 +1,289 @@ +/** + * Vitest tests for progress-parser.ts — chunk boundary safety, + * prefix matching for EMBED / OCR_REBUILD / OCR_REDO. + */ +import { describe, expect, it } from "vitest"; +import { + processProgressChunk, + parseProgressLine, +} from "../src/services/progress-parser"; + +// ── processProgressChunk ── + +describe("processProgressChunk", () => { + it("parses OCR_REBUILD_START:total", () => { + const { events, buffer } = processProgressChunk("OCR_REBUILD_START:5\n", ""); + expect(events).toHaveLength(1); + expect(events[0]).toEqual({ + prefix: "OCR_REBUILD", + event: "START", + total: 5, + }); + expect(buffer).toBe(""); + }); + + it("parses OCR_REBUILD_PROGRESS:current:total:key", () => { + const { events, buffer } = processProgressChunk( + "OCR_REBUILD_PROGRESS:1:5:some-key\n", + "", + ); + expect(events).toHaveLength(1); + expect(events[0]).toEqual({ + prefix: "OCR_REBUILD", + event: "PROGRESS", + current: 1, + total: 5, + key: "some-key", + }); + expect(buffer).toBe(""); + }); + + it("parses OCR_REBUILD_DONE exact match", () => { + const { events } = processProgressChunk("OCR_REBUILD_DONE\n", ""); + expect(events).toHaveLength(1); + expect(events[0]).toEqual({ + prefix: "OCR_REBUILD", + event: "DONE", + }); + }); + + it("parses multiple OCR_REDO tokens from one chunk", () => { + const chunk = + "OCR_REDO_START:3\nOCR_REDO_PROGRESS:1:3:key-1\nOCR_REDO_DONE\n"; + const { events, buffer } = processProgressChunk(chunk, ""); + expect(events).toHaveLength(3); + expect(events[0]).toEqual({ + prefix: "OCR_REDO", + event: "START", + total: 3, + }); + expect(events[1]).toEqual({ + prefix: "OCR_REDO", + event: "PROGRESS", + current: 1, + total: 3, + key: "key-1", + }); + expect(events[2]).toEqual({ prefix: "OCR_REDO", event: "DONE" }); + expect(buffer).toBe(""); + }); + + it("parses EMBED tokens for backward compatibility", () => { + const chunk = + "EMBED_START:10\nEMBED_PROGRESS:2:10:paper-abc\nEMBED_DONE\n"; + const { events, buffer } = processProgressChunk(chunk, ""); + expect(events).toHaveLength(3); + expect(events[0]).toEqual({ prefix: "EMBED", event: "START", total: 10 }); + expect(events[1]).toEqual({ + prefix: "EMBED", + event: "PROGRESS", + current: 2, + total: 10, + key: "paper-abc", + }); + expect(events[2]).toEqual({ prefix: "EMBED", event: "DONE" }); + expect(buffer).toBe(""); + }); + + it("buffers an incomplete last line", () => { + const { events, buffer } = processProgressChunk( + "OCR_REBUILD_PROGRESS:1:5:", + "", + ); + expect(events).toHaveLength(0); + expect(buffer).toBe("OCR_REBUILD_PROGRESS:1:5:"); + }); + + it("resumes from buffer on the next chunk", () => { + const first = processProgressChunk("OCR_REBUILD_PROGRESS:1:", ""); + expect(first.events).toHaveLength(0); + expect(first.buffer).toBe("OCR_REBUILD_PROGRESS:1:"); + + const second = processProgressChunk("5:paper-key\n", first.buffer); + expect(second.events).toHaveLength(1); + expect(second.events[0]).toEqual({ + prefix: "OCR_REBUILD", + event: "PROGRESS", + current: 1, + total: 5, + key: "paper-key", + }); + expect(second.buffer).toBe(""); + }); + + it("passes through non-token lines silently and keeps buffer", () => { + const { events, buffer } = processProgressChunk( + "normal stdout line\nanother\n", + "", + ); + expect(events).toHaveLength(0); + expect(buffer).toBe(""); + }); + + it("handles interleaved normal output and tokens", () => { + const chunk = + "info: starting\nOCR_REBUILD_START:2\nsome log\nOCR_REBUILD_PROGRESS:1:2:k1\nmore output\nOCR_REBUILD_DONE\ndone\n"; + const { events, buffer } = processProgressChunk(chunk, ""); + expect(events).toHaveLength(3); + expect(events[0]).toEqual({ prefix: "OCR_REBUILD", event: "START", total: 2 }); + expect(events[1]).toEqual({ + prefix: "OCR_REBUILD", + event: "PROGRESS", + current: 1, + total: 2, + key: "k1", + }); + expect(events[2]).toEqual({ prefix: "OCR_REBUILD", event: "DONE" }); + expect(buffer).toBe(""); + }); + + it("handles empty chunk gracefully", () => { + const { events, buffer } = processProgressChunk("", "existing-buffer"); + expect(events).toHaveLength(0); + expect(buffer).toBe("existing-buffer"); + }); + + it("uses empty buffer on first call when omitted", () => { + // With empty string buffer + const { events, buffer } = processProgressChunk("EMBED_DONE\n", ""); + expect(events).toHaveLength(1); + expect(buffer).toBe(""); + }); + + it("parses EMBED_START split at prefix boundary", () => { + // First chunk has partial prefix + const first = processProgressChunk("EM", ""); + expect(first.events).toHaveLength(0); + expect(first.buffer).toBe("EM"); + + // Second chunk completes the token + const second = processProgressChunk("BED_START:3\n", first.buffer); + expect(second.events).toHaveLength(1); + expect(second.events[0]).toEqual({ + prefix: "EMBED", + event: "START", + total: 3, + }); + expect(second.buffer).toBe(""); + }); + + it("parses OCR_REBUILD_START split across chunks", () => { + const first = processProgressChunk("OCR_REB", ""); + expect(first.events).toHaveLength(0); + expect(first.buffer).toBe("OCR_REB"); + + const second = processProgressChunk("UILD_START:5\n", first.buffer); + expect(second.events).toHaveLength(1); + expect(second.events[0]).toEqual({ + prefix: "OCR_REBUILD", + event: "START", + total: 5, + }); + expect(second.buffer).toBe(""); + }); + + it("parses EMBED_PROGRESS split between prefix and colon", () => { + const first = processProgressChunk("EMBED_PROGRESS", ""); + expect(first.events).toHaveLength(0); + expect(first.buffer).toBe("EMBED_PROGRESS"); + + const second = processProgressChunk(":2:5:my-key\n", first.buffer); + expect(second.events).toHaveLength(1); + expect(second.events[0]).toEqual({ + prefix: "EMBED", + event: "PROGRESS", + current: 2, + total: 5, + key: "my-key", + }); + expect(second.buffer).toBe(""); + }); + + it("parses EMBED_DONE across two chunks", () => { + const first = processProgressChunk("EMBED_D", ""); + expect(first.events).toHaveLength(0); + expect(first.buffer).toBe("EMBED_D"); + + const second = processProgressChunk("ONE\n", first.buffer); + expect(second.events).toHaveLength(1); + expect(second.events[0]).toEqual({ + prefix: "EMBED", + event: "DONE", + }); + expect(second.buffer).toBe(""); + }); + it("parses EMBED tokens with interleaved normal output across chunks", () => { + // First chunk: START is complete, PROGRESS line is split without trailing \n + const first = processProgressChunk( + "log line\nEMBED_START:2\nmore log\nEMBED_PROGRESS:1:2:", + "", + ); + expect(first.events).toHaveLength(1); + expect(first.events[0]).toEqual({ prefix: "EMBED", event: "START", total: 2 }); + // Buffer holds only the last incomplete fragment + expect(first.buffer).toBe("EMBED_PROGRESS:1:2:"); + + // Second chunk completes the PROGRESS line and has complete DONE + const second = processProgressChunk("k1\nEMBED_DONE\n", first.buffer); + expect(second.events).toHaveLength(2); + expect(second.events[0]).toEqual({ + prefix: "EMBED", event: "PROGRESS", + current: 1, total: 2, key: "k1", + }); + expect(second.events[1]).toEqual({ prefix: "EMBED", event: "DONE" }); + expect(second.buffer).toBe(""); + }); + + it("parses all EMBED tokens across chunk boundaries", () => { + const first = processProgressChunk("EMBED_START:2\nEMBED_PR", ""); + expect(first.events).toHaveLength(1); + expect(first.events[0]).toEqual({ prefix: "EMBED", event: "START", total: 2 }); + expect(first.buffer).toBe("EMBED_PR"); + + const second = processProgressChunk("OGRESS:1:2:k1\nEMBED_DONE\n", first.buffer); + expect(second.events).toHaveLength(2); + expect(second.events[0]).toEqual({ + prefix: "EMBED", event: "PROGRESS", + current: 1, total: 2, key: "k1", + }); + expect(second.events[1]).toEqual({ prefix: "EMBED", event: "DONE" }); + expect(second.buffer).toBe(""); + }); +}); + +// ── parseProgressLine ── + +describe("parseProgressLine", () => { + it("returns START for OCR_REBUILD_START:5", () => { + expect(parseProgressLine("OCR_REBUILD_START:5")).toEqual({ + prefix: "OCR_REBUILD", + event: "START", + total: 5, + }); + }); + + it("returns PROGRESS for full token", () => { + expect(parseProgressLine("OCR_REDO_PROGRESS:3:10:paper-z")).toEqual({ + prefix: "OCR_REDO", + event: "PROGRESS", + current: 3, + total: 10, + key: "paper-z", + }); + }); + + it("returns DONE for exact match", () => { + expect(parseProgressLine("EMBED_DONE")).toEqual({ + prefix: "EMBED", + event: "DONE", + }); + }); + + it("returns null for non-token line", () => { + expect(parseProgressLine("hello world")).toBeNull(); + }); + + it("returns null for empty string", () => { + expect(parseProgressLine("")).toBeNull(); + }); +}); diff --git a/paperforge/worker/ocr.py b/paperforge/worker/ocr.py index ec3252c7..09908187 100644 --- a/paperforge/worker/ocr.py +++ b/paperforge/worker/ocr.py @@ -2102,6 +2102,150 @@ def _rewrite_note_fields(text: str, **fields: object) -> str: return updated +def _find_notes_by_key(lit_root: Path | None) -> dict[str, Path]: + """Scan literature directory and build {zotero_key: note_path} map.""" + notes: dict[str, Path] = {} + if not lit_root or not lit_root.exists(): + return notes + for note_file in sorted(lit_root.rglob("*.md")): + if note_file.name in ("fulltext.md", "deep-reading.md", "discussion.md"): + continue + try: + text = note_file.read_text(encoding="utf-8") + except Exception: + continue + key_match = re.search(r"^zotero_key:\s*(.+)$", text, re.MULTILINE) + if not key_match: + continue + zkey = key_match.group(1).strip().strip('"').strip("'") + if not zkey or not re.match(r"^[A-Za-z0-9]{8}$", zkey): + continue + existing = notes.get(zkey) + if existing is None or note_file.name == f"{zkey}.md": + notes[zkey] = note_file + return notes + + +def redo_papers_for_keys( + vault: Path, + keys: list[str], + *, + verbose: bool = False, + progress_callback: Callable[[str], None] | None = None, + stop_check: Callable[[], bool] | None = None, +) -> dict: + """Redo specific papers: full artifact delete + OCR run + post-check. + + Processes papers sequentially so per-paper progress and cooperative + stop are natural. Each paper goes through: + 1. Delete derived artifacts and workspace fulltexts + 2. Rewrite note as pending + 3. Run OCR for this single paper + 4. Check result, update note, refresh index + + Args: + vault: Vault root. + keys: Zotero citation keys to redo. + verbose: Log additional details. + progress_callback: Called with key after each paper's cycle completes. + stop_check: Called before each paper; return True to stop. + + Returns: + dict with success_keys, failed_keys, exit_code. + """ + from paperforge.worker._utils import pipeline_paths + + paths = pipeline_paths(vault) + ocr_root = paths.get("ocr") + lit_root = paths.get("literature") + + notes_by_key = _find_notes_by_key(lit_root) + + success_keys: list[str] = [] + failed_keys: list[str] = [] + _ocr_exit_code = 0 + + for key in keys: + if stop_check is not None and stop_check(): + break + + note_file = notes_by_key.get(key) + if note_file is None: + logger.warning("Redo: note not found for %s, counting as failed", key) + failed_keys.append(key) + _ocr_exit_code = 1 + if progress_callback is not None: + progress_callback(key) + continue + + try: + original_text = note_file.read_text(encoding="utf-8") + except Exception: + logger.warning("Redo: cannot read note for %s, counting as failed", key) + failed_keys.append(key) + if progress_callback is not None: + progress_callback(key) + _ocr_exit_code = 1 + continue + + # ── Guard: nopdf papers must not be touched ── + meta_path = ocr_root / key / "meta.json" if ocr_root else None + _existing_meta = read_json(meta_path) if meta_path and meta_path.exists() else {} + if str(_existing_meta.get("ocr_status", "") or "").strip().lower() == "nopdf": + logger.warning("Redo: %s is nopdf — cannot redo (missing PDF), skipping", key) + failed_keys.append(key) + _ocr_exit_code = 1 + if progress_callback is not None: + progress_callback(key) + continue + + # ── Phase 1: delete artifacts ── + for wf in _workspace_fulltext_candidates(lit_root, key): + wf.unlink(missing_ok=True) + ocr_dir = ocr_root / key if ocr_root else None + if ocr_dir and ocr_dir.exists(): + shutil.rmtree(ocr_dir) + + # ── Phase 2: rewrite note as pending ── + text = _rewrite_note_fields( + original_text, + do_ocr=True, ocr_status="pending", fulltext_md_path="", ocr_redo=True, + ) + note_file.write_text(text, encoding="utf-8") + + # ── Phase 3: run OCR for this paper ── + _paper_exit = run_ocr(vault, verbose=verbose, no_progress=True, selected_keys={key}) + if _paper_exit != 0: + _ocr_exit_code = _paper_exit + + # ── Phase 4: post-OCR check ── + meta_path = ocr_root / key / "meta.json" if ocr_root else None + meta = read_json(meta_path) if meta_path and meta_path.exists() else {} + status, _error = validate_ocr_meta(paths, meta) if meta else ("pending", "") + + current_text = note_file.read_text(encoding="utf-8") + current_text = _rewrite_note_fields(current_text, ocr_status=status) + if status == "done": + current_text = _rewrite_note_fields(current_text, ocr_redo=False) + success_keys.append(key) + else: + current_text = _rewrite_note_fields(current_text, ocr_redo=True) + failed_keys.append(key) + _ocr_exit_code = _ocr_exit_code or 1 + note_file.write_text(current_text, encoding="utf-8") + refresh_index_entry(vault, key) + + if progress_callback is not None: + progress_callback(key) + + return { + "success_keys": success_keys, + "failed_keys": failed_keys, + "exit_code": _ocr_exit_code, + } + + + def ocr_redo_papers(vault: Path, dry_run: bool = False, verbose: bool = False, no_progress: bool = False) -> int: """Scan for papers with ocr_redo: true, reset and immediately rerun OCR. @@ -2207,6 +2351,7 @@ def ocr_redo_papers(vault: Path, dry_run: bool = False, verbose: bool = False, n return exit_code + def run_ocr( vault: Path, verbose: bool = False, no_progress: bool = False, selected_keys: set[str] | None = None ) -> int: diff --git a/paperforge/worker/ocr_rebuild.py b/paperforge/worker/ocr_rebuild.py index 380d11eb..2765ca78 100644 --- a/paperforge/worker/ocr_rebuild.py +++ b/paperforge/worker/ocr_rebuild.py @@ -4,6 +4,7 @@ import datetime import json import logging from pathlib import Path +from collections.abc import Callable from paperforge.core.io import read_json, write_json @@ -587,7 +588,10 @@ def _rebuild_one_paper(vault: Path, key: str) -> dict: _phase5_finalize(resolved, structured, rendered, span_meta_patch, health_overall=health_overall) return {"key": key, "status": "ok"} -def _run_parallel_rebuild(vault: Path, keys: list[str], workers: int) -> list[dict]: +def _run_parallel_rebuild( + vault: Path, keys: list[str], workers: int, + on_progress: Callable[[str], None] | None = None, +) -> list[dict]: """Run rebuild in parallel using a process pool.""" from concurrent.futures import ProcessPoolExecutor, as_completed @@ -601,15 +605,20 @@ def _run_parallel_rebuild(vault: Path, keys: list[str], workers: int) -> list[di results.append(result) except Exception as e: results.append({"key": key, "status": "failed", "error": str(e)}) + if on_progress is not None: + on_progress(key) return results + def run_derived_rebuild_for_keys( vault: Path, keys: list[str], progress_bar=None, checkpoint_dir: Path | None = None, parallel: int = 4, + on_progress: Callable[[str], None] | None = None, + stop_check: Callable[[], bool] | None = None, ) -> dict: """Run derived-layer rebuild for the given paper keys without raw OCR rerun. @@ -625,6 +634,9 @@ def run_derived_rebuild_for_keys( progress_bar: Optional progress bar wrapper (tqdm-style). checkpoint_dir: Deprecated, kept for backward compatibility. parallel: Number of parallel workers (0 = serial). Default 4. + on_progress: Optional callback called with key after each paper completes. + stop_check: Optional callable returning True if stop was requested. + Checked before starting the next paper (serial path only). """ if not keys: return {"rebuild_count": 0} @@ -632,18 +644,25 @@ def run_derived_rebuild_for_keys( workers = int(parallel) if parallel else 0 if workers > 0 and len(keys) > 1: - results = _run_parallel_rebuild(vault, keys, workers) + # Parallel path: does not support stop_check — all futures submitted at once. + results = _run_parallel_rebuild(vault, keys, workers, on_progress=on_progress) return {"rebuild_count": sum(1 for r in results if r.get("status") == "ok")} rebuilt_count = 0 keys_iter = progress_bar(keys, desc="OCR rebuild") if progress_bar else keys for key in keys_iter: + if stop_check is not None and stop_check(): + break result = _rebuild_one_paper(vault, key) if result.get("status") == "ok": rebuilt_count += 1 + if on_progress is not None: + on_progress(key) return {"rebuild_count": rebuilt_count} + + def _enrich_meta_from_paper_note(vault: Path, key: str, meta_path: Path) -> None: """Read the paper note's Zotero frontmatter and inject into meta.json. diff --git a/tests/cli/test_ocr_progress_contracts.py b/tests/cli/test_ocr_progress_contracts.py new file mode 100644 index 00000000..b118ede2 --- /dev/null +++ b/tests/cli/test_ocr_progress_contracts.py @@ -0,0 +1,500 @@ +"""CLI contract tests for OCR rebuild/redo progress tokens. + +Tests the streaming progress token contract defined in issue #64: +- OCR_REBUILD_START:{total} +- OCR_REBUILD_PROGRESS:{current}:{total}:{key} +- OCR_REBUILD_DONE +- OCR_REDO_START:{total} +- OCR_REDO_PROGRESS:{current}:{total}:{key} +- OCR_REDO_DONE + +Batch mode only (multiple keys). Single-key calls remain silent. +Dry-run must not emit progress tokens. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + + +# ── Helpers ────────────────────────────────────────────────────────────── + + +def _make_minimal_vault(root: Path) -> Path: + """Create a minimal vault with paperforge.json for path resolution.""" + vault = root / "vault" + vault.mkdir(parents=True) + cfg = { + "system_dir": "System", + "resources_dir": "Resources", + "literature_dir": "Literature", + "control_dir": "LiteratureControl", + "base_dir": "Bases", + "skill_dir": ".opencode/skills", + "zotero_dir": "System/Zotero", + } + (vault / "paperforge.json").write_text(json.dumps(cfg), encoding="utf-8") + dirs = [ + "System/PaperForge/ocr", + "System/PaperForge/exports", + "System/PaperForge/indexes", + "System/PaperForge/config", + "System/Zotero/storage", + "Resources/Literature", + "Resources/LiteratureControl", + "Bases", + ".opencode/skills/literature-qa/scripts", + ] + for d in dirs: + (vault / d).mkdir(parents=True) + return vault + + +def _add_ocr_meta(vault: Path, key: str, status: str = "pending") -> None: + """Create OCR meta.json for a given paper key.""" + meta_dir = vault / "System/PaperForge/ocr" / key + meta_dir.mkdir(parents=True, exist_ok=True) + meta = { + "zotero_key": key, + "ocr_status": status, + "page_count": 1, + "generated_at": "2026-07-01T00:00:00+00:00", + } + (meta_dir / "meta.json").write_text(json.dumps(meta, indent=2), encoding="utf-8") + + + +def _add_literature_note(vault: Path, key: str, title: str = "Test Paper") -> None: + """Create a minimal literature note with zotero_key in frontmatter.""" + lit_dir = vault / "Resources" / "Literature" + lit_dir.mkdir(parents=True, exist_ok=True) + note = lit_dir / f"{key}.md" + note.write_text( + f"---\nzotero_key: \"{key}\"\ntitle: \"{title}\"\n---\n\nPaper content.\n", + encoding="utf-8", + ) + + +def _mock_run_ocr(monkeypatch) -> None: + """Mock run_ocr to create minimal meta.json with done status (no API calls).""" + import json + + def _fake_run_ocr(vault: Path, **kwargs: object) -> int: + keys: set[str] = kwargs.get("selected_keys") or set() + from paperforge.worker._utils import pipeline_paths + paths = pipeline_paths(vault) + ocr_root = paths.get("ocr") + if ocr_root: + for key in keys: + meta_dir = ocr_root / key + meta_dir.mkdir(parents=True, exist_ok=True) + (meta_dir / "meta.json").write_text( + json.dumps({"zotero_key": key, "ocr_status": "done"}), + encoding="utf-8", + ) + return 0 + + monkeypatch.setattr("paperforge.worker.ocr.run_ocr", _fake_run_ocr) + + +def _mock_validate_ocr_meta(monkeypatch) -> None: + """Mock validate_ocr_meta to return done status.""" + monkeypatch.setattr( + "paperforge.worker.ocr.validate_ocr_meta", + lambda _paths, _meta: ("done", ""), + ) + +# ── Redo Progress Token Tests ──────────────────────────────────────────── + + +class TestOcrRedoProgressTokens: + """OCR_REDO_* progress token contract tests.""" + + _K1 = "KEY00001" + _K2 = "KEY00002" + _K3 = "KEY00003" + + def test_redo_multi_key_emits_start_progress_done( + self, capsys, tmp_path, monkeypatch + ): + """Multiple redo keys emit OCR_REDO_START, PROGRESS per key, DONE.""" + _mock_run_ocr(monkeypatch) + _mock_validate_ocr_meta(monkeypatch) + vault = _make_minimal_vault(tmp_path) + for k in (self._K1, self._K2, self._K3): + _add_literature_note(vault, k) + + from paperforge.commands.ocr import _run_ocr_redo + + rc = _run_ocr_redo(vault, keys=[self._K1, self._K2, self._K3]) + + assert rc == 0 + captured = capsys.readouterr().out + lines = [l for l in captured.split("\n") if l.strip()] + + # Token order: START, then PROGRESS per key, summary, DONE + assert lines[0] == "OCR_REDO_START:3" + progress_lines = [l for l in lines if l.startswith("OCR_REDO_PROGRESS")] + assert len(progress_lines) == 3 + assert progress_lines[0] == "OCR_REDO_PROGRESS:1:3:KEY00001" + assert progress_lines[1] == "OCR_REDO_PROGRESS:2:3:KEY00002" + assert progress_lines[2] == "OCR_REDO_PROGRESS:3:3:KEY00003" + assert "Redo OCR done" in captured + assert "OCR_REDO_DONE" in captured + + def test_redo_single_key_no_tokens(self, capsys, tmp_path, monkeypatch): + """Single redo key emits no progress tokens.""" + _mock_run_ocr(monkeypatch) + _mock_validate_ocr_meta(monkeypatch) + vault = _make_minimal_vault(tmp_path) + _add_literature_note(vault, self._K1) + + from paperforge.commands.ocr import _run_ocr_redo + + rc = _run_ocr_redo(vault, keys=[self._K1]) + + assert rc == 0 + captured = capsys.readouterr().out + assert "OCR_REDO_START" not in captured + assert "OCR_REDO_PROGRESS" not in captured + assert "OCR_REDO_DONE" not in captured + assert "Redo OCR done=1" in captured + + def test_redo_dry_run_no_tokens(self, capsys, tmp_path, monkeypatch): + """Dry-run redo emits no progress tokens.""" + _mock_run_ocr(monkeypatch) + _mock_validate_ocr_meta(monkeypatch) + vault = _make_minimal_vault(tmp_path) + _add_literature_note(vault, self._K1) + _add_literature_note(vault, self._K2) + + from paperforge.commands.ocr import _run_ocr_redo + + rc = _run_ocr_redo(vault, keys=[self._K1, self._K2], dry_run=True) + + assert rc == 0 + captured = capsys.readouterr().out + # Multi-key dry-run is not batch (batch = total > 1 and not dry_run) + assert "OCR_REDO_START" not in captured + assert "OCR_REDO_PROGRESS" not in captured + assert "OCR_REDO_DONE" not in captured + assert "Would redo" in captured + + def test_redo_prefix_separate_from_rebuild(self, capsys, tmp_path, monkeypatch): + """OCR_REDO prefix is distinct from OCR_REBUILD.""" + _mock_run_ocr(monkeypatch) + _mock_validate_ocr_meta(monkeypatch) + vault = _make_minimal_vault(tmp_path) + for k in (self._K1, self._K2, self._K3): + _add_literature_note(vault, k) + + from paperforge.commands.ocr import _run_ocr_redo + + _run_ocr_redo(vault, keys=[self._K1, self._K2, self._K3]) + captured = capsys.readouterr().out + + assert "OCR_REDO_START" in captured + assert "OCR_REDO_PROGRESS" in captured + assert "OCR_REDO_DONE" in captured + assert "OCR_REBUILD_START" not in captured + + + + +# ── Rebuild Progress Token Tests ───────────────────────────────────────── + + +class TestOcrRebuildProgressTokens: + """OCR_REBUILD_* progress token contract tests.""" + + def _setup_mock_selection_and_worker( + self, vault: Path, keys: list[str], monkeypatch + ) -> None: + """Mock selection and worker for rebuild, calling on_progress.""" + from paperforge.worker.ocr_maintenance import OCRMaintenanceRow as Row + + rows = [Row( + key=k, + title=f"Paper {k}", + title_full=f"Full Title for Paper {k}", + status="done", + health="good", + pages=5, + blocks=100, + version="2.0", + finished_at="2026-07-01", + rebuild_finished_at="", + figures=0, + tables=0, + model="test", + structured_content_hash=f"hash_{k}", + can_rebuild=True, + ) for k in keys] + + monkeypatch.setattr( + "paperforge.worker.ocr_maintenance.collect_maintenance_rows", + lambda _v: rows, + ) + def _mock_rebuild( + _vault: Path, + _keys: list[str], + **kwargs: object, + ) -> dict: + on_progress = kwargs.get("on_progress") + if on_progress: + for k in _keys: + on_progress(k) + return {"rebuild_count": len(_keys)} + + monkeypatch.setattr( + "paperforge.worker.ocr_rebuild.run_derived_rebuild_for_keys", + _mock_rebuild, + ) + + def test_rebuild_multi_key_emits_start_progress_done( + self, capsys, monkeypatch, tmp_path + ): + """Multiple rebuild keys emit OCR_REBUILD_START, PROGRESS per key, DONE.""" + vault = _make_minimal_vault(tmp_path) + keys = ["KEY001", "KEY002", "KEY003"] + self._setup_mock_selection_and_worker(vault, keys, monkeypatch) + + from paperforge.commands.ocr import _run_ocr_rebuild + + rc = _run_ocr_rebuild(vault, keys=keys) + + assert rc == 0 + captured = capsys.readouterr().out + lines = [l for l in captured.split("\n") if l.strip()] + + assert lines[0] == "OCR_REBUILD_START:3", f"First token: {lines}" + progress_lines = [l for l in lines if "OCR_REBUILD_PROGRESS" in l] + assert len(progress_lines) == 3 + assert progress_lines[0] == "OCR_REBUILD_PROGRESS:1:3:KEY001" + assert progress_lines[1] == "OCR_REBUILD_PROGRESS:2:3:KEY002" + assert progress_lines[2] == "OCR_REBUILD_PROGRESS:3:3:KEY003" + assert lines[-1] == "OCR_REBUILD_DONE", f"Last token: {lines}" + + def test_rebuild_single_key_no_tokens(self, capsys, monkeypatch, tmp_path): + """Single rebuild key emits no progress tokens.""" + vault = _make_minimal_vault(tmp_path) + key = "KEY001" + self._setup_mock_selection_and_worker(vault, [key], monkeypatch) + + from paperforge.commands.ocr import _run_ocr_rebuild + + rc = _run_ocr_rebuild(vault, keys=[key]) + + assert rc == 0 + captured = capsys.readouterr().out + assert "OCR_REBUILD_START" not in captured + assert "OCR_REBUILD_PROGRESS" not in captured + assert "OCR_REBUILD_DONE" not in captured + + def test_rebuild_dry_run_no_tokens(self, capsys, monkeypatch, tmp_path): + """Dry-run rebuild emits no progress tokens.""" + vault = _make_minimal_vault(tmp_path) + self._setup_mock_selection_and_worker(vault, ["KEY001", "KEY002"], monkeypatch) + + from paperforge.commands.ocr import _run_ocr_rebuild + + rc = _run_ocr_rebuild(vault, keys=["KEY001", "KEY002"], dry_run=True) + + assert rc == 0 + captured = capsys.readouterr().out + assert "OCR_REBUILD_START" not in captured + assert "OCR_REBUILD_PROGRESS" not in captured + assert "OCR_REBUILD_DONE" not in captured + assert "Would rebuild" in captured + + def test_rebuild_prefix_uses_ocr_rebuild_not_redo( + self, capsys, monkeypatch, tmp_path + ): + """OCR rebuild uses OCR_REBUILD prefix, not OCR_REDO.""" + vault = _make_minimal_vault(tmp_path) + keys = ["KEY001", "KEY002"] + self._setup_mock_selection_and_worker(vault, keys, monkeypatch) + + from paperforge.commands.ocr import _run_ocr_rebuild + + _run_ocr_rebuild(vault, keys=keys) + captured = capsys.readouterr().out + + assert "OCR_REBUILD_START" in captured + assert "OCR_REBUILD_PROGRESS" in captured + assert "OCR_REBUILD_DONE" in captured + assert "OCR_REDO_START" not in captured + +class TestCooperativeStop: + """Cooperative stop tests for batch OCR operations.""" + + def test_rebuild_stop_emits_done_and_exit_code_130(self, capsys, monkeypatch, tmp_path): + """Rebuild batch stop sets exit 130 and emits DONE with partial progress.""" + vault = _make_minimal_vault(tmp_path) + keys = ["KEY001", "KEY002", "KEY003"] + + # Set up maintenance rows via mock + from paperforge.worker.ocr_maintenance import OCRMaintenanceRow as Row + rows = [Row( + key=k, title=f"Paper {k}", title_full=f"Full {k}", + status="done", health="good", pages=5, blocks=100, + version="2.0", finished_at="2026-07-01", rebuild_finished_at="", + figures=0, tables=0, model="test", + structured_content_hash=f"hash_{k}", can_rebuild=True, + ) for k in keys] + monkeypatch.setattr( + "paperforge.worker.ocr_maintenance.collect_maintenance_rows", + lambda _v: rows, + ) + + # Mock _rebuild_one_paper so the real run_derived_rebuild_for_keys can run + call_count = [0] + def _mock_rebuild_one(vault, key): + call_count[0] += 1 + return {"key": key, "status": "ok"} + monkeypatch.setattr( + "paperforge.worker.ocr_rebuild._rebuild_one_paper", + _mock_rebuild_one, + ) + + # Mock stop_check to stop after first paper + stop_count = [0] + def _stop_check(): + stop_count[0] += 1 + return stop_count[0] > 1 # stop before the 2nd call + + from paperforge.worker.ocr_rebuild import run_derived_rebuild_for_keys + from paperforge.worker._progress import progress_bar + + # Simulate what _run_ocr_rebuild does for batch + print("OCR_REBUILD_START:3") + count = [0] + def _on_progress(key): + count[0] += 1 + print(f"OCR_REBUILD_PROGRESS:{count[0]}:3:{key}") + + result = run_derived_rebuild_for_keys( + vault, keys, + progress_bar=progress_bar, + parallel=0, + on_progress=_on_progress, + stop_check=_stop_check, + ) + print(f"Done. Rebuilt {result['rebuild_count']} paper(s).") + print("OCR_REBUILD_DONE") + + captured = capsys.readouterr().out + lines = [l for l in captured.split("\n") if l.strip()] + + # Only first key should have been processed + assert result["rebuild_count"] == 1 + assert call_count[0] == 1 + # Should still emit DONE + assert "OCR_REBUILD_START:3" in captured + assert "OCR_REBUILD_PROGRESS:1:3:KEY001" in captured + assert "OCR_REBUILD_PROGRESS:2:3:KEY002" not in captured + assert "OCR_REBUILD_PROGRESS:3:3:KEY003" not in captured + assert "OCR_REBUILD_DONE" in captured + + def test_redo_stop_emits_done_and_partial_progress(self, capsys, monkeypatch, tmp_path): + """Redo batch stop emits DONE with partial progress and exit 130.""" + _mock_run_ocr(monkeypatch) + _mock_validate_ocr_meta(monkeypatch) + vault = _make_minimal_vault(tmp_path) + _add_literature_note(vault, "KEY00001") + _add_literature_note(vault, "KEY00002") + _add_literature_note(vault, "KEY00003") + + from paperforge.commands.ocr import _run_ocr_redo + + # Mock _make_cooperative_stop to stop after first paper + class _StopChecker: + def __init__(self): + self._call_count = 0 + def __call__(self): + self._call_count += 1 + return self._call_count >= 2 # stop before 2nd paper + + checker = _StopChecker() + def _fake_make_cooperative_stop(): + return (checker, lambda: None) + + monkeypatch.setattr( + "paperforge.commands.ocr._make_cooperative_stop", + _fake_make_cooperative_stop, + ) + + rc = _run_ocr_redo(vault, keys=["KEY00001", "KEY00002", "KEY00003"]) + + assert rc == 130, f"Expected exit code 130 for stopped batch, got {rc}" + captured = capsys.readouterr().out + lines = [l for l in captured.split("\n") if l.strip()] + + assert lines[0] == "OCR_REDO_START:3" + assert "OCR_REDO_PROGRESS:1:3:KEY00001" in captured + assert "OCR_REDO_PROGRESS:2:3:KEY00002" not in captured + assert "OCR_REDO_DONE" in captured + assert "Batch stopped" in captured + + + # On Windows os.kill(pid, SIGINT) does not trigger Python signal + # handlers the same way; this test is platform-dependent and skipped. + def test_make_cooperative_stop_signal_handler(self): + """_make_cooperative_stop installs handler and tracks flag.""" + import sys + if sys.platform == "win32": + pytest.skip("SIGINT signal handling differs on Windows") + from paperforge.commands.ocr import _make_cooperative_stop + + is_stopped, restore = _make_cooperative_stop() + + # Initially not stopped + assert not is_stopped() + + # Simulate SIGINT + import signal + import os + os.kill(os.getpid(), signal.SIGINT) + + # Flag should be set + assert is_stopped() + + # Restore original handler + restore() + + # After restore, new call installs fresh handler + is_stopped2, restore2 = _make_cooperative_stop() + assert not is_stopped2() + restore2() + + def test_make_cooperative_stop_stdin(self, monkeypatch): + """_make_cooperative_stop reads PAPERFORGE_STOP from stdin to set flag.""" + import os + import time + + r_fd, w_fd = os.pipe() + fake_stdin = os.fdopen(r_fd, "r", encoding="utf-8") + + monkeypatch.setattr("sys.stdin", fake_stdin) + + from paperforge.commands.ocr import _make_cooperative_stop + + is_stopped, restore = _make_cooperative_stop() + + # Initially not stopped + assert not is_stopped() + + # Write stop command to the pipe + os.write(w_fd, b"PAPERFORGE_STOP\n") + os.close(w_fd) + + # Give reader thread time to process + time.sleep(0.2) + + # Flag should be set + assert is_stopped(), "Stdin stop command should set flag" + + restore() diff --git a/tests/test_ocr_maintenance.py b/tests/test_ocr_maintenance.py index 927fa6c3..7628d25e 100644 --- a/tests/test_ocr_maintenance.py +++ b/tests/test_ocr_maintenance.py @@ -631,54 +631,79 @@ class TestOcrListKeys: class TestOcrRedoKeyed: """_run_ocr_redo with keys parameter.""" - def test_redo_single_key(self, tmp_path) -> None: + def test_redo_single_key(self, tmp_path, monkeypatch) -> None: + """Single key redo delegates to worker with full cycle.""" from paperforge.commands.ocr import _run_ocr_redo - - ocr_root = _ocr_path(tmp_path) - (ocr_root / "KEY1").mkdir(parents=True) - meta = {"zotero_key": "KEY1", "ocr_status": "done", "ocr_job_id": "job-123"} - (ocr_root / "KEY1" / "meta.json").write_text(json.dumps(meta), encoding="utf-8") - - result = _run_ocr_redo(tmp_path, keys=["KEY1"]) - assert result == 0 - reloaded = json.loads( - (ocr_root / "KEY1" / "meta.json").read_text(encoding="utf-8") + from tests.cli.test_ocr_progress_contracts import ( + _make_minimal_vault, + _add_literature_note, + _mock_run_ocr, ) - assert reloaded["ocr_status"] == "pending" - assert reloaded["ocr_job_id"] == "" + _mock_run_ocr(monkeypatch) + monkeypatch.setattr("paperforge.worker.ocr.validate_ocr_meta", + lambda _p, _m: ("done", "")) - def test_redo_multiple_keys(self, tmp_path) -> None: - from paperforge.commands.ocr import _run_ocr_redo + vault = _make_minimal_vault(tmp_path) + _add_literature_note(vault, "KEY00001") - ocr_root = _ocr_path(tmp_path) - for key in ["KEY1", "KEY2"]: - (ocr_root / key).mkdir(parents=True) - meta = {"zotero_key": key, "ocr_status": "done", "ocr_job_id": f"job-{key}"} - (ocr_root / key / "meta.json").write_text(json.dumps(meta), encoding="utf-8") - - result = _run_ocr_redo(tmp_path, keys=["KEY1", "KEY2"]) + result = _run_ocr_redo(vault, keys=["KEY00001"]) assert result == 0 - for key in ["KEY1", "KEY2"]: - reloaded = json.loads( - (ocr_root / key / "meta.json").read_text(encoding="utf-8") - ) - assert reloaded["ocr_status"] == "pending" - assert reloaded["ocr_job_id"] == "" + # Worker should have created meta.json with done status + meta_path = vault / "System/PaperForge/ocr" / "KEY00001" / "meta.json" + assert meta_path.exists(), "Meta should exist after redo cycle" + import json + meta = json.loads(meta_path.read_text(encoding="utf-8")) + assert meta.get("ocr_status") == "done" - def test_redo_dry_run(self, tmp_path) -> None: + def test_redo_multiple_keys(self, tmp_path, monkeypatch) -> None: + """Multiple keys redo delegates to worker for each key.""" from paperforge.commands.ocr import _run_ocr_redo + from tests.cli.test_ocr_progress_contracts import ( + _make_minimal_vault, + _add_literature_note, + _mock_run_ocr, + ) + _mock_run_ocr(monkeypatch) + monkeypatch.setattr("paperforge.worker.ocr.validate_ocr_meta", + lambda _p, _m: ("done", "")) - ocr_root = _ocr_path(tmp_path) - (ocr_root / "KEY1").mkdir(parents=True) - meta = {"zotero_key": "KEY1", "ocr_status": "done", "ocr_job_id": "job-123"} - meta_path = ocr_root / "KEY1" / "meta.json" - meta_path.write_text(json.dumps(meta), encoding="utf-8") + vault = _make_minimal_vault(tmp_path) + for k in ("KEY00001", "KEY00002"): + _add_literature_note(vault, k) - _run_ocr_redo(tmp_path, keys=["KEY1"], dry_run=True) + result = _run_ocr_redo(vault, keys=["KEY00001", "KEY00002"]) + assert result == 0 + # Both papers should have meta.json + for k in ("KEY00001", "KEY00002"): + meta_path = vault / "System/PaperForge/ocr" / k / "meta.json" + assert meta_path.exists(), f"Meta for {k} should exist" + def test_redo_dry_run(self, tmp_path, monkeypatch) -> None: + """Dry-run redo prints without modifying artifacts.""" + from paperforge.commands.ocr import _run_ocr_redo + from tests.cli.test_ocr_progress_contracts import ( + _make_minimal_vault, + _add_literature_note, + ) - reloaded = json.loads(meta_path.read_text(encoding="utf-8")) - assert reloaded["ocr_status"] == "done" - assert reloaded["ocr_job_id"] == "job-123" + vault = _make_minimal_vault(tmp_path) + _add_literature_note(vault, "KEY00001") + + import io as _io + import sys as _sys + captured = _io.StringIO() + old = _sys.stdout + _sys.stdout = captured + try: + result = _run_ocr_redo(vault, keys=["KEY00001"], dry_run=True) + finally: + _sys.stdout = old + output = captured.getvalue() + assert result == 0 + assert "Would redo" in output + assert "dry-run" in output.lower() + # Dry-run must not create OCR directory + ocr_dir = vault / "System/PaperForge/ocr" / "KEY00001" + assert not ocr_dir.exists(), "Dry-run should not create OCR artifacts" def test_redo_no_keys_falls_back(self, monkeypatch) -> None: from paperforge.commands.ocr import _run_ocr_redo @@ -701,33 +726,43 @@ class TestOcrRedoKeyed: assert call_kwargs.get("dry_run") is False assert call_kwargs.get("verbose") is True - def test_redo_skips_nopdf(self, tmp_path) -> None: + def test_redo_skips_nopdf(self, tmp_path, monkeypatch) -> None: + """nopdf papers are guarded and reported as failed, not reset.""" from paperforge.commands.ocr import _run_ocr_redo + from tests.cli.test_ocr_progress_contracts import ( + _make_minimal_vault, + _add_literature_note, + _mock_run_ocr, + ) + _mock_run_ocr(monkeypatch) + monkeypatch.setattr("paperforge.worker.ocr.validate_ocr_meta", + lambda _p, _m: ("done", "")) - ocr_root = _ocr_path(tmp_path) - - (ocr_root / "NOPDF").mkdir(parents=True) - (ocr_root / "NOPDF" / "meta.json").write_text( - json.dumps({"zotero_key": "NOPDF", "ocr_status": "nopdf"}), - encoding="utf-8", - ) - (ocr_root / "KEY1").mkdir(parents=True) - (ocr_root / "KEY1" / "meta.json").write_text( - json.dumps({"zotero_key": "KEY1", "ocr_status": "done", "ocr_job_id": "job-123"}), + vault = _make_minimal_vault(tmp_path) + # Paper with nopdf status + _add_literature_note(vault, "NOPDF001") + nopdf_meta = vault / "System/PaperForge/ocr" / "NOPDF001" / "meta.json" + nopdf_meta.parent.mkdir(parents=True, exist_ok=True) + nopdf_meta.write_text( + json.dumps({"zotero_key": "NOPDF001", "ocr_status": "nopdf"}), encoding="utf-8", ) + # Normal redoable paper + _add_literature_note(vault, "KEY00001") - _run_ocr_redo(tmp_path, keys=["NOPDF", "KEY1"]) + result = _run_ocr_redo(vault, keys=["NOPDF001", "KEY00001"]) + # Should be non-zero (nopdf counts as failed) + assert result != 0 - nopdf_meta = json.loads( - (ocr_root / "NOPDF" / "meta.json").read_text(encoding="utf-8") - ) - assert nopdf_meta["ocr_status"] == "nopdf" + # nopdf paper must be untouched + assert nopdf_meta.exists() + import json as _json + nopdf_reloaded = _json.loads(nopdf_meta.read_text(encoding="utf-8")) + assert nopdf_reloaded["ocr_status"] == "nopdf" - key1_meta = json.loads( - (ocr_root / "KEY1" / "meta.json").read_text(encoding="utf-8") - ) - assert key1_meta["ocr_status"] == "pending" + # Regular paper should have been processed + key1_meta = vault / "System/PaperForge/ocr" / "KEY00001" / "meta.json" + assert key1_meta.exists() class TestRunOcrListDispatch: