diff --git a/PROJECT-MANAGEMENT.md b/PROJECT-MANAGEMENT.md index 98d567db..7e886a1c 100644 --- a/PROJECT-MANAGEMENT.md +++ b/PROJECT-MANAGEMENT.md @@ -1,13 +1,13 @@ > **Branch:** `master` | **Last Updated:** 2026-07-18 -> **Active work:** [Control-center PRD #74](https://github.com/LLLin000/PaperForge/issues/74) is split into eight dependency-linked implementation issues (#75–#82). Setup/config (#75), capability envelopes (#76), and Managed Runtime/navigation (#77) are implemented; Library/OCR/Memory capability tracers (#78) are next. +> **Active work:** [Control-center PRD #74](https://github.com/LLLin000/PaperForge/issues/74) is split into eight dependency-linked implementation issues (#75–#82). Setup/config (#75), capability envelopes (#76), Managed Runtime/navigation (#77), and Library/OCR/Memory capability tracers (#78) are implemented; SecretStorage (#79) is next. > > --- > > **Current state:** Retrieval recovery is merged and live. OCR multi-key rebuild/redo streams progress and refreshes canonical maintenance state. The control plane now includes schema-v1 capability envelopes plus a machine-local Managed Runtime with immutable slots, atomic activation, rollback, cancellation, and managed-first command dispatch. > -> #71/#72 prototypes and #73 migration contract are complete. PRD #74 defines the six-module production program; #75–#77 are implemented. The Wayfinder overview and `概览 / 模块详情 / 维护 / 帮助` shell are live; #78 adds real Library/OCR/Memory detail tracers without placeholder pages. +> #71/#72 prototypes and #73 migration contract are complete. PRD #74 defines the six-module production program; #75–#78 are implemented. The Wayfinder overview and `概览 / 模块详情 / 维护 / 帮助` shell are live; #78 added real Library/OCR/Memory detail tracers, replacing placeholder pages. > -> Next: implement [#78](https://github.com/LLLin000/PaperForge/issues/78) in a fresh Matt `/implement` session. +> Next: implement [#79](https://github.com/LLLin000/PaperForge/issues/79) (SecretStorage) in a fresh Matt `/implement` session. ## 1. Architecture ### 1.1 The problem (pre-v2) @@ -63,6 +63,7 @@ raw observations → structural signatures → stable anchors/families → zone | Managed Runtime lifecycle + navigation (#77) | **192 focused + 289 full passed; typecheck/build clean** ✅ | | OMP Matt workflow guard | **Fresh OMP auto-discovery confirmed; 7/7 deterministic guard cases passed** ✅ | +| Library/OCR/Memory capability tracers (#78) | **65 backend tests + 178 focused plugin tests, 324 full plugin tests passed across 11 files; typecheck/build clean; backup restore end-to-end** ✅ |

| Component | Status | @@ -90,6 +91,7 @@ raw observations → structural signatures → stable anchors/families → zone | **Managed Runtime** | ✅ Immutable machine-local slots, atomic pointer activation, rollback/cancel/retention, managed-first command dispatch | | **Agent workflow controls** | ✅ `.omp/RULES.md` + `matt-guard.ts` enforce one-writer Matt flow, worktree isolation, and post-mutation verification before release operations | +| **Library/OCR/Memory detail tracers** | ✅ Real capability probes with module-detail-navigation, installation-navigation, and capability-state views; Python owns capability fact definitions; TypeScript render uses exact allowlist, fails closed on unknown keys | ### 2.3 Fix Status | # | Paper | Issue | Type | Fix | Commit | @@ -155,7 +157,7 @@ raw observations → structural signatures → stable anchors/families → zone [Wayfinder: Restore PaperForge retrieval end to end](https://github.com/LLLin000/PaperForge/issues/45) completed and the resulting retrieval fixes are merged to `master`. The live Literature-hub vault has a healthy 2560-dimensional vec0 index; M and @ search paths are operational. ### Layer 3: Plugin UI -The OCR maintenance slice has a canonical All/Recommended state model, selected batch actions, streaming progress, cooperative stop, and canonical per-row action routing with confirmation gates for destructive operations. Issues #76–#77 now provide production capability envelopes, the four-destination navigation shell, and the machine-local Managed Runtime lifecycle. Installation/Help are real probes; Library/OCR/Memory/Maintenance remain explicit placeholders pending #78/#80. Stale or malformed persisted evidence fails closed. +The OCR maintenance slice has a canonical All/Recommended state model, selected batch actions, streaming progress, cooperative stop, and canonical per-row action routing with confirmation gates for destructive operations. Issues #76–#78 now provide production capability envelopes, the four-destination navigation shell, the machine-local Managed Runtime lifecycle, and real Library/OCR/Memory detail probes. Installation/Help/Library/OCR/Memory are real probes; Maintenance remains an explicit placeholder pending #80. Stale or malformed persisted evidence fails closed. ### Layer 4: Downstream Tools `chunker.py` uses hardcoded section regex + fixed 3-paragraph groups. OCR has rich structured output (sections, headings, figures, tables with captions) — chunker should consume this structure directly. Figures/tables should support separate embedding (text + future vision). @@ -163,7 +165,7 @@ The OCR maintenance slice has a canonical All/Recommended state model, selected Remaining legacy OCR issues (carried forward): ## 4. Active Queue -1. 🔴 **[Control-center PRD #74](https://github.com/LLLin000/PaperForge/issues/74)** — published and split into eight native dependency-linked issues (#75–#82). #75–#77 are implemented; #78 Library/OCR/Memory capability tracers are the next unblocked slice. +1. 🔴 **[Control-center PRD #74](https://github.com/LLLin000/PaperForge/issues/74)** — published and split into eight native dependency-linked issues (#75–#82). #75–#78 are implemented; #79 SecretStorage is the next unblocked slice. 2. ✅ **[Capability-state vocabulary](https://github.com/LLLin000/PaperForge/issues/69)** — resolved at `issuecomment-4971161072`. Orthogonal availability/activity/attention axes, 6-state capability ordinal, 12 canonical verbs, backend-owned severity and primary actions, maintenance projection. 3. ✅ **[Managed runtime](https://github.com/LLLin000/PaperForge/issues/70)** — resolved at `issuecomment-4971239398`. Plugin-managed immutable runtime slots, system-Python bootstrap with validated-triplet fallback, single `active-runtime.json` pointer, `ManagedRuntime` class with `current()`/`status()`/`ensure()`, fail-closed command resolution. 4. ✅ **[Control-center prototype](https://github.com/LLLin000/PaperForge/issues/71)** — resolved with independent Critical/Important PASS review and browser verification at 768px. Six-module control-center HTML prototype covers 5 scenarios with plain-button switcher, primary attention zone, responsive layout, and capability-gated actions. Design decisions recorded in `docs/prototypes/2026-07-14-six-module-control-center.html/.md`. No production implementation before #73. @@ -190,7 +192,8 @@ Remaining legacy OCR issues (carried forward): - [x] Canonicalize setup/config migration ([#75](https://github.com/LLLin000/PaperForge/issues/75)) — 61 focused tests; spec PASS; quality APPROVED - [x] Implement Installation/Help capability tracer ([#76](https://github.com/LLLin000/PaperForge/issues/76)) — 21 backend tests; 169 plugin tests; typecheck/build clean; independent review PASS - [x] Implement Managed Runtime lifecycle and the approved Installation-detail navigation shell ([#77](https://github.com/LLLin000/PaperForge/issues/77)) — 192 focused + 289 full tests; typecheck/build clean; merged to `master` -- [ ] Expose Library, OCR, and Memory capabilities end to end ([#78](https://github.com/LLLin000/PaperForge/issues/78)) +- [x] Expose Library, OCR, and Memory capabilities end to end ([#78](https://github.com/LLLin000/PaperForge/issues/78)) — 65 backend tests + 178 focused plugin tests, 324 full plugin tests across 11 files; typecheck/build clean; fail-closed recognizable config for Library/OCR; red rebuild_result stays non-destructive rebuild; queued OCR progress starts at 0; failed/null Library sync exit outcome is forwarded into fresh Python probe and remains sync actionable +- [ ] Implement SecretStorage for capability secrets ([#79](https://github.com/LLLin000/PaperForge/issues/79)) --- ## 5. Key File Map @@ -348,6 +351,7 @@ Remaining legacy OCR issues (carried forward): | 2026-07-15 | Capability integration advances by real tracer, never frontend optimism | #76 exposes only Installation and Help as real probes; Library/OCR/Memory/Maintenance remain explicit placeholders until their backend envelopes ship. Persisted malformed or stale evidence becomes unknown/invalid rather than ready. | | 2026-07-15 | Module detail navigation extends Wayfinder instead of replacing it | Preserve the primary-attention zone and concrete backend action. Stage `概览 / 模块详情 / 维护 / 帮助` across #77/#78/#80; use explicit module-title navigation, top ordinary buttons, and no dead placeholder details. | | 2026-07-18 | Enforce Matt with sticky rules plus a minimal deterministic hook | Prose alone cannot prevent orchestration drift. Keep lifecycle guidance in `.omp/RULES.md`; use the hook only for mechanically provable boundaries: one writer, worktree isolation, and verification-after-last-mutation before release operations. | +| 2026-07-18 | Capability facts are Python-owned; TypeScript renders via exact allowlist, fail-closed | Python owns all capability fact definitions and cross-module consistency. TypeScript receives only pre-classified display objects; rendering uses an exact allowlist of known content component keys. Unknown keys render nothing (fail-closed) rather than guessing a classification. TypeScript forwards operation exit outcomes but Python classifies sync failure. This avoids duplicating classification logic across the stack. | --- @@ -453,6 +457,7 @@ python -m ruff check paperforge/worker/ocr_*.py | 2026-07-15 | Installation/Help capability tracer + navigation refinement | Implemented schema-v1 probe envelopes, six-module Overview, setup-complete migration, strict persistence/TTL validation, backend-owned action labels and dispatch, responsive/focus-visible UI, and generated bundle. Verification: 21 backend tests, 169 plugin tests, typecheck/build, live Obsidian stale-cache/action-label smoke test, independent review PASS. Matt flow refined PRD #74 and existing #77/#78/#80 without duplicating issues. | [Issue #76](https://github.com/LLLin000/PaperForge/issues/76), [PRD refinement](https://github.com/LLLin000/PaperForge/issues/74#issuecomment-4980322098) | | 2026-07-15 | Managed Runtime lifecycle + final navigation shell | Completed #77 with immutable runtime slots, synchronous fail-closed `current`, probed `status`, install/repair/update/rollback/cancel/retention, managed-first dispatch, Release-N fallback, four-destination navigation, Installation detail, Agent integration, and Help focus restoration. Verification: 192 focused + 289 full tests; typecheck/build clean. Merged to `master` in `173a4e8..4ef9e98`. | [Issue #77](https://github.com/LLLin000/PaperForge/issues/77) | | 2026-07-18 | OMP Matt workflow enforcement | Removed all discoverable Superpowers installations, retained canonical Ask Matt skills, added sticky project rules, and installed an auto-discovered hook for one-writer batches, worktree isolation, and fresh verification gates. Fresh OMP smoke reached the hook; 7/7 deterministic guard cases passed. | `.omp/RULES.md`, `.omp/hooks/pre/matt-guard.ts` | +| 2026-07-18 | Library/OCR/Memory capability tracers (#78) | Implemented real capability probes for Library, OCR, and Memory with module-detail-navigation, installation-navigation, and capability-state views. Python owns capability facts; TypeScript exact allowlist/fail-closed rendering avoids duplicate classification. Repair: canonical PADDLEOCR_API_TOKEN resolution (env/WinReg/dotenv), OCR probe priority fix (provider-unreachable after actionable rows), overview card dispatch (exact allowlist, not probe), memory backup restore end-to-end (integrity_check + atomic os.replace + corrupt preservation), _embedProcess lifecycle cleanup. Final: fail-closed recognizable config for Library/OCR, red rebuild_result stays non-destructive rebuild, queued OCR progress starts at 0, failed/null Library sync exit outcome forwarded into fresh Python probe and remains sync actionable. Verification: 65 backend + 178 focused plugin + 324 full plugin tests across 11 files; typecheck/build clean; live Obsidian 730/768 smoke. | ## 9. Historical Detail Archive diff --git a/paperforge/cli.py b/paperforge/cli.py index ac38e21a..436f25cd 100644 --- a/paperforge/cli.py +++ b/paperforge/cli.py @@ -323,6 +323,8 @@ def build_parser() -> argparse.ArgumentParser: p_memory_build.add_argument("--json", action="store_true", help="Output as JSON") p_memory_status = p_memory_sp.add_parser("status", help="Check memory database status") p_memory_status.add_argument("--json", action="store_true", help="Output as JSON") + p_memory_restore = p_memory_sp.add_parser("restore-backup", help="Restore memory database from a verified backup") + p_memory_restore.add_argument("--json", action="store_true", help="Output as JSON (PFResult envelope)") p_paper_status = sub.add_parser("paper-status", help="Look up a paper's status") p_paper_status.add_argument("query", help="Paper identifier (zotero_key, DOI, title, alias)") @@ -483,14 +485,21 @@ def build_parser() -> argparse.ArgumentParser: p_probe = sub.add_parser("probe", help="Probe a module's capability state") p_probe.add_argument( "probe_module", - choices=["installation", "help"], - help="Module to probe (installation or help)", + choices=["installation", "help", "library", "ocr", "memory"], + help="Module to probe (installation, library, ocr, memory, or help)", ) p_probe.add_argument( "--json", action="store_true", help="Output as schema-v1 capability envelope JSON", ) + p_probe.add_argument( + "--last-operation-exit-code", + type=int, + default=None, + metavar="CODE", + help="Last operation exit code for library sync failure probe", + ) return parser diff --git a/paperforge/commands/memory.py b/paperforge/commands/memory.py index 89e1941b..e07e3195 100644 --- a/paperforge/commands/memory.py +++ b/paperforge/commands/memory.py @@ -2,6 +2,7 @@ from __future__ import annotations import argparse import sys +from pathlib import Path from paperforge import __version__ as PF_VERSION from paperforge.core.errors import ErrorCode @@ -10,6 +11,143 @@ from paperforge.memory.builder import build_from_index from paperforge.memory.query import get_memory_status + +def _restore_backup(vault: Path) -> PFResult: + """Restore paperforge.db from a validated backup. + + Backup is staged to a temporary file then atomically installed via os.replace. + The current DB (if any) is copied to a timestamped corrupt-* snapshot before replacement. + The backup file is never modified — it remains in place. + + Steps: + 1. Validate backup with context-managed PRAGMA integrity_check + 2. Copy current DB to timestamped .corrupt-* snapshot (if exists) + 3. Copy backup to a unique same-directory temp file + 4. Atomic os.replace(temp, db_path) + 5. On any failure, current DB and backup remain unchanged; clean temp. + """ + import os + import shutil + import sqlite3 + import time + + from paperforge.memory.db import get_memory_db_path + + db_path = get_memory_db_path(vault) + backup_path = db_path.with_suffix(db_path.suffix + ".backup") + + if not backup_path.exists(): + return PFResult( + ok=False, + command="memory restore-backup", + version=PF_VERSION, + error=PFError( + code=ErrorCode.PATH_NOT_FOUND, + message=f"No backup found at {backup_path}", + ), + ) + + # ── Validate backup integrity (read-only, explicit close to release Windows locks) ── + try: + backup_uri = "file:" + backup_path.as_posix() + "?mode=ro" + conn = sqlite3.connect(backup_uri, uri=True) + row = conn.execute("PRAGMA integrity_check").fetchone() + conn.close() + if not row or row[0] != "ok": + return PFResult( + ok=False, + command="memory restore-backup", + version=PF_VERSION, + error=PFError( + code=ErrorCode.VALIDATION_ERROR, + message=f"Backup integrity check failed: {row[0] if row else 'no result'}", + ), + ) + except sqlite3.Error as e: + return PFResult( + ok=False, + command="memory restore-backup", + version=PF_VERSION, + error=PFError( + code=ErrorCode.VALIDATION_ERROR, + message=f"Cannot validate backup: {e}", + ), + ) + + # ── Staging: unique timestamp ── + ts = time.strftime("%Y%m%dT%H%M%S") + ns = str(int(time.time_ns() % 1_000_000_000)).zfill(9) # ponytail: nanosecond suffix for uniqueness + + # ── Preserve current DB via copy (never move) ── + corrupt_path = None + if db_path.exists(): + corrupt_path = db_path.with_name(f"paperforge.corrupt-{ts}-{ns}.db") + try: + shutil.copy2(str(db_path), str(corrupt_path)) + except OSError as e: + return PFResult( + ok=False, + command="memory restore-backup", + version=PF_VERSION, + error=PFError( + code=ErrorCode.INTERNAL_ERROR, + message=f"Failed to preserve current database: {e}", + ), + ) + + # ── Copy backup to unique same-directory temp ── + temp_path = db_path.with_name(f"paperforge.restore-{ts}-{ns}.tmp") + try: + shutil.copy2(str(backup_path), str(temp_path)) + # ponytail: copy2 already flushes; atomic replace is the real guarantee + except OSError as e: + # Clean temp on failure + if temp_path.exists(): + try: + temp_path.unlink() + except OSError: + pass + return PFResult( + ok=False, + command="memory restore-backup", + version=PF_VERSION, + error=PFError( + code=ErrorCode.INTERNAL_ERROR, + message=f"Failed to stage backup: {e}", + ), + ) + + # ── Atomic replace temp → db_path ── + try: + os.replace(str(temp_path), str(db_path)) + except OSError as e: + # Clean temp on failure + if temp_path.exists(): + try: + temp_path.unlink() + except OSError: + pass + return PFResult( + ok=False, + command="memory restore-backup", + version=PF_VERSION, + error=PFError( + code=ErrorCode.INTERNAL_ERROR, + message=f"Failed to restore backup: {e}", + ), + ) + + return PFResult( + ok=True, + command="memory restore-backup", + version=PF_VERSION, + data={ + "action": "restore_backup", + "preserved_as": str(corrupt_path) if corrupt_path else None, + }, + ) + + def run(args: argparse.Namespace) -> int: vault = args.vault_path sub_cmd = args.memory_subcommand @@ -61,45 +199,38 @@ def run(args: argparse.Namespace) -> int: if sub_cmd == "status": try: status = get_memory_status(vault) - # Write memory-runtime-state.json snapshot (JS-First Memory State) - try: - from paperforge.memory.db import get_connection, get_memory_db_path - from paperforge.memory.schema import get_schema_version - from paperforge.memory.state_snapshot import write_memory_runtime - _last_full_build = "" - _schema_ver_db = 0 - _fts_ok = False - _db_p = get_memory_db_path(vault) - if _db_p.exists(): - conn2 = get_connection(_db_p, read_only=True) - _row = conn2.execute( - "SELECT value FROM meta WHERE key = 'last_full_build_at'" - ).fetchone() - if _row: - _last_full_build = _row["value"] - _schema_ver_db = get_schema_version(conn2) - _fts_row = conn2.execute( - "SELECT name FROM sqlite_master WHERE type='table' AND name='paper_fts'" - ).fetchone() - _fts_ok = _fts_row is not None - conn2.close() - write_memory_runtime( - vault, - paper_count_db=status["paper_count_db"], - paper_count_index=status["paper_count_index"], - fresh=status["fresh"], - needs_rebuild=status["needs_rebuild"], - last_full_build_at=_last_full_build, - schema_version_db=_schema_ver_db, - fts_ready=_fts_ok, - ) - except Exception: - pass + from paperforge.memory.db import get_connection, get_memory_db_path + from paperforge.memory.schema import get_schema_version + from paperforge.memory.state_snapshot import write_memory_runtime + _last_full_build = "" + _schema_ver = None + _fts_ok = False + _db_p = get_memory_db_path(vault) + if _db_p.exists(): + conn2 = get_connection(_db_p, read_only=True) + _row = conn2.execute( + "SELECT value FROM meta WHERE key = 'last_full_build'" + ).fetchone() + _last_full_build = _row["value"] if _row else "" + _schema_ver = get_schema_version(conn2) + try: + conn2.execute("SELECT zotero_key FROM papers_fts LIMIT 1") + _fts_ok = True + except Exception: + _fts_ok = False + conn2.close() + write_memory_runtime(vault, status) + data = { + **status, + "last_full_build": _last_full_build, + "schema_version": _schema_ver, + "fts_ok": _fts_ok, + } result = PFResult( ok=True, command="memory status", version=PF_VERSION, - data=status, + data=data, ) except Exception as exc: result = PFResult( @@ -115,8 +246,22 @@ def run(args: argparse.Namespace) -> int: print(result.to_json()) else: if result.ok: - for k, v in status.items(): - print(f" {k}: {v}") + for k, v in (result.data or {}).items(): + print(f"{k}: {v}") + else: + print(f"Error: {result.error.message}", file=sys.stderr) + return 0 if result.ok else 1 + + if sub_cmd == "restore-backup": + result = _restore_backup(vault) + if args.json: + print(result.to_json()) + else: + if result.ok: + data = result.data or {} + print("Memory database restored from verified backup.") + if data.get("preserved_as"): + print(f"Corrupted database preserved as: {data['preserved_as']}") else: print(f"Error: {result.error.message}", file=sys.stderr) return 0 if result.ok else 1 diff --git a/paperforge/commands/probe.py b/paperforge/commands/probe.py index f67f1584..c213fa4e 100644 --- a/paperforge/commands/probe.py +++ b/paperforge/commands/probe.py @@ -1,14 +1,14 @@ -"""paperforge.commands.probe — Capability probe command (Issue #76, contract #69). +"""paperforge.commands.probe — Capability probe command (Issue #76, #78, contract #69). Emits direct schema-v1 capability envelopes (not PFResult-wrapped) for the -Obsidian plugin's six-module control center. Only Installation and Help have -real probes in this tracer; the remaining four modules (Library, OCR, Memory, -Maintenance) are explicit unknown placeholders. +Obsidian plugin's six-module control center. Probes use canonical sources: +paperforge_paths, load_vault_config, get_memory_status, collect_maintenance_rows. """ from __future__ import annotations import json +import os import sys from datetime import datetime, timezone from pathlib import Path @@ -20,6 +20,9 @@ from typing import Any SCHEMA_VERSION = 1 TTL_INSTALLATION = 3600 +TTL_LIBRARY = 300 +TTL_OCR = 60 +TTL_MEMORY = 300 TTL_HELP = 3600 MIN_PYTHON = (3, 11) LEGACY_PATH_KEYS = frozenset({ @@ -27,7 +30,7 @@ LEGACY_PATH_KEYS = frozenset({ "base_dir", "control_dir", "skill_dir", "command_dir", }) -SUPPORTED_MODULES = frozenset({"installation", "help"}) +SUPPORTED_MODULES = frozenset({"installation", "library", "ocr", "memory", "help"}) # --------------------------------------------------------------------------- @@ -35,75 +38,38 @@ SUPPORTED_MODULES = frozenset({"installation", "help"}) # --------------------------------------------------------------------------- def _utcnow_z() -> str: - """Return current UTC time as ISO 8601 with Z suffix (e.g. 2026-07-15T12:34:56Z).""" return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") def build_action_primary( - *, - verb: str, - label: str, - command: str, - destructive: bool = False, - destructive_scope: str | None = None, - destructive_effect: str | None = None, - confirmation_required: bool = False, - confirmation_prompt: str | None = None, - scope: str = "module", - scope_count: int = 1, + *, verb: str, label: str, command: str, + destructive: bool = False, destructive_scope: str | None = None, + destructive_effect: str | None = None, confirmation_required: bool = False, + confirmation_prompt: str | None = None, scope: str = "module", scope_count: int = 1, ) -> dict[str, Any]: - """Build a full action.primary dict per #69 contract fields.""" return { - "verb": verb, - "label": label, - "destructive": destructive, - "destructive_scope": destructive_scope, - "destructive_effect": destructive_effect, - "confirmation_required": confirmation_required, - "confirmation_prompt": confirmation_prompt, - "command": command, - "scope": scope, - "scope_count": scope_count, + "verb": verb, "label": label, "destructive": destructive, + "destructive_scope": destructive_scope, "destructive_effect": destructive_effect, + "confirmation_required": confirmation_required, "confirmation_prompt": confirmation_prompt, + "command": command, "scope": scope, "scope_count": scope_count, } def build_envelope( - *, - module: str, - capability_state: str, - severity: str, - reason_code: str, - reason_text: str, + *, module: str, capability_state: str, severity: str, + reason_code: str, reason_text: str, action_primary: dict[str, Any] | None = None, - activity_state: str = "idle", - activity_label: str | None = None, - activity_progress: float | None = None, - ttl_seconds: int = 3600, + activity_state: str = "idle", activity_label: str | None = None, + activity_progress: dict[str, int] | None = None, ttl_seconds: int = 3600, notices: list[dict[str, Any]] | None = None, ) -> dict[str, Any]: - """Build a schema-v1 capability envelope. - - Required fields per #69 contract: - schema_version, module, capability_state, activity_state, activity_label, - activity_progress, severity, reason{code,text}, action{primary}, notices, - updated_at, ttl_seconds. - """ return { - "schema_version": SCHEMA_VERSION, - "module": module, - "capability_state": capability_state, - "activity_state": activity_state, - "activity_label": activity_label, - "activity_progress": activity_progress, - "severity": severity, - "reason": { - "code": reason_code, - "text": reason_text, - }, - "action": {"primary": action_primary}, - "notices": notices or [], - "updated_at": _utcnow_z(), - "ttl_seconds": ttl_seconds, + "schema_version": SCHEMA_VERSION, "module": module, + "capability_state": capability_state, "activity_state": activity_state, + "activity_label": activity_label, "activity_progress": activity_progress, + "severity": severity, "reason": {"code": reason_code, "text": reason_text}, + "action": {"primary": action_primary}, "notices": notices or [], + "updated_at": _utcnow_z(), "ttl_seconds": ttl_seconds, } @@ -112,17 +78,6 @@ def build_envelope( # --------------------------------------------------------------------------- def _is_recognizable_config(data: Any) -> bool: - """Return True if *data* looks like a recognizable PaperForge config. - - Accepts: - - dict with a ``vault_config`` key (v2 format) - - dict with at least one legacy path key (pre-migration format) - - Rejects: - - non-dict types (list, str, number, bool) - - empty dict ``{}`` - - dict with keys but neither ``vault_config`` nor any legacy path key - """ if not isinstance(data, dict) or len(data) == 0: return False if "vault_config" in data: @@ -130,138 +85,578 @@ def _is_recognizable_config(data: Any) -> bool: return any(k in data for k in LEGACY_PATH_KEYS) +def _load_pf_config(vault: Path) -> tuple[dict[str, Any] | None, str | None]: + """Read paperforge.json. Returns (data, error) tuple. + data=None, error=None → file does not exist. + data=None, error='corrupt' → file exists but is invalid. + data=dict, error=None → valid config. + """ + pf_json = vault / "paperforge.json" + if not pf_json.exists(): + return None, None + try: + raw = pf_json.read_text(encoding="utf-8") + data = json.loads(raw) + except (json.JSONDecodeError, OSError): + return None, "corrupt" + if not _is_recognizable_config(data): + return None, "corrupt" + return data, None + + # --------------------------------------------------------------------------- # Probes # --------------------------------------------------------------------------- def probe_installation(vault: Path) -> dict[str, Any]: - """Probe the Installation module. - - Read-only probe that checks: - 1. paperforge.json exists - 2. paperforge.json is valid and recognizable config - 3. Current Python >= 3.11 - 4. Everything OK → ready - - Does NOT search for ambient Python, spawn interpreters, or instantiate - SetupPlan (that is #77's domain). - """ pf_json = vault / "paperforge.json" - # 1. Missing config if not pf_json.exists(): return build_envelope( - module="installation", - capability_state="missing_input", - severity="warning", - reason_code="installation.config_missing", - reason_text="paperforge.json not found in vault", - action_primary=build_action_primary( - verb="set_config", - label="Set config", - command="paperforge setup", - ), + module="installation", capability_state="missing_input", severity="warning", + reason_code="installation.config_missing", reason_text="paperforge.json not found in vault", + action_primary=build_action_primary(verb="set_config", label="Set config", command="paperforge setup"), ttl_seconds=TTL_INSTALLATION, ) - # 2. Parse JSON try: data = json.loads(pf_json.read_text(encoding="utf-8")) except (json.JSONDecodeError, OSError): return build_envelope( - module="installation", - capability_state="unavailable", - severity="error", - reason_code="installation.config_corrupt", - reason_text="paperforge.json is corrupt or invalid", - action_primary=build_action_primary( - verb="setup", - label="Setup", - command="paperforge setup", - ), + module="installation", capability_state="unavailable", severity="error", + reason_code="installation.config_corrupt", reason_text="paperforge.json is corrupt or invalid", + action_primary=build_action_primary(verb="setup", label="Setup", command="paperforge setup"), ttl_seconds=TTL_INSTALLATION, ) - # 2b. Validate config shape — list, primitive, empty, or unrecognized if not _is_recognizable_config(data): return build_envelope( - module="installation", - capability_state="unavailable", - severity="error", - reason_code="installation.config_corrupt", - reason_text="paperforge.json has unrecognizable structure", - action_primary=build_action_primary( - verb="setup", - label="Setup", - command="paperforge setup", - ), + module="installation", capability_state="unavailable", severity="error", + reason_code="installation.config_corrupt", reason_text="paperforge.json has unrecognizable structure", + action_primary=build_action_primary(verb="setup", label="Setup", command="paperforge setup"), ttl_seconds=TTL_INSTALLATION, ) - # 3. Python version check py_version = sys.version_info[:2] if py_version < MIN_PYTHON: return build_envelope( - module="installation", - capability_state="limited", - severity="warning", + module="installation", capability_state="limited", severity="warning", reason_code="installation.python_version_unsupported", - reason_text=( - f"Python {py_version[0]}.{py_version[1]} < " - f"{MIN_PYTHON[0]}.{MIN_PYTHON[1]}" - ), - action_primary=build_action_primary( - verb="update", - label="Update Python", - command="", - ), + reason_text=f"Python {py_version[0]}.{py_version[1]} < {MIN_PYTHON[0]}.{MIN_PYTHON[1]}", + action_primary=build_action_primary(verb="update", label="Update Python", command=""), ttl_seconds=TTL_INSTALLATION, ) - # 4. All good return build_envelope( - module="installation", - capability_state="ready", - severity="ok", - reason_code="installation.ready", - reason_text="PaperForge is installed and configured", - action_primary=None, - ttl_seconds=TTL_INSTALLATION, + module="installation", capability_state="ready", severity="ok", + reason_code="installation.ready", reason_text="PaperForge is installed and configured", + action_primary=None, ttl_seconds=TTL_INSTALLATION, ) def probe_help(vault: Path) -> dict[str, Any]: # noqa: ARG001 - """Probe the Help module. - - Checks whether the packaged PaperForge help/skill source exists so that - first-run help can be ready immediately. - """ skills_dir = Path(__file__).resolve().parent.parent / "skills" / "paperforge" skill_md = skills_dir / "SKILL.md" if skill_md.exists(): return build_envelope( - module="help", - capability_state="ready", - severity="ok", - reason_code="help.ready", - reason_text="Help and skill documentation available", - action_primary=None, - ttl_seconds=TTL_HELP, + module="help", capability_state="ready", severity="ok", + reason_code="help.ready", reason_text="Help and skill documentation available", + action_primary=None, ttl_seconds=TTL_HELP, + ) + return build_envelope( + module="help", capability_state="limited", severity="warning", + reason_code="help.docs_missing", reason_text="Packaged help source not found", + action_primary=build_action_primary(verb="setup", label="Restore help", command="paperforge setup"), + ttl_seconds=TTL_HELP, + ) + + +def probe_library(vault: Path, last_operation_exit_code: int | None = None) -> dict[str, Any]: + """Probe the Library module using canonical sources. + + When last_operation_exit_code is non-zero, the probe reports a sync failure + envelope after validating config/path, skipping index freshness checks. + """ + data, err = _load_pf_config(vault) + if data is None: + if err == "corrupt": + return build_envelope( + module="library", capability_state="unavailable", severity="error", + reason_code="library.config_corrupt", + reason_text="paperforge.json is corrupt — library sync cannot proceed", + action_primary=build_action_primary(verb="setup", label="Setup", command="paperforge setup"), + ttl_seconds=TTL_LIBRARY, + ) + return build_envelope( + module="library", capability_state="missing_input", severity="warning", + reason_code="library.config_missing", + reason_text="paperforge.json not found — cannot check library configuration", + action_primary=build_action_primary(verb="set_config", label="Set config", command="paperforge setup"), + ttl_seconds=TTL_LIBRARY, ) + # ── zotero_data_dir check ── + vault_cfg = data.get("vault_config", {}) if isinstance(data.get("vault_config"), dict) else {} + zotero_dir = vault_cfg.get("zotero_data_dir") or data.get("zotero_data_dir", "") + zotero_dir = (zotero_dir or "").strip() + + if not zotero_dir: + return build_envelope( + module="library", capability_state="missing_input", severity="warning", + reason_code="library.zotero_missing", reason_text="Zotero data directory not configured", + action_primary=build_action_primary(verb="set_config", label="Configure Zotero", command="paperforge setup"), + ttl_seconds=TTL_LIBRARY, + ) + + zotero_path = Path(zotero_dir) + if not zotero_path.is_absolute(): + zotero_path = (vault / zotero_dir).resolve() + if not zotero_path.exists(): + return build_envelope( + module="library", capability_state="missing_input", severity="error", + reason_code="library.zotero_not_found", + reason_text=f"Zotero data directory not found: {zotero_dir}", + action_primary=build_action_primary(verb="set_config", label="Configure Zotero", command="paperforge setup"), + ttl_seconds=TTL_LIBRARY, + ) + + # ── Sync failure probe: backend-owned envelope after failed manual sync ── + if last_operation_exit_code and last_operation_exit_code != 0: + return build_envelope( + module="library", capability_state="needs_action", severity="error", + reason_code="library.sync_failed", + reason_text=f"Library sync failed (exit code {last_operation_exit_code}) — re-run or check logs", + action_primary=build_action_primary(verb="sync", label="Sync library", command="paperforge sync"), + ttl_seconds=TTL_LIBRARY, + ) + + # ── Canonical index check via paperforge_paths ── + from paperforge.config import paperforge_paths + from paperforge.memory.db import get_memory_db_path + paths = paperforge_paths(vault) + index_path = paths.get("index") # canonical formal-library.json + + if index_path is None or not index_path.exists(): + return build_envelope( + module="library", capability_state="needs_action", severity="warning", + reason_code="library.index_missing", reason_text="Canonical index has not been built — run sync", + action_primary=build_action_primary(verb="sync", label="Sync library", command="paperforge sync"), + ttl_seconds=TTL_LIBRARY, + ) + + # ── Index freshness: canonical export hash (Issue #78) ── + # real asset_index.build_envelope writes "export_hash", not "canonical_index_hash" + notices: list[dict[str, Any]] = [] + try: + from paperforge.worker.asset_index import read_index, _compute_export_hash + envelope = read_index(vault) + if envelope is not None and isinstance(envelope, dict): + stored_hash = envelope.get("export_hash", "") + current_hash = _compute_export_hash(paths) + paper_count = envelope.get("paper_count", 0) + + if not stored_hash or stored_hash != current_hash: + return build_envelope( + module="library", capability_state="needs_action", severity="warning", + reason_code="library.index_stale", + reason_text=f"Library index is stale ({paper_count} papers — export files changed since last sync)", + action_primary=build_action_primary(verb="sync", label="Sync library", command="paperforge sync"), + ttl_seconds=TTL_LIBRARY, + ) + + # Cross-validate DB canonical_index_hash + try: + import sqlite3 as _sqlite3 + items = envelope.get("items", []) + from paperforge.memory.builder import compute_hash + index_hash = compute_hash(items) + db_path = get_memory_db_path(vault) + if db_path.exists(): + conn = _sqlite3.connect(str(db_path)) + try: + row = conn.execute("SELECT value FROM meta WHERE key = 'canonical_index_hash'").fetchone() + db_stored = row[0] if row else "" + if db_stored and db_stored != index_hash: + notices.append({"level": "warning", "message": "DB canonical_index_hash is out of sync with index"}) + except Exception: + pass + finally: + conn.close() + except Exception: + pass + # Export hash valid and index healthy → ready + return build_envelope( + module="library", capability_state="ready", severity="ok", + reason_code="library.ready", + reason_text=f"Library synced and index is fresh ({paper_count} papers)", + notices=notices, action_primary=None, ttl_seconds=TTL_LIBRARY, + ) + elif envelope is not None: + # Legacy list format + return build_envelope( + module="library", capability_state="needs_action", severity="warning", + reason_code="library.index_legacy", reason_text="Canonical index is in legacy format — run sync to migrate", + action_primary=build_action_primary(verb="sync", label="Sync library", command="paperforge sync"), + ttl_seconds=TTL_LIBRARY, + ) + else: + return build_envelope( + module="library", capability_state="needs_action", severity="warning", + reason_code="library.index_missing", reason_text="Canonical index is empty — run sync", + action_primary=build_action_primary(verb="sync", label="Sync library", command="paperforge sync"), + ttl_seconds=TTL_LIBRARY, + ) + except Exception: + # Validation exception → never fall through to ready; report unknown + return build_envelope( + module="library", capability_state="unknown", severity="unknown", + reason_code="library.index_validation_failed", + reason_text="Library index validation failed — probe to retry", + action_primary=build_action_primary(verb="probe", label="Re-check", command="probe library"), + ttl_seconds=TTL_LIBRARY, + ) + + +def probe_ocr(vault: Path) -> dict[str, Any]: + """Probe the OCR module using canonical maintenance rows and env config.""" + data, err = _load_pf_config(vault) + if data is None: + if err == "corrupt": + return build_envelope( + module="ocr", capability_state="unavailable", severity="error", + reason_code="ocr.config_corrupt", reason_text="paperforge.json is corrupt — OCR cannot proceed", + action_primary=build_action_primary(verb="setup", label="Setup", command="paperforge setup"), + ttl_seconds=TTL_OCR, + ) + return build_envelope( + module="ocr", capability_state="missing_input", severity="warning", + reason_code="ocr.config_missing", reason_text="paperforge.json not found — cannot check OCR configuration", + action_primary=build_action_primary(verb="set_config", label="Set config", command="paperforge setup"), + ttl_seconds=TTL_OCR, + ) + + # ── API key / env check — canonical _resolve_paddleocr_token ── + from paperforge.worker.ocr import _resolve_paddleocr_token + token = _resolve_paddleocr_token(vault) + + if not token: + return build_envelope( + module="ocr", capability_state="missing_input", severity="warning", + reason_code="ocr.api_key_missing", + reason_text="PADDLEOCR_API_TOKEN not found in environment — configure API key", + action_primary=build_action_primary(verb="set_config", label="Configure API key", command="paperforge setup"), + ttl_seconds=TTL_OCR, + ) + + # ── Provider reachability via ocr_doctor(config=None, live=False) ── + notices: list[dict[str, Any]] = [] + provider_reachable = True + try: + from paperforge.ocr_diagnostics import ocr_doctor + diag = ocr_doctor(config=None, live=False) + provider_reachable = diag.get("passed", False) + if not provider_reachable: + notices.append({"level": "warning", "message": f"PaddleOCR API unreachable: {diag.get('error', 'unknown error')}"}) + except Exception: + provider_reachable = False + notices.append({"level": "warning", "message": "PaddleOCR API diagnostics unavailable"}) + + # ── Canonical maintenance rows ── + try: + from paperforge.worker.ocr_maintenance import collect_maintenance_rows + rows = collect_maintenance_rows(vault) + except Exception: + return build_envelope( + module="ocr", capability_state="unknown", severity="unknown", + reason_code="ocr.probe_failed", reason_text="OCR maintenance check failed — probe to retry", + action_primary=build_action_primary(verb="probe", label="Probe", command="probe ocr"), + notices=notices, ttl_seconds=TTL_OCR, + ) + + if not rows: + return build_envelope( + module="ocr", capability_state="needs_action", severity="warning", + reason_code="ocr.artifacts_missing", reason_text="No OCR output found — run OCR to process papers", + action_primary=build_action_primary(verb="run", label="Run OCR", command="paperforge ocr run"), + notices=notices, ttl_seconds=TTL_OCR, + ) + + # ── Running/active rows → activity overlay (Issue #78 fix) ── + # Detect statuses running|processing|queued, emit activity_state='running', + # label and {current,total} progress while preserving independently computed + # availability/severity/reason/action. + # current = terminal/completed rows (done family), not queued/running/processing + TERMINAL_STATUSES = frozenset({'done', 'done_degraded'}) + ACTIVE_STATUSES = frozenset({'running', 'processing', 'queued'}) + running_rows = [r for r in rows if getattr(r, 'status', '') in ACTIVE_STATUSES] + completed_count = sum(1 for r in rows if getattr(r, 'status', '') in TERMINAL_STATUSES) + total = len(rows) + act_state = "running" if running_rows else "idle" + if running_rows: + act_label = f"OCR processing ({completed_count}/{total})" + act_progress = {"current": completed_count, "total": total} + else: + act_label = None + act_progress = None + + # ── Determine state from maintenance rows using canonical display_action ── + # Known display_action values: retry_ocr, upgrade_legacy, rebuild_result, none, run_ocr + # derive redo only from retry_ocr|upgrade_legacy (legacy can_redo fallback only when display_action is absent) + # rebuild from rebuild_result + # unexpected actionable only when display_severity=='actionable' and action is not known + KNOWN_ACTIONS = frozenset({'retry_ocr', 'upgrade_legacy', 'rebuild_result', 'run_ocr', 'none'}) + + # has_failed: status=failed, health=red, or explicit redo display_action. + # Contract: display_action='rebuild_result' must never become redo even when health is red. + has_failed = any( + (r.status == "failed" or r.health == "red" or + getattr(r, 'display_action', '') in ('retry_ocr', 'upgrade_legacy')) + and getattr(r, 'display_action', '') != 'rebuild_result' + for r in rows + ) + has_redo = any( + getattr(r, 'display_action', '') in ('retry_ocr', 'upgrade_legacy') or + (not hasattr(r, 'display_action') and ( + getattr(r, 'recommended_action', '') == 'redo' or getattr(r, 'can_redo', False) + )) + for r in rows + ) + has_degraded = any( + getattr(r, 'display_action', '') == 'rebuild_result' + for r in rows + ) + has_unexpected = any( + hasattr(r, 'display_severity') and r.display_severity == 'actionable' + and hasattr(r, 'display_action') and r.display_action + and r.display_action not in KNOWN_ACTIONS + for r in rows + ) + + + + # ── Priority: redo > run > rebuild_derived > investigate ── + + # Failures → needs_action with redo + if has_failed: + return build_envelope( + module="ocr", capability_state="needs_action", severity="warning", + reason_code="ocr.quality_failures", + reason_text="Some OCR outputs have failed — redo required", + action_primary=build_action_primary( + verb="redo", label="Redo OCR", command="paperforge ocr redo", + destructive=True, destructive_scope="selection", + destructive_effect="Deletes existing OCR derived artifacts for failed papers and re-runs OCR from raw images. Raw images and PDFs are preserved.", + confirmation_required=True, + confirmation_prompt="This will delete existing OCR output for failed papers and re-run OCR. This cannot be undone. Proceed?", + ), + activity_state=act_state, activity_label=act_label, activity_progress=act_progress, + notices=notices, ttl_seconds=TTL_OCR, + ) + + # Redo candidates → needs_action + if has_redo: + return build_envelope( + module="ocr", capability_state="needs_action", severity="warning", + reason_code="ocr.redo_needed", + reason_text="Some OCR outputs need redo — re-run from raw images", + action_primary=build_action_primary( + verb="redo", label="Redo OCR", command="paperforge ocr redo", + destructive=True, destructive_scope="selection", + destructive_effect="Deletes existing OCR derived artifacts for selected papers and re-runs OCR from raw images. Raw images and PDFs are preserved.", + confirmation_required=True, + confirmation_prompt="This will delete existing OCR output for selected papers and re-run OCR. This cannot be undone. Proceed?", + ), + activity_state=act_state, activity_label=act_label, activity_progress=act_progress, + notices=notices, ttl_seconds=TTL_OCR, + ) + + # Pending rows → run action (run before rebuild/investigate) + # Exclude queued/running — those are already captured as activity + has_pending = any( + getattr(r, 'status', '') == 'pending' or getattr(r, 'display_action', '') == 'run_ocr' + for r in rows + ) + if has_pending: + return build_envelope( + module="ocr", capability_state="needs_action", severity="warning", + reason_code="ocr.pending", + reason_text=f"OCR is pending for {total} papers — run to process", + action_primary=build_action_primary( + verb="run", label="Run OCR", command="paperforge ocr run", + ), + activity_state=act_state, activity_label=act_label, activity_progress=act_progress, + notices=notices, ttl_seconds=TTL_OCR, + ) + + # Degraded → needs_action with rebuild + if has_degraded: + return build_envelope( + module="ocr", capability_state="needs_action", severity="warning", + reason_code="ocr.artifacts_stale", + reason_text="Derived OCR artifacts are degraded — rebuild to refresh", + action_primary=build_action_primary( + verb="rebuild_derived", label="Rebuild derived artifacts", + command="paperforge ocr rebuild --all", + destructive=False, + ), + activity_state=act_state, activity_label=act_label, activity_progress=act_progress, + notices=notices, ttl_seconds=TTL_OCR, + ) + + # Unexpected actionable → investigate (lowest priority before provider) + if has_unexpected: + return build_envelope( + module="ocr", capability_state="limited", severity="warning", + reason_code="ocr.unexpected_action", + reason_text="OCR maintenance reports unexpected actions — run diagnostics", + action_primary=build_action_primary( + verb="investigate", label="Run diagnostics", command="paperforge ocr doctor", + ), + activity_state=act_state, activity_label=act_label, activity_progress=act_progress, + notices=notices, ttl_seconds=TTL_OCR, + ) + + # Provider unreachable → limited + investigate (last resort before ready) + if not provider_reachable: + return build_envelope( + module="ocr", capability_state="limited", severity="warning", + reason_code="ocr.api_unreachable", + reason_text="PaddleOCR API is unreachable — OCR jobs may fail. Local output remains available.", + action_primary=build_action_primary( + verb="investigate", label="Run diagnostics", command="paperforge ocr doctor", + ), + activity_state=act_state, activity_label=act_label, activity_progress=act_progress, + notices=notices, ttl_seconds=TTL_OCR, + ) + + # All good — ready only when no failures/redo/pending/degraded/unexpected and provider reachable return build_envelope( - module="help", - capability_state="limited", - severity="warning", - reason_code="help.docs_missing", - reason_text="Packaged help source not found", - action_primary=build_action_primary( - verb="setup", - label="Restore help", - command="paperforge setup", - ), - ttl_seconds=TTL_HELP, + module="ocr", capability_state="ready", severity="ok", + reason_code="ocr.ready", reason_text=f"OCR pipeline functional ({total} papers processed)", + activity_state=act_state, activity_label=act_label, activity_progress=act_progress, + notices=notices, action_primary=None, ttl_seconds=TTL_OCR, + ) + + +def probe_memory(vault: Path) -> dict[str, Any]: + """Probe the Memory module using canonical get_memory_status.""" + try: + from paperforge.memory.query import get_memory_status + from paperforge.memory.schema import CURRENT_SCHEMA_VERSION as _CURRENT_SCHEMA + status = get_memory_status(vault) + except Exception: + return build_envelope( + module="memory", capability_state="unknown", severity="unknown", + reason_code="memory.probe_failed", reason_text="Memory status check failed — probe to retry", + action_primary=build_action_primary(verb="probe", label="Re-check", command="probe memory"), + ttl_seconds=TTL_MEMORY, + ) + + db_exists: bool = status.get("db_exists", False) + schema_ok: bool = status.get("schema_ok", False) + fresh: bool = status.get("fresh", False) + paper_count_db: int = status.get("paper_count_db", 0) + paper_count_index: int = status.get("paper_count_index", 0) + + if not db_exists: + return build_envelope( + module="memory", capability_state="needs_action", severity="warning", + reason_code="memory.db_missing", reason_text="Memory database has not been built yet", + action_primary=build_action_primary(verb="run", label="Build memory", command="paperforge memory build"), + ttl_seconds=TTL_MEMORY, + ) + + # Schema check failed + if not schema_ok: + if paper_count_db > 0: + # DB has papers but old schema → needs rebuild (CLI memory only has build/status) + return build_envelope( + module="memory", capability_state="needs_action", severity="warning", + reason_code="memory.migration_needed", + reason_text="Memory database schema version is outdated — rebuild to update", + action_primary=build_action_primary( + verb="rebuild_index", label="Rebuild database", command="paperforge memory build", + destructive=False, + ), + ttl_seconds=TTL_MEMORY, + ) + # DB exists but empty/unreadable — rebuild or restore from backup + from paperforge.config import paperforge_paths + paths = paperforge_paths(vault) + backup_exists = (paths["paperforge"] / "indexes" / "paperforge.db.backup").exists() + notices: list[dict[str, Any]] = [] + if backup_exists: + return build_envelope( + module="memory", capability_state="unavailable", severity="error", + reason_code="memory.db_corrupt", + reason_text="Memory database is corrupted — a verified backup is available for restore", + action_primary=build_action_primary( + verb="restore_backup", label="Restore from backup", command="paperforge memory restore-backup", + destructive=True, destructive_scope="module", + destructive_effect="Replaces the current corrupted database with the verified backup. The corrupted database will be preserved as a timestamped snapshot.", + confirmation_required=True, + confirmation_prompt="This will replace the memory database with the backup copy. The corrupted database will be preserved. Proceed?", + ), + notices=notices, ttl_seconds=TTL_MEMORY, + ) + return build_envelope( + module="memory", capability_state="unavailable", severity="error", + reason_code="memory.db_corrupt", + reason_text="Memory database is corrupted or uninitialized — rebuild required (source data preserved)", + action_primary=build_action_primary( + verb="run", label="Rebuild memory", command="paperforge memory build", + destructive=False, + ), + notices=notices, ttl_seconds=TTL_MEMORY, + ) + + # Stale: schema OK but index hash doesn't match + if not fresh: + return build_envelope( + module="memory", capability_state="needs_action", severity="warning", + reason_code="memory.index_stale", + reason_text=f"Memory index needs rebuild (DB: {paper_count_db} papers, Index: {paper_count_index} papers)", + action_primary=build_action_primary( + verb="rebuild_index", label="Rebuild index", command="paperforge memory build", + destructive=False, + ), + ttl_seconds=TTL_MEMORY, + ) + # ── Vector index health via get_embed_status ── + notices: list[dict[str, Any]] = [] + try: + from paperforge.embedding.status import get_embed_status + embed_status = get_embed_status(vault) + vector_healthy = embed_status.get("healthy", False) and embed_status.get("total_chunks", 0) > 0 + if not vector_healthy and paper_count_db > 0: + return build_envelope( + module="memory", capability_state="needs_action", severity="warning", + reason_code="memory.index_stale", + reason_text="Vector index is missing or corrupted — rebuild to enable semantic search", + action_primary=build_action_primary( + verb="rebuild_index", label="Build vector index", + command="paperforge embed build --force", + destructive=False, + ), + notices=notices, ttl_seconds=TTL_MEMORY, + ) + except Exception: + # Vector check exception → never ready; report unknown + return build_envelope( + module="memory", capability_state="unknown", severity="unknown", + reason_code="memory.vector_probe_failed", + reason_text="Vector index health check failed — probe to retry", + action_primary=build_action_primary(verb="probe", label="Re-check", command="probe memory"), + notices=notices, ttl_seconds=TTL_MEMORY, + ) + + # DB healthy and vector index present → ready + return build_envelope( + module="memory", capability_state="ready", severity="ok", + reason_code="memory.ready", + reason_text=f"Memory database healthy ({paper_count_db} papers, {paper_count_index} indexed)", + notices=notices, action_primary=None, ttl_seconds=TTL_MEMORY, ) @@ -270,15 +665,18 @@ def probe_help(vault: Path) -> dict[str, Any]: # noqa: ARG001 # --------------------------------------------------------------------------- def run(args: Any) -> int: - """Dispatch a probe command and print the envelope. - - Called from paperforge.cli.main(). - """ vault: Path = args.vault_path module: str = args.probe_module if module == "installation": envelope = probe_installation(vault) + elif module == "library": + last_code: int | None = getattr(args, "last_operation_exit_code", None) + envelope = probe_library(vault, last_operation_exit_code=last_code) + elif module == "ocr": + envelope = probe_ocr(vault) + elif module == "memory": + envelope = probe_memory(vault) elif module == "help": envelope = probe_help(vault) else: @@ -286,7 +684,6 @@ def run(args: Any) -> int: return 1 json_output: bool = getattr(args, "json", False) - if json_output: print(json.dumps(envelope, indent=2, ensure_ascii=False)) else: diff --git a/paperforge/plugin/main.js b/paperforge/plugin/main.js index c24fd51e..dba8ef02 100644 --- a/paperforge/plugin/main.js +++ b/paperforge/plugin/main.js @@ -1,12 +1,12 @@ -"use strict";var cr=Object.create;var We=Object.defineProperty;var pr=Object.getOwnPropertyDescriptor;var dr=Object.getOwnPropertyNames;var ur=Object.getPrototypeOf,_r=Object.prototype.hasOwnProperty;var hr=(d,p)=>()=>(p||d((p={exports:{}}).exports,p),p.exports),fr=(d,p)=>{for(var e in p)We(d,e,{get:p[e],enumerable:!0})},Ft=(d,p,e,t)=>{if(p&&typeof p=="object"||typeof p=="function")for(let r of dr(p))!_r.call(d,r)&&r!==e&&We(d,r,{get:()=>p[r],enumerable:!(t=pr(p,r))||t.enumerable});return d};var K=(d,p,e)=>(e=d!=null?cr(ur(d)):{},Ft(p||!d||!d.__esModule?We(e,"default",{value:d,enumerable:!0}):e,d)),gr=d=>Ft(We({},"__esModule",{value:!0}),d);var ht=hr((Mr,xr)=>{xr.exports={versions:[{version:"1.5.15",date:"2026-06-01",title:"\u5168\u6587\u5B58\u50A8\u91CD\u6784 + OCR \u9605\u8BFB\u987A\u5E8F\u4FEE\u590D + Redo \u4E00\u952E\u91CD\u505A",breaking_or_migration:["\u5168\u6587\u6587\u4EF6\u73B0\u5728\u7EDF\u4E00\u5B58\u653E\u4E8E System/PaperForge/ocr/ \u4E0B\uFF0C\u4E0D\u518D\u5728\u5DE5\u4F5C\u533A\u4FDD\u7559\u526F\u672C","Redo OCR \u73B0\u5728\u4F1A\u7ACB\u5373\u6267\u884C\uFF08\u4E00\u952E\u5B8C\u6210\uFF09\uFF0C\u4E0D\u518D\u9700\u8981\u624B\u52A8\u518D\u8DD1\u4E00\u6B21"],new_features:["Redo OCR \u4E00\u952E\u95ED\u73AF\uFF1A\u52FE\u9009 \u2192 \u70B9\u6309\u94AE \u2192 \u81EA\u52A8\u5B8C\u6210\u5168\u90E8\u6D41\u7A0B","\u8BBE\u7F6E\u9875\u65B0\u589E\u300C\u66F4\u65B0\u4E0E\u624B\u518C\u300D\u6807\u7B7E\u9875\uFF0C\u53EF\u968F\u65F6\u67E5\u770B\u7248\u672C\u66F4\u65B0\u8BB0\u5F55\u548C\u4F7F\u7528\u624B\u518C","\u63D2\u4EF6\u66F4\u65B0\u540E\u81EA\u52A8\u5F39\u51FA\u66F4\u65B0\u8BF4\u660E"],fixes:["\u4FEE\u590D\u5168\u6587\u9605\u8BFB\u987A\u5E8F\u6DF7\u4E71\uFF0C\u4F18\u5316\u6574\u4F53\u6392\u7248\u4F53\u9A8C","\u4FEE\u590D\u7AE0\u8282\u6807\u9898\u548C\u6B63\u6587\u6BB5\u843D\u9519\u4F4D\u65AD\u5F00\u7684\u95EE\u9898","\u4FEE\u590D\u56FE\u8868\u548C\u5BF9\u5E94\u56FE\u6CE8\u88AB\u5206\u5F00\u7684\u95EE\u9898","\u4FEE\u590D\u9996\u9875\u6458\u8981\u533A\u5757\u6392\u5E8F\u5F02\u5E38","\u4FEE\u590D\u5E76\u6392\u56FE\u7247\u672A\u80FD\u81EA\u52A8\u5408\u5E76\u7684\u95EE\u9898","Dashboard \u73B0\u5728\u80FD\u6B63\u786E\u8BC6\u522B\u65B0\u7684\u5168\u6587\u6587\u4EF6\u4F4D\u7F6E"],recommended_actions:["\u65E7\u7248 OCR \u5168\u6587\u53EF\u80FD\u5B58\u5728\u9605\u8BFB\u987A\u5E8F\u95EE\u9898\uFF0C\u5EFA\u8BAE\u5BF9\u91CD\u8981\u8BBA\u6587\u6267\u884C\u4E00\u6B21 Redo OCR","\u6253\u5F00\u5168\u6587\u8BF7\u76F4\u63A5\u4F7F\u7528 Dashboard \u7684\u300C\u6253\u5F00\u5168\u6587\u300D\u6309\u94AE"]}]}});var Dr={};fr(Dr,{default:()=>it});module.exports=gr(Dr);var Y=require("obsidian"),q=K(require("fs")),Me=K(require("path")),ge=require("child_process");var Pe="paperforge-status",Ne="paperforge",Tt='',ie=[{id:"paperforge-sync",title:"Sync Library",desc:"Pull new references from Zotero and generate literature notes",icon:"\u21BB",cmd:"sync",okMsg:"Sync complete"},{id:"paperforge-ocr",title:"Run OCR",desc:"Extract full text and figures from PDFs via PaddleOCR",icon:"\u229E",cmd:"ocr",okMsg:"OCR started"},{id:"paperforge-doctor",title:"Run Doctor",desc:"Verify PaperForge setup \u2014 check configs, Zotero, paths, and index health",icon:"\u2695",cmd:"doctor",okMsg:"Doctor complete"},{id:"paperforge-repair",title:"Repair Issues",desc:"Fix three-way state divergence, path errors, and rebuild index",icon:"\u21BA",cmd:"repair",args:["--fix","--fix-paths"],okMsg:"Repair complete"},{id:"paperforge-ocr-redo",title:"Redo OCR",desc:"Re-run OCR for papers marked ocr_redo: true",icon:"\u21BA",cmd:"ocr",args:["redo"],okMsg:"OCR redo started"}],Ve={vault_path:"",setup_complete:!1,auto_update:!0,auto_update_on_startup:!0,agent_platform:"opencode",language:"",paddleocr_api_key:"",zotero_data_dir:"",python_path:"",features:{memory_layer:!0,vector_db:!1},selected_skill_platform:"opencode",vector_db_api_key:"",vector_db_api_base:"",vector_db_api_model:"text-embedding-3-small",frozen_skills:{},system_dir:"",resources_dir:"",literature_dir:"",base_dir:"",capabilityState:{},last_seen_version:""};function At(d,p){if(!p||!p.note_path)return p;let e=d.vault.getAbstractFileByPath(p.note_path);if(!e)return p;let t=d.metadataCache.getFileCache(e),r=t&&t.frontmatter;if(!r)return p;let n={...p};for(let a of["do_ocr","analyze","ocr_status","ocr_redo","deep_reading_status"])Object.prototype.hasOwnProperty.call(r,a)&&(n[a]=r[a]);return n}function ot(d,p){return d&&{...d,...p}}var lt=1,Ce=["installation","library","ocr","memory","maintenance","help"],mr=["unknown","unavailable","missing_input","needs_action","limited","ready"],yr=["unknown","ok","warning","error"],vr=["idle","running"];function br(d){if(!d||typeof d!="object"||Array.isArray(d))return!1;let p=d;return!(typeof p.verb!="string"||typeof p.label!="string"||typeof p.destructive!="boolean"||p.destructive_scope!==null&&typeof p.destructive_scope!="string"||p.destructive_effect!==null&&typeof p.destructive_effect!="string"||typeof p.confirmation_required!="boolean"||p.confirmation_prompt!==null&&typeof p.confirmation_prompt!="string"||typeof p.command!="string"||typeof p.scope!="string"||typeof p.scope_count!="number")}function $e(d){return{verb:"probe",label:"Check",destructive:!1,destructive_scope:null,destructive_effect:null,confirmation_required:!1,confirmation_prompt:null,command:`probe ${d}`,scope:d,scope_count:1}}function Dt(){return{verb:"setup",label:"Open Setup Wizard",destructive:!1,destructive_scope:null,destructive_effect:null,confirmation_required:!1,confirmation_prompt:null,command:"setup",scope:"installation",scope_count:1}}function ct(d,p){if(!d||typeof d!="object")return!1;let e=d;if(e.schema_version!==1||typeof e.module!="string"||!e.module||!Ce.includes(e.module)||p!==void 0&&e.module!==p||typeof e.capability_state!="string"||!mr.includes(e.capability_state)||typeof e.severity!="string"||!yr.includes(e.severity)||typeof e.activity_state!="string"||!vr.includes(e.activity_state)||e.activity_label!==null&&typeof e.activity_label!="string")return!1;if(e.activity_progress!==null){if(typeof e.activity_progress!="object")return!1;let n=e.activity_progress;if(typeof n.current!="number"||typeof n.total!="number")return!1}if(!Array.isArray(e.notices)||!e.reason||typeof e.reason!="object")return!1;let t=e.reason;if(typeof t.code!="string"||typeof t.text!="string"||!e.action||typeof e.action!="object")return!1;let r=e.action;return!(r.primary!==null&&!br(r.primary)||typeof e.updated_at!="string"||!e.updated_at||typeof e.ttl_seconds!="number")}function Re(d){return{schema_version:lt,module:d,capability_state:"unknown",activity_state:"idle",activity_label:null,activity_progress:null,severity:"unknown",reason:{code:`${d}.no_probe`,text:`${d} has not been probed yet.`},action:{primary:$e(d)},notices:[],updated_at:new Date(0).toISOString(),ttl_seconds:0}}function pt(d){return{schema_version:lt,module:d,capability_state:"unknown",activity_state:"idle",activity_label:null,activity_progress:null,severity:"unknown",reason:{code:`${d}.stale`,text:`Cached probe data for ${d} is stale.`},action:{primary:$e(d)},notices:[],updated_at:new Date(0).toISOString(),ttl_seconds:0}}function Fe(d){return{schema_version:lt,module:d,capability_state:"unknown",activity_state:"idle",activity_label:null,activity_progress:null,severity:"unknown",reason:{code:`${d}.invalid_response`,text:`Probe response for ${d} was invalid.`},action:{primary:$e(d)},notices:[],updated_at:new Date(0).toISOString(),ttl_seconds:0}}function dt(d){if(d.activity_state==="running")return!1;if(d.ttl_seconds<=0)return!0;let p=new Date(d.updated_at).getTime();return isNaN(p)?!0:Date.now()-p>d.ttl_seconds*1e3}function qe(d){return d.capability_state==="ready"&&d.action.primary===null}function Ze(d){var r,n,a;let p=(r=d.action)==null?void 0:r.primary,e=(n=p==null?void 0:p.verb)!=null?n:"probe",t=(a=p==null?void 0:p.label)!=null?a:e;return e==="setup"||e==="set_config"||e==="update"?{kind:"setup",verb:e,label:t}:e==="probe"?{kind:"probe",verb:e,label:t}:{kind:"action",verb:e,label:t}}function Ot(d,p){let e={};for(let t of p){let r=d[t];if(!r||typeof r!="object"){e[t]=Re(t);continue}if(!ct(r,t)){e[t]=Fe(t);continue}if(dt(r)){e[t]=pt(t);continue}e[t]=r}return e}var ut={en:{action_running:"Running ",api_key_missing:"Missing",api_key_set:"Entered",btn_install:"Open Setup Wizard",btn_install_desc:"Check whether the environment is ready, then open the step-by-step setup wizard",btn_reconfig:"Reconfigure",btn_reconfig_desc:"Open the setup wizard again to change directories, platform, or API keys",btn_validate:"Validate",check_bbt_fail:"Not detected",check_bbt_ok:"Installed",check_python_fail:"Not found",check_python_ok:"Ready",check_zotero_fail:"Not detected",check_zotero_ok:"Found",complete_export_path:"Save Better BibTeX JSON exports into:",complete_next:"Recommended next steps",complete_step1:"Open Dashboard",complete_step1_desc:'Press Ctrl+P and run "PaperForge: Open Main Panel", or click the PaperForge icon in the left sidebar.',complete_step2:"Sync Literature",complete_step2_desc:"In the main panel, click Sync Library to bring papers from Zotero into Obsidian and generate notes.",complete_step3:"Run OCR",complete_step3_desc:"In the Obsidian Base view, mark do_ocr:true on papers, then run OCR in the main panel.",complete_step4:"Configure Better BibTeX Auto-export",complete_step4_desc:'In Zotero, right-click the library or collection you want to sync -> Export -> Better BibTeX JSON -> enable "Keep updated".',complete_summary:"Saved Configuration",complete_title:"Setup Complete",copied:"Copied!",copy_pf_deep_cmd:"Copy /pf-deep Command",dashboard_drift_warning:"PaperForge CLI (v{0}) differs from plugin (v{1}). Open Settings \u2192 Runtime Health to sync.",deep_reading_not_found:"Deep reading file not found",desc:"Obsidian + Zotero literature pipeline. Sync papers, generate notes, run OCR, and read deeply in one place.",dir_base:"Base Dir",dir_index:"Index Dir",dir_notes:"Notes Dir",dir_resources:"Resource Dir",dir_system:"System Dir",dir_vault:"Vault Path",error_copied:"Copied!",error_copy_diagnostic:"Copy diagnostic",feat_agent_platform:"Agent Platform",feat_agent_platform_desc:"Select which agent platform to manage skills for.",feat_api_base_url:"API Base URL",feat_api_base_url_desc:"Custom OpenAI-compatible API endpoint. Leave empty for default.",feat_api_model:"API Model",feat_api_model_desc:"Embedding model name for this endpoint.",feat_build_btn:"Build",feat_build_complete:"Vector build complete.",feat_build_failed:"Build failed. See terminal output.",feat_building:"Building...",feat_cache_remove_failed:"Failed: {0}",feat_cache_removed:"Model cache removed.",feat_checking:"Checking...",feat_checking_btn:"Checking...",feat_deps_checking:"Checking dependencies...",feat_deps_missing:"Dependencies not installed. Required: chromadb, openai.",feat_enter_key:"Enter a valid OpenAI API key.",feat_install_btn:"Install",feat_install_deps:"Install Dependencies",feat_install_deps_desc:"pip install chromadb openai (~35MB).",feat_install_done:"Dependencies installed. Building vectors...",feat_install_failed:"Install failed: ",feat_installing:"Installing...",feat_installing_pkgs:"Installing {pkgs}...",feat_key_rejected:"API key rejected.",feat_memory_desc:"The Memory Layer is the core data engine of PaperForge, powered by SQLite. It integrates literature metadata (papers, assets, aliases, reading events), provides FTS5 metadata search across titles, abstracts, authors, domains, and collections, and powers agent-context and paper-status. Always active \u2014 no toggle needed.",feat_memory_rebuild_btn:"Rebuild",feat_memory_rebuild_done:"Memory DB rebuilt.",feat_memory_rebuild_failed:"Rebuild failed.",feat_memory_rebuilding:"Rebuilding...",feat_model_changed_warn:"Model changed ({0} -> {1}). Existing vectors are incompatible \u2014 rebuild required.",feat_network_error:"Network error: ",feat_no_python:"No Python found. Check Installation tab.",feat_not_cached:"Not cached",feat_openai_key:"OpenAI API Key",feat_openai_key_desc:"Used for API embedding calls. Model is defined below.",feat_output_copied:"Output copied to clipboard.",feat_rebuild_btn:"Rebuild",feat_rebuild_vectors:"Rebuild Vectors",feat_rebuild_vectors_changed:"Model changed \u2014 rebuild to update all vectors.",feat_rebuild_vectors_desc:"Rebuild all OCR fulltext vectors. Required after model or mode change.",feat_removing:"Removing...",feat_retry_btn:"Retry",feat_skills_desc:"Manage and enable/disable agent skills installed in your vault. Each row corresponds to a SKILL.md file \u2014 toggle off to prevent the agent from auto-invoking that skill.",feat_skills_system:"System Skills ship with PaperForge and are updated alongside PaperForge.",feat_skills_user:"User Skills are custom skills you install from community or create yourself.",feat_uninstall_btn:"Uninstall",feat_valid_key:"API key valid.",feat_vector_config_label:"Vector Settings",feat_vector_corrupted:"Vector index corrupted \u2014 needs force rebuild.",feat_vector_desc:"Vector Database enables semantic search across OCR-extracted fulltext via API embedding. Documents are split into chunks, embedded via OpenAI-compatible API, and stored in ChromaDB.",feat_vector_enable:"Enable Vector Retrieval",feat_vector_enable_desc:"Semantic search across OCR fulltext. Requires: pip install openai chromadb (~35MB).",feat_vector_rebuild_force_btn:"Force Rebuild",feat_verify:"Verify",feat_verify_btn:"Verify",field_paddleocr:"PaddleOCR API Key",field_python_custom:"Custom Path",field_python_interp:"Python Interpreter",field_zotero_data:"Zotero Data Dir",field_zotero_placeholder:"Required. Path to Zotero data directory for PDF attachment resolution.",guide_ocr:"Run OCR",guide_ocr_desc:"In the main panel, click Run OCR to extract full text and figures from PDFs for later reading and analysis.",guide_open:"Open Main Panel",guide_open_desc:'Press Ctrl+P and run "PaperForge: Open Main Panel", or click the PaperForge icon in the left sidebar.',guide_sync:"Sync Literature",guide_sync_desc:"After Better BibTeX JSON export is configured, click Sync Library to import papers from Zotero into Obsidian and generate notes automatically.",header_title:"PaperForge",install_bootstrapping:"PaperForge Python package not found. Installing automatically...",install_btn:"Start Install",install_btn_retry:"Retry",install_btn_running:"Installing...",install_complete:"Installation complete!",install_failed:"Installation failed: ",install_validating:"Validating setup...",jump_to_deep_reading:"Open Deep Reading",label_agent:"Agent Platform",nav_close:"Close",nav_next:"Next",nav_prev:"Back",not_set:"Not entered",notice_check_fail:"Missing: ",notice_python_missing:"Python was not detected. Install Python 3.10+ and add it to PATH.",ocr_privacy_title:"OCR Privacy Notice",ocr_privacy_warning:"OCR will upload PDFs to the PaddleOCR API. Do not upload sensitive or confidential documents.",ocr_queue_add:"Add to OCR Queue",ocr_queue_added:"Added to OCR queue",ocr_queue_remove:"Remove from OCR Queue",ocr_queue_removed:"Removed from OCR queue",ocr_understand:"I understand, continue",optional_later:"(can be set later in Settings)",orphan_delete_failed:"Prune failed",orphan_delete_selected:"Delete {count} selected",orphan_deleted:"Deleted {count} orphan workspace(s)",orphan_desc:"These papers are no longer in your Zotero library.",orphan_deselect_all:"Deselect all",orphan_explain:"Removed from Zotero. Workspace files remain on disk.",orphan_keep_all:"Keep all",orphan_none_selected:"No papers selected for deletion",orphan_select_all:"Select all",orphan_title:"Found {count} orphan paper(s)",panel_actions:"Quick Actions",prep_bbt:"Better BibTeX",prep_bbt_desc:"In Zotero: Tools -> Add-ons -> install Better BibTeX.",prep_export:"Better BibTeX Auto-export",prep_export_desc:'In Zotero, right-click the collection you want to sync -> Export Collection -> BetterBibTeX JSON -> enable "Keep updated" -> save the JSON file into the exports folder shown below. Obsidian Base views will use the JSON filename as the Base name:',prep_export_path_label:"Save the exported JSON file into this folder:",prep_key:"PaddleOCR Key",prep_key_desc:"Get your API key from https://aistudio.baidu.com/paddleocr",prep_python:"Python 3.10+",prep_python_desc:"Python must be available from the command line. If you are not sure, click below to auto-detect.",prep_zotero:"Zotero Desktop",prep_zotero_desc:"Install Zotero from https://www.zotero.org",run_in_agent:"Run in {0}",runtime_health:"Runtime Health",runtime_health_checking:"Checking...",runtime_health_desc:"Check whether the installed paperforge Python package matches the plugin version and whether the deployed skill contract is current.",runtime_health_match:"Match",runtime_health_mismatch:"Mismatch",runtime_health_package_ver:"Python package v{0}",runtime_health_plugin_ver:"Plugin v{0}",runtime_health_sync:"Sync Runtime",runtime_health_sync_done:"Runtime synced to v{0}",runtime_health_sync_fail:"Sync failed: {0}",runtime_health_syncing:"Syncing...",section_config:"Current Configuration",section_guide:"How To Use",section_prep:"Preparation",section_prep_desc:"Before first use, finish these 4 preparation items. Better BibTeX auto-export is configured after setup:",setup_done:"PaperForge environment is ready",setup_pending:"Not installed yet. Finish the preparation items below, then open the wizard.",tab_overview:"Overview",tab_modules:"Module Detail",tab_help:"Help",tab_setup:"Installation",tab_features:"Features",tab_maintenance:"Maintenance",validate_base:"Base directory is required",validate_fail:"Please complete the required fields below",validate_index:"Index directory is required",validate_key:"PaddleOCR API key (optional, needed for OCR)",validate_notes:"Notes directory is required",validate_resources:"Resources directory is required",validate_system:"System directory is required",validate_vault:"Vault path is required",validate_zotero:"Zotero data directory (optional, needed for PDF linking)",wizard_agent_hint:"Choose the AI agent platform you use most often. PaperForge will place the matching command and skill files in the correct location.",wizard_dir_hint:"PaperForge stores user-facing literature data under the resources directory. These folders will live there:",wizard_dir_sub_hint:"Resolved folder preview based on the names below:",wizard_intro:"This wizard walks you through the full setup. In most cases, the default values are fine to keep.",wizard_keys_hint:"Enter your PaddleOCR API key below. If you want PaperForge to auto-locate Zotero PDFs, you can also fill in the Zotero data directory.",wizard_preview:"After installation, system files stay at the vault root while literature data stays under the resources directory.",wizard_safety:"Safety: if the selected folders already contain files, setup preserves existing files and only creates missing PaperForge folders and files.",wizard_step1:"Overview",wizard_step2:"Directory Setup",wizard_step3:"Platform & Keys",wizard_step4:"Install",wizard_step5:"Done",wizard_skip_ocr_desc:"OCR will not be available until you configure a valid PaddleOCR API key. You can continue setup now and configure it later in Settings.",wizard_skip_ocr_continue:"Continue without OCR key",wizard_skip_ocr_back:"Back to configure",wizard_api_hint_skip:"OCR key is optional \u2014 you may skip it and configure later.",wizard_sys_hint:"These folders live at the vault root, outside the resources directory:",wizard_title:"PaperForge Setup Wizard",ocr_maint_no_action:"No Action Needed",ocr_maint_rebuild:"Rebuild Recommended",ocr_maint_failed:"OCR Failed",ocr_maint_limited:"Result Limited",ocr_maint_needs_attention:"Needs Attention",ocr_maint_limitations:"Result Limitations",ocr_maint_hero_ok:"OCR looks usable overall.",ocr_maint_hero_warn:"OCR needs attention: {rebuild} rebuild recommended, {failed} failed.",ocr_maint_hero_note:"This page only promotes issues where maintenance is likely to help. Some papers may have limitations that maintenance will not improve.",ocr_maint_limitations_intro:"These papers look less certain, but PaperForge does not currently have a high-confidence maintenance action to recommend.",ocr_maint_all_papers:"All Papers",ocr_maint_rebuild_btn:"Rebuild results",ocr_maint_redo_btn:"Rerun OCR",ocr_maint_redo_confirm:"Rerun OCR for {n} paper(s)? Existing derived OCR artifacts will be replaced.",maintenance_group_retry:"Needs Retry",maintenance_group_rebuild:"Can Rebuild",maintenance_group_legacy:"Upgrade Available (Optional)",maintenance_btn_retry:"Retry",maintenance_btn_rebuild:"Rebuild",maintenance_btn_upgrade:"Upgrade",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...",version_papers_count:"{n} papers",version_current:"current",version_restore_btn:"Restore",version_compare_btn:"Compare",version_restore_selected:"Restore selected",version_clear_old:"Clear old versions (free {size})",version_no_backups:"No version history available",version_restore_confirm:"Restore {label} for {paper}?",version_restore_done:"Restored {label}",version_compare_title:"{vA} vs {vB}",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 paper content...",retrieval_search_idle_hint:"Type a keyword or @ to search paper content",retrieval_searching_metadata:"Searching metadata...",retrieval_searching_deep:"Deep searching...",retrieval_search_cancel:"Cancel",retrieval_results_count:"{n} result(s)",retrieval_empty:"No matching papers found.",retrieval_empty_tips:"Try broader terms or use @ deep search.",retrieval_vectors_not_built:"Vector index not built",retrieval_vectors_not_built_desc:"Build vectors to enable @ deep search with semantic matching.",retrieval_open_vector_settings:"Open Vector Settings",retrieval_backend_unavailable:"Search backend unavailable",retrieval_backend_unavailable_desc:"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_desc:"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. Rebuild to use deep search.",retrieval_rebuild_vectors:"Rebuild Vectors",retrieval_build_idle:"Vector database ready",retrieval_build_ready:"{n} vector(s) built",retrieval_build_stopping:"Stopping...",retrieval_build_stopped:"Build stopped ({n}/{t} papers)",retrieval_build_failed:"Build failed",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?",md_select_installation:"Installation",md_select_library:"Library",md_select_ocr:"OCR",md_select_memory:"Memory",installation_detail_heading:"Installation Details",btn_back_to_overview:"\u2190 Back to Overview",agent_integration_section:"Agent Integration",module_detail_open_installation:"Open Installation",module_detail_open_help:"Help & Docs",module_detail_open_maintenance:"Maintenance",md_unavailable_module:"Not available yet",managed_runtime_status:"Runtime Status",managed_runtime_install:"Install Runtime",managed_runtime_repair:"Repair Runtime",managed_runtime_rollback:"Rollback",managed_runtime_update:"Update Runtime",managed_runtime_check_status:"Check Status",managed_runtime_refresh:"Refresh Status",managed_runtime_manual_setup:"Manual Setup",managed_runtime_stop:"Stop",managed_runtime_unknown_state:"Unknown",managed_runtime_ok_state:"Ready",managed_runtime_not_installed:"Not Installed",managed_runtime_needs_repair:"Needs Repair",managed_runtime_unavailable:"Unavailable",managed_runtime_last_verified:"Last verified: {time}",managed_runtime_running:"Runtime operation in progress...",managed_runtime_action_complete:"Runtime operation completed.",managed_runtime_action_failed:"Runtime operation failed: {error}",managed_runtime_action_cancelled:"Runtime operation cancelled.",cc_summary_ok:"All systems ready",cc_summary_core_ok:"Core environment ready; {n} modules pending detection",cc_summary_attention:"Some modules need attention",cc_summary_ok_body:"PaperForge environment is fully operational. Installation and documentation are verified.",cc_summary_core_ok_body:"Installation and Help modules are active. Library, OCR, Memory, and Maintenance will show live status once their backends are connected.",cc_summary_attention_body:"One or more core modules require your attention to function properly.",cc_badge_ok:"Ready",cc_badge_pending:"Pending",cc_badge_setup:"Setup needed",cc_badge_attention:"Needs attention",cc_diagnostic_toggle:"Details",cc_n_ready:"{n} ready",cc_n_pending:"{n} pending",cc_title:"System Status",cc_desc:"Real-time status of PaperForge core modules. Modules with a pending action need your attention.",cc_zone_attention:"Needs Attention",cc_zone_modules:"All Modules",cc_module_installation:"Installation",cc_module_help:"Help & Docs",cc_module_library:"Library Index",cc_module_ocr:"OCR Engine",cc_module_memory:"Memory Layer",cc_module_maintenance:"Maintenance",cc_state_ready:"Ready",cc_state_limited:"Limited",cc_state_unavailable:"Unavailable",cc_state_unknown:"Unknown",cc_severity_ok:"OK",cc_severity_unknown:"Unknown",cc_severity_warning:"Warning",cc_severity_error:"Error",cc_state_missing_input:"Missing Input",cc_state_needs_action:"Needs Action",cc_action_setup:"Open Setup Wizard",cc_action_probe:"Check",cc_action_set_config:"Set Config",cc_action_update:"Update",cc_reason_installation_ready:"PaperForge environment is set up correctly.",cc_reason_config_missing:"Configuration file is missing. Run setup to create one.",cc_reason_config_corrupt:"Configuration file is corrupt. Run setup to repair.",cc_reason_python_version_unsupported:"Python version is not supported. Install Python 3.11+.",cc_reason_help_ready:"Help documentation is available.",cc_reason_docs_missing:"Help documentation is not yet installed.",cc_reason_placeholder:"Detection pending \u2014 will show live status once connected.",cc_diag_module:"Module",cc_diag_state:"State",cc_diag_severity:"Severity",cc_diag_activity:"Activity",cc_diag_reason:"Reason",cc_diag_ttl:"TTL",cc_diag_updated:"Updated",cc_reason_no_probe:"{module} has not been probed yet.",cc_reason_stale:"Cached probe data for {module} is stale.",cc_reason_invalid_response:"Probe response for {module} was invalid.",cc_reason_probing:"Checking {module} status...",cc_notice_placeholder:"{module} module probing is not yet available; it will be added in a future update.",cc_notice_refreshed:"Module status refreshed.",cc_notice_refresh_failed:"Failed to refresh module status.",cc_activity_idle:"Idle",cc_activity_running:"Running"},zh:{action_running:"\u6B63\u5728\u6267\u884C ",api_key_missing:"\u672A\u914D\u7F6E \u2717",api_key_set:"\u5DF2\u914D\u7F6E \u2713",btn_install:"\u6253\u5F00\u5B89\u88C5\u5411\u5BFC",btn_install_desc:"\u81EA\u52A8\u68C0\u6D4B Python + \u524D\u7F6E\u73AF\u5883\uFF0C\u901A\u8FC7\u540E\u6253\u5F00\u5206\u6B65\u5B89\u88C5\u5411\u5BFC",btn_reconfig:"\u91CD\u65B0\u914D\u7F6E",btn_reconfig_desc:"\u91CD\u65B0\u8FD0\u884C\u5B89\u88C5\u5411\u5BFC\uFF0C\u4FEE\u6539\u76EE\u5F55\u6216\u5BC6\u94A5\u914D\u7F6E",btn_validate:"\u9A8C\u8BC1",check_bbt_fail:"\u672A\u68C0\u6D4B\u5230",check_bbt_ok:"\u5DF2\u5B89\u88C5",check_python_fail:"\u672A\u5B89\u88C5",check_python_ok:"\u5DF2\u5C31\u7EEA",check_zotero_fail:"\u672A\u68C0\u6D4B\u5230",check_zotero_ok:"\u5DF2\u5B89\u88C5",complete_next:"\u4E0B\u4E00\u6B65\u64CD\u4F5C",complete_step1:"\u6253\u5F00 PaperForge Dashboard",complete_step1_desc:"Ctrl+P \u2192 \u8F93\u5165 PaperForge: Open Dashboard\uFF0C\u6216\u70B9\u5DE6\u4FA7\u4E66\u672C\u56FE\u6807",complete_step2:"\u540C\u6B65\u6587\u732E",complete_step2_desc:"Dashboard \u4E2D\u70B9 Sync Library\uFF0C\u4ECE Zotero \u62C9\u53D6\u6587\u732E\u751F\u6210\u7B14\u8BB0",complete_step3:"\u8FD0\u884C OCR",complete_step3_desc:"Dashboard \u4E2D\u70B9 Run OCR\uFF0C\u63D0\u53D6 PDF \u5168\u6587\u4E0E\u56FE\u8868",complete_step4:"\u914D\u7F6E BBT \u81EA\u52A8\u5BFC\u51FA",complete_summary:"\u5F53\u524D\u5B8C\u6574\u914D\u7F6E",complete_title:"\u2713 PaperForge \u5B89\u88C5\u5B8C\u6210",copied:"\u5DF2\u590D\u5236\uFF01",copy_pf_deep_cmd:"\u590D\u5236 /pf-deep \u547D\u4EE4",dashboard_drift_warning:'\u63D2\u4EF6\u7248\u672C\u4E0E Python \u8FD0\u884C\u65F6\u7248\u672C\u4E0D\u5339\u914D\u3002\u8BF7\u5728\u8BBE\u7F6E\u4E2D\u70B9\u51FB"\u540C\u6B65\u8FD0\u884C\u65F6"\u3002',deep_reading_not_found:"\u7CBE\u8BFB\u6587\u4EF6\u672A\u627E\u5230",desc:"Obsidian + Zotero \u6587\u732E\u7BA1\u7406\u6D41\u6C34\u7EBF\u3002\u81EA\u52A8\u540C\u6B65\u6587\u732E\u3001\u751F\u6210\u7B14\u8BB0\u3001OCR \u63D0\u53D6\u5168\u6587\uFF0C\u4E00\u7AD9\u5F0F\u6587\u732E\u7CBE\u8BFB\u5DE5\u4F5C\u6D41\u3002",dir_base:"Base \u76EE\u5F55",dir_index:"\u7D22\u5F15\u76EE\u5F55",dir_notes:"\u6B63\u6587\u76EE\u5F55",dir_resources:"\u8D44\u6E90\u76EE\u5F55",dir_system:"\u7CFB\u7EDF\u76EE\u5F55",dir_vault:"Vault \u8DEF\u5F84",error_copied:"\u5DF2\u590D\u5236\uFF01",error_copy_diagnostic:"\u590D\u5236\u8BCA\u65AD\u4FE1\u606F",feat_agent_platform:"Agent \u5E73\u53F0",feat_agent_platform_desc:"\u9009\u62E9\u8981\u7BA1\u7406\u7684 Agent \u5E73\u53F0\u3002",feat_api_base_url:"API \u5730\u5740",feat_api_base_url_desc:"\u81EA\u5B9A\u4E49 OpenAI \u517C\u5BB9 API \u7AEF\u70B9\u3002\u7559\u7A7A\u4F7F\u7528\u9ED8\u8BA4\u5730\u5740\u3002",feat_api_model:"API \u6A21\u578B",feat_api_model_desc:"\u8BE5\u7AEF\u70B9\u4F7F\u7528\u7684\u5D4C\u5165\u6A21\u578B\u540D\u79F0\u3002",feat_build_btn:"\u6784\u5EFA",feat_build_complete:"\u5411\u91CF\u6784\u5EFA\u5B8C\u6210\u3002",feat_build_failed:"\u6784\u5EFA\u5931\u8D25\u3002\u8BF7\u67E5\u770B\u7EC8\u7AEF\u8F93\u51FA\u3002",feat_building:"\u6784\u5EFA\u4E2D\u2026",feat_cache_remove_failed:"\u5931\u8D25\uFF1A{0}",feat_cache_removed:"\u6A21\u578B\u7F13\u5B58\u5DF2\u6E05\u9664\u3002",feat_checking:"\u68C0\u6D4B\u4E2D\u2026",feat_checking_btn:"\u68C0\u6D4B\u4E2D\u2026",feat_deps_checking:"\u6B63\u5728\u68C0\u6D4B\u4F9D\u8D56\u2026",feat_deps_missing:"\u4F9D\u8D56\u672A\u5B89\u88C5\u3002\u9700\u8981\uFF1Achromadb, openai\u3002",feat_enter_key:"\u8BF7\u8F93\u5165\u6709\u6548\u7684 OpenAI API Key\u3002",feat_install_btn:"\u5B89\u88C5",feat_install_deps:"\u5B89\u88C5\u4F9D\u8D56",feat_install_done:"\u4F9D\u8D56\u5DF2\u5B89\u88C5\u3002\u6B63\u5728\u6784\u5EFA\u5411\u91CF\u2026",feat_install_failed:"\u5B89\u88C5\u5931\u8D25\uFF1A",feat_installing:"\u5B89\u88C5\u4E2D\u2026",feat_installing_pkgs:"\u6B63\u5728\u5B89\u88C5 {pkgs}...",feat_key_rejected:"API Key \u88AB\u62D2\u7EDD\u3002",feat_memory_desc:"\u8BB0\u5FC6\u5C42\u662F PaperForge \u7684\u6838\u5FC3\u6570\u636E\u5F15\u64CE\uFF0C\u57FA\u4E8E SQLite \u6784\u5EFA\u3002\u5B83\u6574\u5408\u4E86\u6587\u732E\u5143\u6570\u636E\uFF08\u8BBA\u6587\u3001\u8D44\u6E90\u6587\u4EF6\u3001\u522B\u540D\u3001\u9605\u8BFB\u4E8B\u4EF6\uFF09\uFF0C\u652F\u6301 FTS5 \u5143\u6570\u636E\u68C0\u7D22\uFF08\u6807\u9898\u3001\u6458\u8981\u3001\u4F5C\u8005\u3001domain\u3001collection\uFF09\uFF0C\u5E76\u4E3A agent-context \u548C paper-status \u547D\u4EE4\u63D0\u4F9B\u6570\u636E\u652F\u6491\u3002\u59CB\u7EC8\u8FD0\u884C\uFF0C\u65E0\u9700\u624B\u52A8\u5F00\u542F\u3002",feat_memory_rebuild_btn:"\u91CD\u5EFA\u6570\u636E\u5E93",feat_memory_rebuild_done:"\u8BB0\u5FC6\u6570\u636E\u5E93\u91CD\u5EFA\u5B8C\u6210\u3002",feat_memory_rebuild_failed:"\u91CD\u5EFA\u5931\u8D25\u3002",feat_memory_rebuilding:"\u91CD\u5EFA\u4E2D\u2026",feat_model:"\u6A21\u578B",feat_model_changed_warn:"\u6A21\u578B\u5DF2\u66F4\u6362\uFF08{0} -> {1}\uFF09\u3002\u5DF2\u6709\u5411\u91CF\u4E0D\u517C\u5BB9\u2014\u2014\u9700\u8981\u91CD\u5EFA\u3002",feat_network_error:"\u7F51\u7EDC\u9519\u8BEF\uFF1A",feat_no_python:"\u672A\u627E\u5230 Python\u3002\u8BF7\u67E5\u770B\u5B89\u88C5\u6807\u7B7E\u9875\u3002",feat_not_cached:"\u672A\u7F13\u5B58",feat_openai_key:"OpenAI API Key",feat_openai_key_desc:"\u7528\u4E8E API \u5D4C\u5165\u8C03\u7528\uFF0C\u6A21\u578B\u5728\u4E0B\u65B9\u5B9A\u4E49\u3002",feat_output_copied:"\u8F93\u51FA\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F\u3002",feat_rebuild_btn:"\u91CD\u5EFA",feat_rebuild_vectors:"\u91CD\u5EFA\u5411\u91CF",feat_rebuild_vectors_changed:"\u6A21\u578B\u5DF2\u66F4\u6362 \u2014 \u9700\u8981\u91CD\u5EFA\u5411\u91CF\u3002",feat_rebuild_vectors_desc:"\u91CD\u5EFA\u6240\u6709 OCR \u5168\u6587\u5411\u91CF\u3002\u66F4\u6362\u6A21\u578B\u6216\u6A21\u5F0F\u540E\u9700\u8981\u91CD\u5EFA\u3002",feat_removing:"\u5220\u9664\u4E2D\u2026",feat_retry_btn:"\u91CD\u8BD5",feat_skills_desc:"\u7BA1\u7406 Vault \u4E2D\u5DF2\u5B89\u88C5\u7684 Agent \u6280\u80FD\u3002\u6BCF\u884C\u5BF9\u5E94\u4E00\u4E2A SKILL.md \u6587\u4EF6\uFF0C\u5173\u95ED\u5F00\u5173\u53EF\u963B\u6B62 Agent \u81EA\u52A8\u8C03\u7528\u8BE5\u6280\u80FD\u3002",feat_skills_system:"\u7CFB\u7EDF\u6280\u80FD\u968F PaperForge \u4E00\u540C\u53D1\u5E03\uFF0C\u4F1A\u8DDF\u968F PaperForge \u7248\u672C\u66F4\u65B0\u3002",feat_skills_user:"\u7528\u6237\u6280\u80FD\u662F\u4F60\u81EA\u884C\u5B89\u88C5\u6216\u521B\u5EFA\u7684\u81EA\u5B9A\u4E49\u6280\u80FD\u3002",feat_uninstall_btn:"\u5378\u8F7D",feat_valid_key:"API Key \u6709\u6548\u3002",feat_vector_config_label:"\u5411\u91CF\u5E93\u914D\u7F6E",feat_vector_corrupted:"\u5411\u91CF\u7D22\u5F15\u5DF2\u635F\u574F \u2014 \u9700\u8981\u5F3A\u5236\u91CD\u5EFA\u3002",feat_vector_desc:"\u5411\u91CF\u6570\u636E\u5E93\u901A\u8FC7\u5D4C\u5165\u6A21\u578B\u5B9E\u73B0 OCR \u5168\u6587\u7684\u8BED\u4E49\u641C\u7D22\u3002\u6587\u6863\u88AB\u5207\u5206\u4E3A\u6587\u672C\u5757\uFF08chunk\uFF09\uFF0C\u7F16\u7801\u4E3A\u5411\u91CF\u5B58\u5165 ChromaDB\u3002\u652F\u6301\u672C\u5730\u6A21\u578B\uFF08\u514D\u8D39\uFF0CCPU \u8FD0\u884C\uFF09\u6216 OpenAI API\uFF08\u4ED8\u8D39\uFF0C\u66F4\u5FEB\u901F\uFF09\u3002",feat_vector_enable:"\u542F\u7528\u5411\u91CF\u68C0\u7D22",feat_vector_enable_desc:"\u5BF9 OCR \u5168\u6587\u8FDB\u884C\u8BED\u4E49\u641C\u7D22\u3002\u9700\u5B89\u88C5: pip install chromadb sentence-transformers openai (~500MB)\u3002",feat_vector_rebuild_force_btn:"\u5F3A\u5236\u91CD\u5EFA",feat_verify:"\u9A8C\u8BC1",feat_verify_btn:"\u9A8C\u8BC1",field_paddleocr:"PaddleOCR API \u5BC6\u94A5",field_python_custom:"\u81EA\u5B9A\u4E49 Python \u8DEF\u5F84",field_python_interp:"\u5F53\u524D Python \u89E3\u91CA\u5668",field_zotero_data:"Zotero \u6570\u636E\u76EE\u5F55",field_zotero_placeholder:"\u53EF\u9009\uFF0C\u7528\u4E8E\u81EA\u52A8\u68C0\u6D4B PDF",guide_ocr:"\u8FD0\u884C OCR",guide_ocr_desc:"Dashboard \u4E2D\u70B9 Run OCR\uFF0C\u63D0\u53D6 PDF \u5168\u6587\u4E0E\u56FE\u8868",guide_open:"\u6253\u5F00 Dashboard",guide_open_desc:"Ctrl+P \u2192 \u8F93\u5165 PaperForge: Open Dashboard\uFF0C\u6216\u70B9\u5DE6\u4FA7\u4E66\u672C\u56FE\u6807",guide_sync:"\u540C\u6B65\u6587\u732E",guide_sync_desc:"Dashboard \u4E2D\u70B9 Sync Library\uFF0C\u4ECE Zotero \u62C9\u53D6\u6587\u732E\u751F\u6210\u7B14\u8BB0",header_title:"PaperForge",install_bootstrapping:"\u672A\u68C0\u6D4B\u5230 PaperForge Python \u5305\uFF0C\u6B63\u5728\u81EA\u52A8\u5B89\u88C5\u2026",install_btn:"\u5F00\u59CB\u5B89\u88C5",install_btn_retry:"\u91CD\u8BD5",install_btn_running:"\u6B63\u5728\u5B89\u88C5...",install_complete:"\u2713 \u5B89\u88C5\u5B8C\u6210\uFF01",install_failed:"\u2717 \u5B89\u88C5\u5931\u8D25\uFF1A",install_validating:"\u6B63\u5728\u6821\u9A8C\u5B89\u88C5\u73AF\u5883\u2026",jump_to_deep_reading:"\u8DF3\u8F6C\u5230\u7CBE\u8BFB",label_agent:"Agent \u5E73\u53F0",nav_close:"\u5173\u95ED",nav_next:"\u4E0B\u4E00\u6B65 \u2192",nav_prev:"\u2190 \u4E0A\u4E00\u6B65",no_pending_ocr:"\u6240\u6709 OCR \u4EFB\u52A1\u5DF2\u5B8C\u6210",not_set:"\u672A\u8BBE\u7F6E",notice_check_fail:"\u672A\u901A\u8FC7: ",notice_python_missing:"Python \u672A\u68C0\u6D4B\u5230\uFF0C\u8BF7\u5148\u5B89\u88C5 Python 3.10+ \u5E76\u52A0\u5165 PATH",ocr_privacy_title:"OCR \u9690\u79C1\u63D0\u793A",ocr_privacy_warning:"OCR \u4F1A\u5C06 PDF \u4E0A\u4F20\u5230 PaddleOCR API \u8FDB\u884C\u5904\u7406\u3002\u8BF7\u4E0D\u8981\u4E0A\u4F20\u5305\u542B\u654F\u611F\u4FE1\u606F\u6216\u65E0\u6CD5\u5916\u4F20\u7684\u6587\u732E\u3002",ocr_queue_add:"\u52A0\u5165 OCR \u961F\u5217",ocr_queue_added:"\u5DF2\u52A0\u5165 OCR \u961F\u5217",ocr_queue_remove:"\u79FB\u51FA OCR \u961F\u5217",ocr_queue_removed:"\u5DF2\u79FB\u51FA OCR \u961F\u5217",ocr_understand:"\u6211\u4E86\u89E3\uFF0C\u7EE7\u7EED",optional_later:"\uFF08\u7A0D\u540E\u53EF\u5728\u8BBE\u7F6E\u4E2D\u8865\u5145\uFF09",orphan_delete_failed:"\u6E05\u7406\u5931\u8D25",orphan_delete_selected:"\u5220\u9664 {count} \u7BC7",orphan_deleted:"\u5DF2\u5220\u9664 {count} \u7BC7\u6B8B\u7559\u6587\u732E",orphan_desc:"\u8FD9\u4E9B\u6587\u732E\u5DF2\u4ECE Zotero \u4E2D\u79FB\u9664\u3002",orphan_deselect_all:"\u53D6\u6D88\u5168\u9009",orphan_explain:"\u5DF2\u4ECE Zotero \u4E2D\u79FB\u9664\u3002\u5DE5\u4F5C\u533A\u6587\u4EF6\u4ECD\u4FDD\u7559\u5728\u78C1\u76D8\u4E0A\u3002",orphan_keep_all:"\u4FDD\u7559\u5168\u90E8",orphan_none_selected:"\u672A\u9009\u62E9\u4EFB\u4F55\u6587\u732E",orphan_select_all:"\u5168\u9009",orphan_title:"\u53D1\u73B0 {count} \u7BC7\u6B8B\u7559\u6587\u732E",panel_actions:"\u5FEB\u6377\u64CD\u4F5C",prep_bbt:"Better BibTeX",prep_bbt_desc:"Zotero \u2192 \u5DE5\u5177 \u2192 \u63D2\u4EF6 \u2192 \u5B89\u88C5 Better BibTeX",prep_export:"BBT \u81EA\u52A8\u5BFC\u51FA",prep_export_desc:"\u53F3\u952E\u6587\u732E\u5B50\u5206\u7C7B \u2192 \u5BFC\u51FA\u5206\u7C7B \u2192 BetterBibTeX JSON \u2192 \u52FE\u9009\u4FDD\u6301\u66F4\u65B0 \u2192 \u5BFC\u51FA\u5230\uFF08JSON \u6587\u4EF6\u540D\u5373\u4E3A Base \u540D\uFF09\uFF1A",prep_key:"PaddleOCR Key",prep_key_desc:"\u5728 https://aistudio.baidu.com/paddleocr \u83B7\u53D6 API Key",prep_python:"Python 3.10+",prep_python_desc:"\u786E\u4FDD Python \u53EF\u547D\u4EE4\u884C\u8C03\u7528\u3002\u70B9\u51FB\u4E0B\u65B9\u6309\u94AE\u81EA\u52A8\u68C0\u6D4B\u3002",prep_zotero:"Zotero \u684C\u9762\u7248",prep_zotero_desc:"\u5B89\u88C5 Zotero (https://www.zotero.org)",run_in_agent:"\u5728 {0} \u4E2D\u8FD0\u884C",runtime_health:"\u8FD0\u884C\u65F6\u72B6\u6001",runtime_health_checking:"\u6B63\u5728\u68C0\u6D4B\u2026",runtime_health_desc:"\u68C0\u67E5\u63D2\u4EF6\u4E0E Python \u8FD0\u884C\u65F6\u7248\u672C\u7684\u5339\u914D\u60C5\u51B5\uFF0C\u5E76\u786E\u8BA4\u5DF2\u90E8\u7F72\u7684 skill contract \u662F\u5426\u4E3A\u5F53\u524D\u7248\u672C\u3002",runtime_health_match:"\u5339\u914D",runtime_health_mismatch:"\u4E0D\u5339\u914D",runtime_health_package_ver:"Python \u5305 v{0}",runtime_health_plugin_ver:"\u63D2\u4EF6 v{0}",runtime_health_sync:"\u540C\u6B65\u8FD0\u884C\u65F6",runtime_health_sync_done:"\u8FD0\u884C\u65F6\u5DF2\u540C\u6B65\u81F3 v{0}",runtime_health_sync_fail:"\u8FD0\u884C\u65F6\u540C\u6B65\u5931\u8D25\uFF1A{0}",runtime_health_syncing:"\u6B63\u5728\u540C\u6B65\u2026",section_config:"\u5F53\u524D\u914D\u7F6E",section_guide:"\u64CD\u4F5C\u65B9\u5F0F",section_prep:"\u5B89\u88C5\u51C6\u5907",section_prep_desc:"\u9996\u6B21\u4F7F\u7528\u524D\uFF0C\u8BF7\u4F9D\u6B21\u5B8C\u6210\u4EE5\u4E0B\u51C6\u5907\uFF1A",setup_done:"\u2713 PaperForge \u73AF\u5883\u5DF2\u914D\u7F6E\u5B8C\u6210",setup_pending:"\u5C1A\u672A\u5B89\u88C5\uFF0C\u5B8C\u6210\u5B89\u88C5\u51C6\u5907\u540E\u70B9\u51FB\u5B89\u88C5\u5411\u5BFC",tab_overview:"\u6982\u89C8",tab_modules:"\u6A21\u5757\u8BE6\u60C5",tab_help:"\u5E2E\u52A9",tab_features:"\u529F\u80FD",tab_setup:"\u5B89\u88C5",tab_maintenance:"\u7EF4\u62A4",validate_base:"Base \u76EE\u5F55\u672A\u586B\u5199",validate_fail:"\u914D\u7F6E\u9A8C\u8BC1\u5931\u8D25",validate_index:"\u7D22\u5F15\u76EE\u5F55\u672A\u586B\u5199",validate_key:"PaddleOCR API \u5BC6\u94A5\u672A\u586B\u5199",validate_notes:"\u6B63\u6587\u76EE\u5F55\u672A\u586B\u5199",validate_resources:"\u8D44\u6E90\u76EE\u5F55\u672A\u586B\u5199",validate_system:"\u7CFB\u7EDF\u76EE\u5F55\u672A\u586B\u5199",validate_vault:"Vault \u8DEF\u5F84\u672A\u586B\u5199",validate_zotero:"Zotero \u6570\u636E\u76EE\u5F55\u4E3A\u5FC5\u586B\u9879",wizard_agent_hint:"\u9009\u62E9\u4F60\u4F7F\u7528\u7684 AI Agent \u5E73\u53F0\uFF0C\u5B89\u88C5\u65F6\u5C06\u6309\u5BF9\u5E94\u683C\u5F0F\u90E8\u7F72\u6280\u80FD\u6587\u4EF6\uFF1A",wizard_dir_hint:"\u8D44\u6E90\u76EE\u5F55\u662F\u6587\u732E\u6570\u636E\u7684\u7EDF\u4E00\u6839\u76EE\u5F55\uFF0C\u4EE5\u4E0B\u5B50\u76EE\u5F55\u5C06\u521B\u5EFA\u5728\u5176\u5185\u90E8\uFF1A",wizard_dir_sub_hint:"\u8D44\u6E90\u76EE\u5F55\u5185\u7684\u4E24\u4E2A\u5B50\u76EE\u5F55\uFF1A",wizard_intro:"\u672C\u5411\u5BFC\u5C06\u5F15\u5BFC\u60A8\u5B8C\u6210 PaperForge \u73AF\u5883\u7684\u5B8C\u6574\u914D\u7F6E\u3002\u5B89\u88C5\u8FC7\u7A0B\u4F1A\u81EA\u52A8\u521B\u5EFA\u6240\u6709\u76EE\u5F55\u7ED3\u6784\uFF0C\u65E0\u9700\u624B\u52A8\u64CD\u4F5C\u3002",wizard_keys_hint:"\u4EE5\u4E0B\u4E3A API \u5BC6\u94A5\u4E0E Zotero \u914D\u7F6E\uFF1A",wizard_preview:"\u7CFB\u7EDF\u6587\u4EF6\u548C Agent \u914D\u7F6E\u4F4D\u4E8E Vault \u6839\u76EE\u5F55\u4E0B\u3002\u6587\u732E\u6570\u636E\uFF08\u6B63\u6587\u3001\u7D22\u5F15\uFF09\u7EDF\u4E00\u5B58\u653E\u5728\u8D44\u6E90\u76EE\u5F55\u5185\u3002\u5B89\u88C5\u540E\u4ECD\u53EF\u5728\u8BBE\u7F6E\u4E2D\u4FEE\u6539\u3002",wizard_safety:"\u5B89\u5168\u8BF4\u660E\uFF1A\u5982\u679C\u4F60\u9009\u62E9\u7684\u76EE\u5F55\u91CC\u5DF2\u7ECF\u6709\u6587\u4EF6\uFF0C\u5B89\u88C5\u5411\u5BFC\u4F1A\u4FDD\u7559\u5DF2\u6709\u5185\u5BB9\uFF0C\u53EA\u8865\u5145\u7F3A\u5931\u7684 PaperForge \u6587\u4EF6\u548C\u76EE\u5F55\u3002",wizard_step1:"\u6982\u89C8",wizard_step2:"\u76EE\u5F55",wizard_step3:"Agent",wizard_step4:"\u5B89\u88C5",wizard_step5:"\u5B8C\u6210",wizard_skip_ocr_desc:"OCR \u529F\u80FD\u5728\u914D\u7F6E\u6709\u6548\u7684 PaddleOCR API \u5BC6\u94A5\u4E4B\u524D\u4E0D\u53EF\u7528\u3002\u60A8\u53EF\u4EE5\u7EE7\u7EED\u5B8C\u6210\u8BBE\u7F6E\uFF0C\u7A0D\u540E\u5728\u8BBE\u7F6E\u4E2D\u914D\u7F6E\u3002",wizard_skip_ocr_continue:"\u7EE7\u7EED\uFF0C\u7A0D\u540E\u914D\u7F6E\u5BC6\u94A5",wizard_skip_ocr_back:"\u8FD4\u56DE\u914D\u7F6E",wizard_api_hint_skip:"OCR \u5BC6\u94A5\u4E3A\u9009\u586B\u9879 \u2014 \u53EF\u8DF3\u8FC7\uFF0C\u7A0D\u540E\u5728\u8BBE\u7F6E\u4E2D\u914D\u7F6E\u3002",wizard_sys_hint:"\u72EC\u7ACB\u4E8E\u8D44\u6E90\u76EE\u5F55\u7684\u7CFB\u7EDF\u6587\u4EF6\uFF1A",wizard_title:"PaperForge \u5B89\u88C5\u5411\u5BFC",ocr_maint_no_action:"\u65E0\u9700\u5904\u7406",ocr_maint_rebuild:"\u5EFA\u8BAE\u91CD\u5EFA",ocr_maint_failed:"OCR \u5931\u8D25",ocr_maint_limited:"\u7ED3\u679C\u4E00\u822C",ocr_maint_needs_attention:"\u9700\u8981\u5904\u7406",ocr_maint_limitations:"\u7ED3\u679C\u8BF4\u660E",ocr_maint_hero_ok:"OCR \u6574\u4F53\u6B63\u5E38\u3002",ocr_maint_hero_warn:"OCR \u9700\u8981\u5173\u6CE8\uFF1A{rebuild} \u7BC7\u5EFA\u8BAE\u91CD\u5EFA\uFF0C{failed} \u7BC7\u5904\u7406\u5931\u8D25\u3002",ocr_maint_hero_note:"\u672C\u9875\u53EA\u63D0\u793A\u7EF4\u62A4\u540E\u5927\u6982\u7387\u4F1A\u6539\u5584\u7684\u95EE\u9898\u3002\u90E8\u5206\u8BBA\u6587\u6548\u679C\u4E00\u822C\uFF0C\u7EF4\u62A4\u672A\u5FC5\u80FD\u6539\u5584\u3002",ocr_maint_limitations_intro:"\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",ocr_maint_redo_confirm:"\u786E\u8BA4\u91CD\u65B0 OCR {n} \u7BC7\u8BBA\u6587\uFF1F\u73B0\u6709\u7684\u6D3E\u751F OCR \u7ED3\u679C\u5C06\u88AB\u66FF\u6362\u3002",maintenance_group_retry:"\u9700\u8981\u91CD\u8BD5",maintenance_group_rebuild:"\u53EF\u91CD\u5EFA\u7ED3\u679C",maintenance_group_legacy:"\u53EF\u5347\u7EA7\u65E7\u7ED3\u679C\uFF08\u53EF\u9009\uFF09",maintenance_btn_retry:"\u91CD\u8BD5",maintenance_btn_rebuild:"\u91CD\u5EFA",maintenance_btn_upgrade:"\u5347\u7EA7",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...",version_papers_count:"{n} \u7BC7\u8BBA\u6587",version_current:"\u5F53\u524D",version_restore_btn:"\u6062\u590D",version_compare_btn:"\u5BF9\u6BD4",version_restore_selected:"\u6062\u590D\u9009\u4E2D\u7248\u672C",version_clear_old:"\u6E05\u9664\u65E7\u7248\u672C (\u91CA\u653E {size})",version_no_backups:"\u6CA1\u6709\u53EF\u6062\u590D\u7684\u5386\u53F2\u7248\u672C",md_select_installation:"\u5B89\u88C5",md_select_library:"\u6587\u732E\u5E93",md_select_ocr:"OCR",md_select_memory:"\u8BB0\u5FC6\u5C42",installation_detail_heading:"\u5B89\u88C5\u8BE6\u60C5",btn_back_to_overview:"\u2190 \u8FD4\u56DE\u6982\u89C8",agent_integration_section:"Agent \u96C6\u6210",module_detail_open_installation:"\u6253\u5F00\u5B89\u88C5",module_detail_open_help:"\u5E2E\u52A9\u4E0E\u6587\u6863",module_detail_open_maintenance:"\u7EF4\u62A4",md_unavailable_module:"\u6682\u4E0D\u53EF\u7528",managed_runtime_status:"\u8FD0\u884C\u65F6\u72B6\u6001",managed_runtime_install:"\u5B89\u88C5\u8FD0\u884C\u65F6",managed_runtime_repair:"\u4FEE\u590D\u8FD0\u884C\u65F6",managed_runtime_rollback:"\u56DE\u6EDA",managed_runtime_update:"\u66F4\u65B0\u8FD0\u884C\u65F6",managed_runtime_check_status:"\u68C0\u67E5\u72B6\u6001",managed_runtime_refresh:"\u5237\u65B0\u72B6\u6001",managed_runtime_manual_setup:"\u624B\u52A8\u914D\u7F6E",managed_runtime_stop:"\u505C\u6B62",managed_runtime_unknown_state:"\u672A\u77E5",managed_runtime_ok_state:"\u5C31\u7EEA",managed_runtime_not_installed:"\u672A\u5B89\u88C5",managed_runtime_needs_repair:"\u9700\u8981\u4FEE\u590D",managed_runtime_unavailable:"\u4E0D\u53EF\u7528",managed_runtime_last_verified:"\u6700\u540E\u9A8C\u8BC1\uFF1A{time}",managed_runtime_running:"\u8FD0\u884C\u65F6\u64CD\u4F5C\u8FDB\u884C\u4E2D...",managed_runtime_action_complete:"\u8FD0\u884C\u65F6\u64CD\u4F5C\u5DF2\u5B8C\u6210\u3002",managed_runtime_action_cancelled:"\u8FD0\u884C\u65F6\u64CD\u4F5C\u5DF2\u53D6\u6D88\u3002",managed_runtime_action_failed:"\u8FD0\u884C\u65F6\u64CD\u4F5C\u5931\u8D25\uFF1A{error}",version_restore_confirm:"\u786E\u8BA4\u5C06 {paper} \u6062\u590D\u5230 {label}\uFF1F",version_restore_done:"\u5DF2\u6062\u590D\u5230 {label}",version_compare_title:"{vA} vs {vB}",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...\uFF08@ \u542F\u52A8\u6DF1\u5EA6\u641C\u7D22\uFF09",retrieval_search_placeholder_deep:"\u641C\u7D22\u8BBA\u6587\u5185\u5BB9...",retrieval_search_idle_hint:"\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:"{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 @ \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:"\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:"\u641C\u7D22\u540E\u7AEF\u4E0D\u53EF\u7528",retrieval_backend_unavailable_desc:"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\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\u91CD\u5EFA\u5411\u91CF\u540E\u624D\u80FD\u4F7F\u7528\u6DF1\u5EA6\u641C\u7D22\u3002",retrieval_rebuild_vectors:"\u91CD\u5EFA\u5411\u91CF",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...",cc_summary_attention:"\u90E8\u5206\u6A21\u5757\u9700\u8981\u5173\u6CE8",cc_summary_ok_body:"PaperForge \u73AF\u5883\u5DF2\u5B8C\u5168\u5C31\u7EEA\u3002\u5B89\u88C5\u548C\u5E2E\u52A9\u6587\u6863\u5747\u6B63\u5E38\u53EF\u7528\u3002",cc_summary_core_ok_body:"\u5B89\u88C5\u548C\u5E2E\u52A9\u6A21\u5757\u5DF2\u53EF\u7528\u3002\u6587\u732E\u7D22\u5F15\u3001OCR\u3001\u8BB0\u5FC6\u5C42\u548C\u7EF4\u62A4\u6A21\u5757\u5C06\u5728\u540E\u7AEF\u63A5\u5165\u540E\u663E\u793A\u5B9E\u65F6\u72B6\u6001\u3002",cc_summary_attention_body:"\u4E00\u4E2A\u6216\u591A\u4E2A\u6838\u5FC3\u6A21\u5757\u9700\u8981\u60A8\u7684\u5173\u6CE8\u624D\u80FD\u6B63\u5E38\u8FD0\u884C\u3002",retrieval_build_stopped:"\u6784\u5EFA\u5DF2\u505C\u6B62\uFF08{n}/{t} \u7BC7\uFF09",retrieval_build_failed:"\u6784\u5EFA\u5931\u8D25",retrieval_build_stale:"\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:"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\u73B0\u6709\u6587\u672C\u5757\uFF0C\u662F\u5426\u7EE7\u7EED\uFF1F",cc_summary_ok:"\u5168\u90E8\u6B63\u5E38",cc_summary_core_ok:"\u6838\u5FC3\u73AF\u5883\u6B63\u5E38\uFF1B{n} \u4E2A\u6A21\u5757\u72B6\u6001\u68C0\u6D4B\u5F85\u63A5\u5165",cc_badge_ok:"\u5DF2\u5C31\u7EEA",cc_badge_pending:"\u5F85\u63A5\u5165",cc_badge_setup:"\u9700\u8981\u5B89\u88C5",cc_badge_attention:"\u9700\u8981\u6CE8\u610F",cc_diagnostic_toggle:"\u8BE6\u60C5",cc_n_ready:"{n} \u5DF2\u5C31\u7EEA",cc_n_pending:"{n} \u5F85\u63A5\u5165",cc_title:"\u7CFB\u7EDF\u72B6\u6001",cc_desc:"PaperForge \u6838\u5FC3\u6A21\u5757\u7684\u5B9E\u65F6\u72B6\u6001\u3002\u6709\u5F85\u5904\u7406\u64CD\u4F5C\u7684\u6A21\u5757\u9700\u8981\u60A8\u7684\u5173\u6CE8\u3002",cc_zone_attention:"\u9700\u8981\u5173\u6CE8",cc_zone_modules:"\u6240\u6709\u6A21\u5757",cc_module_installation:"\u5B89\u88C5",cc_module_help:"\u5E2E\u52A9\u4E0E\u6587\u6863",cc_module_library:"\u6587\u732E\u7D22\u5F15",cc_module_ocr:"OCR \u5F15\u64CE",cc_module_memory:"\u8BB0\u5FC6\u5C42",cc_module_maintenance:"\u7EF4\u62A4",cc_state_ready:"\u5C31\u7EEA",cc_state_limited:"\u53D7\u9650",cc_state_unavailable:"\u4E0D\u53EF\u7528",cc_state_unknown:"\u672A\u77E5",cc_severity_ok:"\u6B63\u5E38",cc_severity_unknown:"\u672A\u77E5",cc_severity_warning:"\u8B66\u544A",cc_severity_error:"\u9519\u8BEF",cc_state_missing_input:"\u7F3A\u5C11\u8F93\u5165",cc_state_needs_action:"\u9700\u8981\u64CD\u4F5C",cc_action_setup:"\u6253\u5F00\u5B89\u88C5\u5411\u5BFC",cc_action_probe:"\u68C0\u6D4B",cc_action_set_config:"\u914D\u7F6E\u8BBE\u7F6E",cc_action_update:"\u66F4\u65B0",cc_reason_installation_ready:"PaperForge \u73AF\u5883\u5DF2\u6B63\u786E\u914D\u7F6E\u3002",cc_reason_config_missing:"\u914D\u7F6E\u6587\u4EF6\u7F3A\u5931\uFF0C\u8BF7\u8FD0\u884C\u5B89\u88C5\u5411\u5BFC\u3002",cc_reason_config_corrupt:"\u914D\u7F6E\u6587\u4EF6\u635F\u574F\uFF0C\u8BF7\u8FD0\u884C\u5B89\u88C5\u5411\u5BFC\u4FEE\u590D\u3002",cc_reason_python_version_unsupported:"Python \u7248\u672C\u4E0D\u53D7\u652F\u6301\uFF0C\u8BF7\u5B89\u88C5 Python 3.11+\u3002",cc_reason_help_ready:"\u5E2E\u52A9\u6587\u6863\u5DF2\u53EF\u7528\u3002",cc_reason_docs_missing:"\u5E2E\u52A9\u6587\u6863\u5C1A\u672A\u5B89\u88C5\u3002",cc_reason_placeholder:"\u72B6\u6001\u68C0\u6D4B\u5F85\u63A5\u5165\uFF0C\u63A5\u5165\u540E\u5C06\u663E\u793A\u5B9E\u65F6\u72B6\u6001\u3002",cc_diag_module:"\u6A21\u5757",cc_diag_state:"\u72B6\u6001",cc_diag_severity:"\u4E25\u91CD\u7A0B\u5EA6",cc_diag_activity:"\u6D3B\u52A8",cc_diag_reason:"\u539F\u56E0",cc_diag_ttl:"TTL",cc_diag_updated:"\u66F4\u65B0\u65F6\u95F4",cc_reason_no_probe:"{module} \u5C1A\u672A\u68C0\u6D4B\u3002",cc_reason_stale:"{module} \u7684\u68C0\u6D4B\u6570\u636E\u5DF2\u8FC7\u671F\u3002",cc_reason_invalid_response:"{module} \u7684\u68C0\u6D4B\u54CD\u5E94\u65E0\u6548\u3002",cc_reason_probing:"\u6B63\u5728\u68C0\u6D4B {module} \u72B6\u6001...",cc_notice_placeholder:"{module} \u6A21\u5757\u68C0\u6D4B\u529F\u80FD\u5C1A\u672A\u53EF\u7528\uFF0C\u5C06\u5728\u540E\u7EED\u7248\u672C\u4E2D\u6DFB\u52A0\u3002",cc_notice_refreshed:"\u6A21\u5757\u72B6\u6001\u5DF2\u5237\u65B0\u3002",cc_notice_refresh_failed:"\u6A21\u5757\u72B6\u6001\u5237\u65B0\u5931\u8D25\u3002",cc_activity_idle:"\u7A7A\u95F2",cc_activity_running:"\u8FD0\u884C\u4E2D"}},_t=null;function Er(d){try{let p=d.vault;if(typeof p.getConfig=="function"){let e=p.getConfig("language");if(e&&String(e).startsWith("zh"))return"zh"}}catch(p){}try{if(typeof localStorage!="undefined"){let p=localStorage.getItem("language");if(p&&String(p).startsWith("zh"))return"zh"}}catch(p){}return"en"}function Bt(d){_t=Er(d)==="zh"?ut.zh:ut.en}function o(d){return _t&&_t[d]||ut.en[d]||d}var F=require("obsidian"),W=K(require("fs")),ee=K(require("path")),nr=K(require("os")),G=require("child_process");var sr=K(ht());var he=K(require("fs")),_e=K(require("path")),Mt=K(require("os")),me=require("child_process"),ft=null,It=!1;function z(d,p,e,t){let r=e||he,n=t||me.execFileSync;if(p&&p.python_path&&p.python_path.trim()){let i=p.python_path.trim();if(r.existsSync(i))return{path:i,source:"manual",extraArgs:[]}}let a=[_e.join(d,".paperforge-test-venv","Scripts","python.exe"),_e.join(d,".venv","Scripts","python.exe"),_e.join(d,"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=[{path:"py",extraArgs:["-3"]},{path:"python",extraArgs:[]},{path:"python3",extraArgs:[]}];for(let i of s)try{let l=n(i.path,[...i.extraArgs,"--version"],{encoding:"utf-8",timeout:5e3,windowsHide:!0});if(l&&l.toLowerCase().includes("python"))return{path:i.path,source:"auto-detected",extraArgs:i.extraArgs}}catch(l){}return{path:"python",source:"auto-detected",extraArgs:[]}}function Lt(d,p,e,t,r){t===void 0&&(t=1e4);let n=r||me.execFile;return new Promise(a=>{n(d,["-c","import paperforge; print(paperforge.__version__)"],{cwd:e,timeout:t},(s,i)=>{if(s){a({status:"not-installed",pyVersion:null,pluginVersion:p,error:s.message});return}let l=i&&i.trim()||null;a(l===p?{status:"match",pyVersion:l,pluginVersion:p,error:null}:{status:"mismatch",pyVersion:l,pluginVersion:p,error:null})})})}function gt(d){let p=String(d),t={ENOENT:{type:"python_missing",message:"Python executable not found",recoverable:!0},"python-missing":{type:"python_missing",message:"Python executable not found",recoverable:!0},MODULE_NOT_FOUND:{type:"import_failed",message:"PaperForge package not installed",recoverable:!0},"import-failed":{type:"import_failed",message:"PaperForge package not installed",recoverable:!0},"version-mismatch":{type:"version_mismatch",message:"Plugin and package versions differ",recoverable:!0,action:"sync-runtime"},"pip-failed":{type:"pip_install_failure",message:"pip install command failed",recoverable:!0},ETIMEDOUT:{type:"timeout",message:"Subprocess timed out",recoverable:!0,action:"retry"},timeout:{type:"timeout",message:"Subprocess timed out",recoverable:!0,action:"retry"},NO_PYTHON:{type:"no_python",message:"Python executable not found",recoverable:!0,action:"open-setup"},VECTOR_NOT_BUILT:{type:"vectors_not_built",message:"Vector index has not been built yet",recoverable:!0,action:"open-vector-settings"},VECTOR_CORRUPTED:{type:"vectors_corrupted",message:"Vector index is corrupted",recoverable:!0,action:"force-rebuild"},MODEL_CHANGED:{type:"model_changed",message:"Embedding model has changed since vectors were built",recoverable:!0,action:"rebuild-vectors"},BACKEND_UNAVAILABLE:{type:"backend_unavailable",message:"Python CLI search backend is not responding",recoverable:!0,action:"run-doctor"},TIMEOUT:{type:"timeout",message:"Search timed out",recoverable:!0,action:"retry"},INTERNAL_ERROR:{type:"internal_error",message:"An internal error occurred",recoverable:!1}}[p];return t?{...t}:{type:"unknown",message:String(d),recoverable:!1}}function Nt(d,p,e){e===void 0&&(e=[]);let t=`paperforge==${p}`,r=`git+https://github.com/LLLin000/PaperForge.git@${p}`,n=[...e,"-m","pip","install","--upgrade",t],a=[...e,"-m","pip","install","--upgrade",r];return{cmd:d,url:r,args:a,pypiArgs:n,gitArgs:a,timeout:12e4}}function Vt(d,p,e,t,r,n){let a=r||me.spawn;return new Promise(s=>{let i=Date.now(),l={cwd:e,timeout:t,windowsHide:!0};n&&(l.env=n);let c=a(d,p,l),_=[],h=[];c.stdout.on("data",u=>{_.push(u.toString("utf-8"))}),c.stderr.on("data",u=>{h.push(u.toString("utf-8"))}),c.on("close",u=>{s({stdout:_.join(""),stderr:h.join(""),exitCode:u,elapsed:Date.now()-i})}),c.on("error",u=>{s({stdout:_.join(""),stderr:h.join("")+` -`+u.message,exitCode:-1,elapsed:Date.now()-i})})})}function mt(){if(It)return ft;It=!0;try{let d;if(process.platform==="win32"){let p=process.env.ComSpec||"C:\\Windows\\System32\\cmd.exe";d=(0,me.execFileSync)(p,["/c","where","git"],{timeout:5e3,windowsHide:!0,encoding:"utf-8"})}else d=(0,me.execFileSync)("which",["git"],{timeout:5e3,encoding:"utf-8"});if(d){let p=d.split(` -`)[0].trim();p&&(ft=_e.dirname(p))}}catch(d){}return ft}function Se(){let d={...process.env},p=process.platform,e=Mt.homedir(),t=[],r=mt();r&&t.push(r),p==="darwin"?t.push("/opt/homebrew/bin","/usr/local/bin","/usr/bin",`${e}/.local/bin`):p==="linux"&&t.push("/usr/local/bin","/usr/bin",`${e}/.local/bin`);let n=d.PATH||"";return d.PATH=[...t,n].filter(Boolean).join(_e.delimiter),d}function $t(d){return String(d).toLowerCase().replace(/[^a-z0-9]/g,"").includes("betterbibtex")}function yt(d){if(!d)return!1;try{if(!he.existsSync(d))return!1;for(let p of he.readdirSync(d))if($t(p))return!0}catch(p){}return!1}function Je(d){if(!d)return!1;try{if(!he.existsSync(d))return!1;for(let p of he.readdirSync(d)){let e=_e.join(d,p,"extensions");try{if(!he.existsSync(e))continue;for(let t of he.readdirSync(e))if($t(t))return!0}catch(t){}}}catch(p){}return!1}var xe=K(require("fs")),U=K(require("path")),vt=require("child_process"),fe=null;function kr(d,p){let e=p||xe,t=U.join(d,"paperforge.json"),r={system_dir:"System",resources_dir:"Resources",literature_dir:"Literature",base_dir:"Bases"};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||{};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,_warning:null}}catch(n){return console.warn("PaperForge: Failed to read paperforge.json, using defaults",n),{...r,_warning:"paperforge.json invalid; using defaults"}}}function ce(d,p){let e=kr(d,p),t=U.join(d,e.system_dir,"PaperForge");return{vault:d,systemDir:t,indexesDir:U.join(t,"indexes"),logsDir:U.join(t,"logs"),dbPath:U.join(t,"indexes","paperforge.db"),memoryStatePath:U.join(t,"indexes","memory-runtime-state.json"),vectorStatePath:U.join(t,"indexes","vector-runtime-state.json"),healthStatePath:U.join(t,"indexes","runtime-health.json"),buildStatePath:U.join(t,"indexes","vector-build-state.json"),orphanStatePath:U.join(t,"indexes","sync-orphan-state.json"),exportsDir:U.join(t,"exports"),ocrDir:U.join(t,"ocr"),pluginDataPath:U.join(d,".obsidian","plugins","paperforge","data.json"),pfJsonPath:U.join(d,"paperforge.json"),configWarning:e._warning}}function bt(d){try{return xe.existsSync(d)?JSON.parse(xe.readFileSync(d,"utf-8")):null}catch(p){return null}}function wr(d){let p=ce(d);return bt(p.memoryStatePath)}var Te=null;function Ge(d){let p=ce(d),e=Date.now();if(Te&&Te.vaultPath===d&&e-Te.ts<2e3)return Te.result;let t="",r=[U.join(d,".paperforge-test-venv","Scripts","python.exe"),U.join(d,".venv","Scripts","python.exe"),U.join(d,"venv","Scripts","python.exe")];for(let a=0;a({...a,_selected:!0,_idx:s})),this.vaultPath=r,this.py=n}_updateUI(){let e=this.orphans.filter(t=>t._selected);this._countEl.setText(o("orphan_delete_selected").replace("{count}",String(e.length))),this._selectAllBtn.setText(e.length===this.orphans.length?o("orphan_deselect_all"):o("orphan_select_all"));for(let t of this.orphans){let r=this._rowEls[t._idx];r&&r.toggleClass("paperforge-orphan-dimmed",!t._selected)}}onOpen(){let{contentEl:e}=this;e.addClass("paperforge-modal"),e.createEl("h2",{text:o("orphan_title").replace("{count}",String(this.orphans.length))}),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){let a=t.createEl("div",{cls:"paperforge-orphan-row"+(n._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"});i.createEl("span",{cls:"paperforge-orphan-key",text:n.citation_key||n.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"}),n.collection_path&&l.createEl("span",{cls:"paperforge-tag tag-collection",text:n.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",{cls:"paperforge-orphan-meta",text:c.join(" \xB7 ")}),s.createEl("div",{cls:"paperforge-orphan-explain",text:o("orphan_explain")}),a.addEventListener("click",()=>{n._selected=!n._selected,this._updateUI()})}let r=e.createEl("div",{cls:"paperforge-modal-actions"});this._selectAllBtn=r.createEl("button",{cls:"paperforge-step-btn",text:"Deselect all"}),this._selectAllBtn.addEventListener("click",()=>{let n=this.orphans.every(a=>a._selected);for(let a of this.orphans)a._selected=!n;this._updateUI()}),this._countEl=r.createEl("button",{cls:"paperforge-step-btn mod-cta",text:"Delete "+this.orphans.length+" selected"}),r.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 ne.Notice(o("orphan_none_selected"));return}if(this._countEl.setText("Deleting..."),this._countEl.setAttr("disabled",""),this._selectAllBtn.setAttr("disabled",""),!this.py||!this.py.path){new ne.Notice("PaperForge: Python not found"),this.close();return}let a=n.map(s=>s.key);(0,He.execFile)(this.py.path,[...this.py.extraArgs,"-m","paperforge","--vault",this.vaultPath,"prune","--force","--json",...a],{cwd:this.vaultPath,timeout:6e4},(s,i)=>{if(s){new ne.Notice("PaperForge: prune failed"),this.close();return}try{let l=JSON.parse(i),c=l.data&&l.data.deleted||[];new ne.Notice("Deleted "+c.length+" orphan workspace(s)")}catch(l){new ne.Notice("PaperForge: prune done")}this.close()})})}onClose(){this.contentEl.empty()}};function Xe(d,p,e){console.log("[PF] checkOrphanState called");try{let r=ce(e).orphanStatePath;if(!pe.existsSync(r)){console.log("[PF] orphan file NOT FOUND");return}console.log("[PF] orphan file FOUND");let n=pe.readFileSync(r,"utf-8"),s=JSON.parse(n).orphans||[];if(console.log("[PF] orphans count:",s.length),s.length===0)return;let i=ye(e,p.settings);console.log("[PF] py.path:",i?i.path:"null"),new Et(d,s,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 ne.Modal{constructor(e,t,r){super(e);this._pendingSave=null;this._showSkipConfirm=!1;this.plugin=t,this._step=1,this._onComplete=r}onOpen(){this._render()}onClose(){this.contentEl.empty()}_render(){let{contentEl:e}=this;e.empty(),e.addClass("paperforge-modal"),this._renderStepIndicator(),this._renderStepContent(),this._renderNavigation()}_renderStepIndicator(){let e=[o("wizard_step1"),o("wizard_step2"),o("wizard_step3"),o("wizard_step4"),o("wizard_step5")],t=this.contentEl.createEl("div",{cls:"paperforge-step-bar"});e.forEach((r,n)=>{let a=n+1,s=t.createEl("div",{cls:`paperforge-step-dot ${a===this._step?"active":""} ${a1&&e.createEl("button",{cls:"paperforge-step-btn",text:o("nav_prev")}).addEventListener("click",()=>{this._step--,this._showSkipConfirm=!1,this._render()}),this._step<5?e.createEl("button",{cls:"paperforge-step-btn mod-cta",text:o("nav_next")}).addEventListener("click",()=>{if(this._step===3){let r=this._validateStep3();if(r.blocked){if(r.reason==="zotero")return;if(r.reason==="ocr"){this._showSkipConfirm=!0,this._render();return}}}this._step++,this._showSkipConfirm=!1,this._render()}):e.createEl("button",{cls:"paperforge-step-btn",text:o("nav_close")}).addEventListener("click",()=>this.close())}_validateStep3(){let e=this.plugin.settings,t=Ht(this._apiKeyValidated,e.zotero_data_dir);if(t.reason==="ocr")return t;let r=(e.zotero_data_dir||"").trim();if(!r)return new ne.Notice("Zotero \u6570\u636E\u76EE\u5F55\u4E3A\u5FC5\u586B\u9879\uFF0C\u8BF7\u586B\u5199\u8DEF\u5F84"),{blocked:!0,reason:"zotero"};if(!pe.existsSync(r))return new ne.Notice("Zotero \u6570\u636E\u76EE\u5F55\u8DEF\u5F84\u4E0D\u5B58\u5728"),{blocked:!0,reason:"zotero"};if(!pe.statSync(r).isDirectory())return new ne.Notice("Zotero \u6570\u636E\u76EE\u5F55\u8DEF\u5F84\u4E0D\u662F\u4E00\u4E2A\u76EE\u5F55"),{blocked:!0,reason:"zotero"};let n=zt.join(r,"storage");return!pe.existsSync(n)||!pe.statSync(n).isDirectory()?(new ne.Notice("Zotero \u6570\u636E\u76EE\u5F55\u4E2D\u672A\u627E\u5230 storage/ \u5B50\u76EE\u5F55"),{blocked:!0,reason:"zotero"}):{blocked:!1}}_stepOverview(e){e.createEl("h2",{text:o("wizard_title")}),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"});a.textContent=`\u{1F4C1} Vault (${r})`;let s=n.createEl("div",{cls:"paperforge-dir-children"}),i=s.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"}).createEl("div",{cls:"paperforge-dir-node file",text:`\u{1F4C1} ${t.literature_dir||"Literature"}/ \u2014 \u6587\u732E\u5361\u7247`}),s.createEl("div",{cls:"paperforge-dir-node folder",text:`\u{1F4C1} ${t.base_dir||"Bases"}/ \u2014 \u6570\u636E\u7BA1\u7406\u9762\u677F`}),s.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`}),e.createEl("p",{text:o("wizard_preview"),cls:"paperforge-modal-hint"}),e.createEl("p",{text:o("wizard_safety"),cls:"paperforge-modal-hint"});let c=e.createEl("div",{cls:"paperforge-summary"}),_=[{label:o("dir_resources"),val:`${r}/${t.resources_dir||"Resources"}`},{label:o("dir_notes"),val:`${r}/${t.resources_dir||"Resources"}/${t.literature_dir||"Literature"}`},{label:o("dir_base"),val:`${r}/${t.base_dir||"Bases"}`},{label:o("dir_system"),val:`${r}/${t.system_dir||"System"}`}];for(let h of _){let u=c.createEl("div",{cls:"paperforge-summary-row"});u.createEl("span",{cls:"paperforge-summary-label",text:h.label}),u.createEl("span",{cls:"paperforge-summary-value",text:h.val})}}_stepDirectories(e){e.createEl("h2",{text:o("wizard_step2")}),e.createEl("p",{text:o("wizard_intro")});let t=this.plugin.settings,r=this.app.vault.adapter.basePath;this._modalField(e,o("dir_vault"),r,!0),e.createEl("p",{text:o("wizard_dir_hint"),cls:"paperforge-modal-hint"}),this._modalInput(e,"\u8D44\u6E90\u76EE\u5F55\uFF08\u521B\u5EFA\u6587\u732E\u5361\u7247\u76EE\u5F55\u7684\u5730\u65B9\uFF09","resources_dir",t.resources_dir,"Resources"),e.createEl("p",{text:o("wizard_dir_sub_hint"),cls:"paperforge-modal-hint"}),this._modalInput(e,"\u6587\u732E\u5361\u7247\u76EE\u5F55\uFF08\u5B58\u653E\u6587\u732E\u5361\u7247\u7684\u5730\u65B9\uFF0CBase \u6570\u636E\u6765\u6E90\uFF09","literature_dir",t.literature_dir,"Literature"),e.createEl("p",{text:o("wizard_sys_hint"),cls:"paperforge-modal-hint"}),this._modalInput(e,"\u7CFB\u7EDF\u76EE\u5F55\uFF08\u5B58\u653E Zotero \u8F6F\u94FE\u63A5\u548C PaperForge \u7CFB\u7EDF\u6587\u4EF6\uFF09","system_dir",t.system_dir,"System"),this._modalInput(e,"Base \u76EE\u5F55\uFF08\u5B58\u653E\u6570\u636E\u7BA1\u7406\u9762\u677F\u7684\u5730\u65B9\uFF09","base_dir",t.base_dir,"Bases"),e.createEl("p",{text:o("wizard_safety"),cls:"paperforge-modal-hint"});let n=e.createEl("div",{cls:"paperforge-summary"}),a=[{label:o("dir_resources"),val:`${r}/${t.resources_dir||""}`},{label:o("dir_notes"),val:`${r}/${t.resources_dir||""}/${t.literature_dir||""}`},{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})}}_stepKeys(e){if(e.createEl("h2",{text:o("wizard_step3")}),this._showSkipConfirm){this._renderSkipConfirm(e);return}let t=this.plugin.settings;e.createEl("p",{text:o("wizard_agent_hint"),cls:"paperforge-modal-hint"});let r=[{key:"opencode",name:"OpenCode"},{key:"claude",name:"Claude Code"},{key:"cursor",name:"Cursor"},{key:"github_copilot",name:"GitHub Copilot"},{key:"windsurf",name:"Windsurf"},{key:"codex",name:"Codex"},{key:"gemini",name:"Gemini CLI"},{key:"cline",name:"Cline"}],n=e.createEl("div",{cls:"paperforge-modal-field"});n.createEl("label",{cls:"paperforge-modal-label",text:o("label_agent")});let a=n.createEl("select",{cls:"paperforge-modal-select"});for(let h of r){let u=a.createEl("option",{text:h.name,attr:{value:h.key}});h.key===(t.agent_platform||"opencode")&&(u.selected=!0)}a.addEventListener("change",()=>{t.agent_platform=a.value,this._pendingSave&&clearTimeout(this._pendingSave),this._pendingSave=setTimeout(()=>{this.plugin.saveSettings(),this._pendingSave=null},500)}),e.createEl("p",{text:o("wizard_keys_hint"),cls:"paperforge-modal-hint"});let s=e.createEl("div",{cls:"paperforge-modal-field"});s.createEl("label",{cls:"paperforge-modal-label",text:o("field_paddleocr")});let i=s.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",{cls:"paperforge-apikey-status",text:""});let l=s.createEl("button",{cls:"paperforge-step-btn",text:"\u9A8C\u8BC1"});l.addEventListener("click",()=>this._validateApiKey(i.value,l)),i.addEventListener("input",()=>{t.paddleocr_api_key=i.value,this._apiKeyValidated=!1,this._apiKeyStatus.textContent="",this._apiKeyStatus.className="paperforge-apikey-status"}),this._pendingSave&&clearTimeout(this._pendingSave),this._pendingSave=setTimeout(()=>{this.plugin.saveSettings(),this._pendingSave=null},500),e.createEl("p",{text:o("wizard_api_hint_skip"),cls:"paperforge-modal-hint"});let c=e.createEl("div",{cls:"paperforge-modal-field"});c.createEl("label",{cls:"paperforge-modal-label",text:o("field_zotero_data")});let _=c.createEl("input",{cls:"paperforge-modal-input",attr:{type:"text",placeholder:o("field_zotero_placeholder")}});_.value=t.zotero_data_dir||"",_.addEventListener("input",()=>{t.zotero_data_dir=_.value,this._pendingSave&&clearTimeout(this._pendingSave),this._pendingSave=setTimeout(()=>{this.plugin.saveSettings(),this._pendingSave=null},500)})}_validateApiKey(e,t){if(!e||e.length<10){this._apiKeyStatus.textContent="\u5BC6\u94A5\u683C\u5F0F\u4E0D\u6B63\u786E\u3002\u53EF\u70B9\u4E0B\u4E00\u6B65\u8DF3\u8FC7\uFF0C\u7B49\u4F1A\u513F\u518D\u914D\u7F6E\u3002",this._apiKeyStatus.className="paperforge-apikey-status error";return}t.disabled=!0,t.textContent="\u9A8C\u8BC1\u4E2D\u2026",this._apiKeyStatus.textContent="\u6B63\u5728\u9A8C\u8BC1\u2026",this._apiKeyStatus.className="paperforge-apikey-status";let r=JSON.stringify({model:"PaddleOCR-VL-1.5"}),n={hostname:"paddleocr.aistudio-app.com",path:"/api/v2/ocr/jobs",method:"POST",headers:{Authorization:"bearer "+e,"Content-Type":"application/json","Content-Length":Buffer.byteLength(r)},timeout:1e4},a=Kt.request(n,s=>{t.disabled=!1,t.textContent="\u9A8C\u8BC1";let i="";s.on("data",l=>i+=l),s.on("end",()=>{try{let l=JSON.parse(i);s.statusCode===400&&l.code===10001?(this._apiKeyStatus.textContent="\u2713 \u5BC6\u94A5\u6709\u6548",this._apiKeyStatus.className="paperforge-apikey-status ok",this._apiKeyValidated=!0):s.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="paperforge-apikey-status error",this._apiKeyValidated=!1):(this._apiKeyStatus.textContent="\u9A8C\u8BC1\u5931\u8D25\uFF1AAPI \u8FD4\u56DE "+s.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){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="paperforge-apikey-status error",this._apiKeyValidated=!1}})});a.on("error",s=>{t.disabled=!1,t.textContent="\u9A8C\u8BC1",this._apiKeyStatus.textContent="\u9A8C\u8BC1\u5931\u8D25\uFF1A\u65E0\u6CD5\u8FDE\u63A5 ("+s.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}),a.write(r),a.end()}_renderSkipConfirm(e){e.createEl("p",{text:o("wizard_skip_ocr_desc"),cls:"paperforge-modal-desc"});let t=e.createEl("div",{cls:"paperforge-modal-actions"});t.createEl("button",{cls:"paperforge-step-btn mod-cta",text:o("wizard_skip_ocr_continue")}).addEventListener("click",()=>{this._showSkipConfirm=!1,this._step++,this._render()}),t.createEl("button",{cls:"paperforge-step-btn",text:o("wizard_skip_ocr_back")}).addEventListener("click",()=>{this._showSkipConfirm=!1,this._render()})}_modalField(e,t,r,n){let a=e.createEl("div",{cls:"paperforge-modal-field"});a.createEl("label",{cls:"paperforge-modal-label",text:t});let s=a.createEl("input",{cls:"paperforge-modal-input",attr:{type:"text"}});s.value=r,s.disabled=!!n}_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",{cls:"paperforge-modal-input",attr:{type:"text",placeholder:a||""}});i.value=n;let l=this.plugin.settings;i.addEventListener("input",()=>{l[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",{cls:"paperforge-modal-input",attr:{type:"password",placeholder:a||""}});i.value=n;let l=this.plugin.settings;i.addEventListener("input",()=>{l[r]=i.value,this._pendingSave&&clearTimeout(this._pendingSave),this._pendingSave=setTimeout(()=>{this.plugin.saveSettings(),this._pendingSave=null},500)})}_stepInstall(e){e.createEl("h2",{text:o("wizard_step4")}),this._installLog=e.createEl("div",{cls:"paperforge-install-log"});let t=e.createEl("button",{cls:"paperforge-step-btn mod-cta",text:o("install_btn")});t.addEventListener("click",()=>this._runInstall(t))}async _runInstall(e){var s,i,l,c,_,h;e.disabled=!0,e.textContent=o("install_btn_running"),this._installLog.setText(o("install_validating")+` -`),this._log(o("install_validating"));let t=this.plugin.settings,r=this._validate();if(r.length>0){this._log(o("validate_fail")+":"),r.forEach(u=>this._log(" \u2717 "+u)),e.disabled=!1,e.textContent=o("install_btn_retry");return}let n=(u,m={})=>new Promise((f,x)=>{let{path:g,extraArgs:b=[]}=z(t.vault_path.trim(),this.plugin.settings,void 0,void 0),E=(0,He.spawn)(g,[...b,...u],{cwd:t.vault_path.trim(),env:Se(),timeout:12e4,...m}),k="",y="";E.stdout.on("data",v=>{let S=v.toString("utf-8");k+=S,m.logStdout&&this._processSetupOutput(S)}),E.stderr.on("data",v=>{let S=v.toString("utf-8");y+=S,this._log("[stderr] "+S.trim())}),E.on("close",v=>{v===0?f({stdout:k,stderr:y}):x(new Error(y.trim()||k.trim()||`exit code ${v}`))}),E.on("error",v=>x(v))}),a=["-m","paperforge","--vault",t.vault_path.trim(),"setup","--headless","--system-dir",t.system_dir.trim(),"--resources-dir",t.resources_dir.trim(),"--literature-dir",t.literature_dir.trim(),"--base-dir",t.base_dir.trim(),"--agent",t.agent_platform||"opencode"];t.zotero_data_dir&&t.zotero_data_dir.trim()&&a.push("--zotero-data",t.zotero_data_dir.trim()),t.paddleocr_api_key&&t.paddleocr_api_key.trim()&&a.push("--paddleocr-key",t.paddleocr_api_key.trim());try{let u=!0;try{await n(["-c","import paperforge"])}catch(m){u=!1}if(!u){this._log(o("install_bootstrapping"));let m=this.plugin.manifest.version;this._log(`[install] Trying PyPI: pip install paperforge==${m}`);let f=["-m","pip","install","--upgrade"];process.platform!=="win32"&&f.push("--user"),f.push(`paperforge==${m}`);try{await n(f,{logStdout:!0})}catch(x){this._log(`[install] PyPI failed, falling back to git: git+https://...@v${m}`),console.warn("[PaperForge] PyPI install failed, falling back to git:",(s=x.message)==null?void 0:s.slice(0,200));let g=["-m","pip","install","--upgrade"];process.platform!=="win32"&&g.push("--user"),g.push(`git+https://github.com/LLLin000/PaperForge.git@v${m}`),await n(g,{logStdout:!0})}}await n(a,{logStdout:!0,env:Se()}),this._log(o("install_complete")),t.setup_complete=!0,await this.plugin.saveSettings(),this._onComplete&&this._onComplete(),setTimeout(()=>{this._step=5,this._render()},800)}catch(u){console.error("PaperForge setup failed:",u.message);let m=this._formatSetupError(u.message);this._log(o("install_failed")+m);let f=(i=this._installLog.parentElement)==null?void 0:i.createEl("button",{cls:"paperforge-copy-diag-btn",text:o("error_copy_diagnostic")||"Copy diagnostic"});if(f){let x=u.message,g=((c=(l=this.plugin)==null?void 0:l.settings)==null?void 0:c.python_path)||"auto",b=((h=(_=this.plugin)==null?void 0:_.manifest)==null?void 0:h.version)||"?",E=process.platform+" "+process.arch,k,y;try{k=mt()||"(not found)"}catch(P){k="(error)"}try{y=z(t.vault_path.trim(),this.plugin.settings,void 0,void 0)}catch(P){y=null}let v=(process.env.PATH||"").length,S=(process.env.PATH||"").toLowerCase().includes("git"),C=["[PaperForge Diagnostic]","Category: "+m,"Plugin version: "+b,"Python: "+g,"Resolved Python: "+((y==null?void 0:y.path)||"?"),"OS: "+E,"Vault path: "+(t.vault_path||"?"),"--- Git ---","Git dir (resolved): "+k,"PATH length: "+v+" chars","PATH contains git: "+S,"--- Raw error ---",x.slice(0,2e3)].join(` -`);f.addEventListener("click",()=>{navigator.clipboard.writeText(C).then(()=>{f.setText(o("error_copied")||"Copied!"),setTimeout(()=>{f.setText(o("error_copy_diagnostic")||"Copy diagnostic")},3e3)}).catch(()=>{new ne.Notice("[!!] Clipboard write failed",6e3)})})}e.disabled=!1,e.textContent=o("install_btn_retry")}}_log(e){this._installLog&&this._installLog.setText(this._installLog.textContent+e+` -`)}_validate(){let e=[],t=this.plugin.settings;return(!t.vault_path||!t.vault_path.trim())&&e.push(o("validate_vault")),(!t.resources_dir||!t.resources_dir.trim())&&e.push(o("validate_resources")),(!t.literature_dir||!t.literature_dir.trim())&&e.push(o("validate_notes")),(!t.base_dir||!t.base_dir.trim())&&e.push(o("validate_base")),(!t.paddleocr_api_key||!t.paddleocr_api_key.trim())&&this._log(" ! "+o("validate_key")+" "+o("optional_later")),(!t.zotero_data_dir||!t.zotero_data_dir.trim())&&this._log(" ! "+o("validate_zotero")+" "+o("optional_later")),e}_processSetupOutput(e){let t=e.split(` +"use strict";var cr=Object.create;var Je=Object.defineProperty;var pr=Object.getOwnPropertyDescriptor;var dr=Object.getOwnPropertyNames;var ur=Object.getPrototypeOf,_r=Object.prototype.hasOwnProperty;var hr=(d,p)=>()=>(p||d((p={exports:{}}).exports,p),p.exports),fr=(d,p)=>{for(var e in p)Je(d,e,{get:p[e],enumerable:!0})},Ft=(d,p,e,t)=>{if(p&&typeof p=="object"||typeof p=="function")for(let r of dr(p))!_r.call(d,r)&&r!==e&&Je(d,r,{get:()=>p[r],enumerable:!(t=pr(p,r))||t.enumerable});return d};var K=(d,p,e)=>(e=d!=null?cr(ur(d)):{},Ft(p||!d||!d.__esModule?Je(e,"default",{value:d,enumerable:!0}):e,d)),gr=d=>Ft(Je({},"__esModule",{value:!0}),d);var ft=hr((Ir,xr)=>{xr.exports={versions:[{version:"1.5.15",date:"2026-06-01",title:"\u5168\u6587\u5B58\u50A8\u91CD\u6784 + OCR \u9605\u8BFB\u987A\u5E8F\u4FEE\u590D + Redo \u4E00\u952E\u91CD\u505A",breaking_or_migration:["\u5168\u6587\u6587\u4EF6\u73B0\u5728\u7EDF\u4E00\u5B58\u653E\u4E8E System/PaperForge/ocr/ \u4E0B\uFF0C\u4E0D\u518D\u5728\u5DE5\u4F5C\u533A\u4FDD\u7559\u526F\u672C","Redo OCR \u73B0\u5728\u4F1A\u7ACB\u5373\u6267\u884C\uFF08\u4E00\u952E\u5B8C\u6210\uFF09\uFF0C\u4E0D\u518D\u9700\u8981\u624B\u52A8\u518D\u8DD1\u4E00\u6B21"],new_features:["Redo OCR \u4E00\u952E\u95ED\u73AF\uFF1A\u52FE\u9009 \u2192 \u70B9\u6309\u94AE \u2192 \u81EA\u52A8\u5B8C\u6210\u5168\u90E8\u6D41\u7A0B","\u8BBE\u7F6E\u9875\u65B0\u589E\u300C\u66F4\u65B0\u4E0E\u624B\u518C\u300D\u6807\u7B7E\u9875\uFF0C\u53EF\u968F\u65F6\u67E5\u770B\u7248\u672C\u66F4\u65B0\u8BB0\u5F55\u548C\u4F7F\u7528\u624B\u518C","\u63D2\u4EF6\u66F4\u65B0\u540E\u81EA\u52A8\u5F39\u51FA\u66F4\u65B0\u8BF4\u660E"],fixes:["\u4FEE\u590D\u5168\u6587\u9605\u8BFB\u987A\u5E8F\u6DF7\u4E71\uFF0C\u4F18\u5316\u6574\u4F53\u6392\u7248\u4F53\u9A8C","\u4FEE\u590D\u7AE0\u8282\u6807\u9898\u548C\u6B63\u6587\u6BB5\u843D\u9519\u4F4D\u65AD\u5F00\u7684\u95EE\u9898","\u4FEE\u590D\u56FE\u8868\u548C\u5BF9\u5E94\u56FE\u6CE8\u88AB\u5206\u5F00\u7684\u95EE\u9898","\u4FEE\u590D\u9996\u9875\u6458\u8981\u533A\u5757\u6392\u5E8F\u5F02\u5E38","\u4FEE\u590D\u5E76\u6392\u56FE\u7247\u672A\u80FD\u81EA\u52A8\u5408\u5E76\u7684\u95EE\u9898","Dashboard \u73B0\u5728\u80FD\u6B63\u786E\u8BC6\u522B\u65B0\u7684\u5168\u6587\u6587\u4EF6\u4F4D\u7F6E"],recommended_actions:["\u65E7\u7248 OCR \u5168\u6587\u53EF\u80FD\u5B58\u5728\u9605\u8BFB\u987A\u5E8F\u95EE\u9898\uFF0C\u5EFA\u8BAE\u5BF9\u91CD\u8981\u8BBA\u6587\u6267\u884C\u4E00\u6B21 Redo OCR","\u6253\u5F00\u5168\u6587\u8BF7\u76F4\u63A5\u4F7F\u7528 Dashboard \u7684\u300C\u6253\u5F00\u5168\u6587\u300D\u6309\u94AE"]}]}});var Dr={};fr(Dr,{default:()=>ot});module.exports=gr(Dr);var X=require("obsidian"),q=K(require("fs")),Ie=K(require("path")),ge=require("child_process");var Pe="paperforge-status",Ne="paperforge",Tt='',ie=[{id:"paperforge-sync",title:"Sync Library",desc:"Pull new references from Zotero and generate literature notes",icon:"\u21BB",cmd:"sync",okMsg:"Sync complete"},{id:"paperforge-ocr",title:"Run OCR",desc:"Extract full text and figures from PDFs via PaddleOCR",icon:"\u229E",cmd:"ocr",okMsg:"OCR started"},{id:"paperforge-doctor",title:"Run Doctor",desc:"Verify PaperForge setup \u2014 check configs, Zotero, paths, and index health",icon:"\u2695",cmd:"doctor",okMsg:"Doctor complete"},{id:"paperforge-repair",title:"Repair Issues",desc:"Fix three-way state divergence, path errors, and rebuild index",icon:"\u21BA",cmd:"repair",args:["--fix","--fix-paths"],okMsg:"Repair complete"},{id:"paperforge-ocr-redo",title:"Redo OCR",desc:"Re-run OCR for papers marked ocr_redo: true",icon:"\u21BA",cmd:"ocr",args:["redo"],okMsg:"OCR redo started"}],Ve={vault_path:"",setup_complete:!1,auto_update:!0,auto_update_on_startup:!0,agent_platform:"opencode",language:"",paddleocr_api_key:"",zotero_data_dir:"",python_path:"",features:{memory_layer:!0,vector_db:!1},selected_skill_platform:"opencode",vector_db_api_key:"",vector_db_api_base:"",vector_db_api_model:"text-embedding-3-small",frozen_skills:{},system_dir:"",resources_dir:"",literature_dir:"",base_dir:"",capabilityState:{},last_seen_version:""};function At(d,p){if(!p||!p.note_path)return p;let e=d.vault.getAbstractFileByPath(p.note_path);if(!e)return p;let t=d.metadataCache.getFileCache(e),r=t&&t.frontmatter;if(!r)return p;let n={...p};for(let a of["do_ocr","analyze","ocr_status","ocr_redo","deep_reading_status"])Object.prototype.hasOwnProperty.call(r,a)&&(n[a]=r[a]);return n}function lt(d,p){return d&&{...d,...p}}var ct=1,Fe=["installation","library","ocr","memory","maintenance","help"],mr=["unknown","unavailable","missing_input","needs_action","limited","ready"],yr=["unknown","ok","warning","error"],vr=["idle","running"];function br(d){if(!d||typeof d!="object"||Array.isArray(d))return!1;let p=d;return!(typeof p.verb!="string"||typeof p.label!="string"||typeof p.destructive!="boolean"||p.destructive_scope!==null&&typeof p.destructive_scope!="string"||p.destructive_effect!==null&&typeof p.destructive_effect!="string"||typeof p.confirmation_required!="boolean"||p.confirmation_prompt!==null&&typeof p.confirmation_prompt!="string"||typeof p.command!="string"||typeof p.scope!="string"||typeof p.scope_count!="number")}function $e(d){return{verb:"probe",label:"Check",destructive:!1,destructive_scope:null,destructive_effect:null,confirmation_required:!1,confirmation_prompt:null,command:`probe ${d}`,scope:d,scope_count:1}}function Dt(){return{verb:"setup",label:"Open Setup Wizard",destructive:!1,destructive_scope:null,destructive_effect:null,confirmation_required:!1,confirmation_prompt:null,command:"setup",scope:"installation",scope_count:1}}function pt(d,p){if(!d||typeof d!="object")return!1;let e=d;if(e.schema_version!==1||typeof e.module!="string"||!e.module||!Fe.includes(e.module)||p!==void 0&&e.module!==p||typeof e.capability_state!="string"||!mr.includes(e.capability_state)||typeof e.severity!="string"||!yr.includes(e.severity)||typeof e.activity_state!="string"||!vr.includes(e.activity_state)||e.activity_label!==null&&typeof e.activity_label!="string")return!1;if(e.activity_progress!==null){if(typeof e.activity_progress!="object")return!1;let n=e.activity_progress;if(typeof n.current!="number"||typeof n.total!="number")return!1}if(!Array.isArray(e.notices)||!e.reason||typeof e.reason!="object")return!1;let t=e.reason;if(typeof t.code!="string"||typeof t.text!="string"||!e.action||typeof e.action!="object")return!1;let r=e.action;return!(r.primary!==null&&!br(r.primary)||typeof e.updated_at!="string"||!e.updated_at||typeof e.ttl_seconds!="number")}function Se(d){return{schema_version:ct,module:d,capability_state:"unknown",activity_state:"idle",activity_label:null,activity_progress:null,severity:"unknown",reason:{code:`${d}.no_probe`,text:`${d} has not been probed yet.`},action:{primary:$e(d)},notices:[],updated_at:new Date(0).toISOString(),ttl_seconds:0}}function dt(d){return{schema_version:ct,module:d,capability_state:"unknown",activity_state:"idle",activity_label:null,activity_progress:null,severity:"unknown",reason:{code:`${d}.stale`,text:`Cached probe data for ${d} is stale.`},action:{primary:$e(d)},notices:[],updated_at:new Date(0).toISOString(),ttl_seconds:0}}function Te(d){return{schema_version:ct,module:d,capability_state:"unknown",activity_state:"idle",activity_label:null,activity_progress:null,severity:"unknown",reason:{code:`${d}.invalid_response`,text:`Probe response for ${d} was invalid.`},action:{primary:$e(d)},notices:[],updated_at:new Date(0).toISOString(),ttl_seconds:0}}function ut(d){if(d.activity_state==="running")return!1;if(d.ttl_seconds<=0)return!0;let p=new Date(d.updated_at).getTime();return isNaN(p)?!0:Date.now()-p>d.ttl_seconds*1e3}function je(d){return d.capability_state==="ready"&&d.action.primary===null}function He(d){var r,n,a;let p=(r=d.action)==null?void 0:r.primary,e=(n=p==null?void 0:p.verb)!=null?n:"probe",t=(a=p==null?void 0:p.label)!=null?a:e;return e==="setup"||e==="set_config"||e==="update"?{kind:"setup",verb:e,label:t}:e==="probe"?{kind:"probe",verb:e,label:t}:{kind:"action",verb:e,label:t}}function Ot(d,p){let e={};for(let t of p){let r=d[t];if(!r||typeof r!="object"){e[t]=Se(t);continue}if(!pt(r,t)){e[t]=Te(t);continue}if(ut(r)){e[t]=dt(t);continue}e[t]=r}return e}var _t={en:{action_running:"Running ",api_key_missing:"Missing",api_key_set:"Entered",btn_install:"Open Setup Wizard",btn_install_desc:"Check whether the environment is ready, then open the step-by-step setup wizard",btn_reconfig:"Reconfigure",btn_reconfig_desc:"Open the setup wizard again to change directories, platform, or API keys",btn_validate:"Validate",check_bbt_fail:"Not detected",check_bbt_ok:"Installed",check_python_fail:"Not found",check_python_ok:"Ready",check_zotero_fail:"Not detected",check_zotero_ok:"Found",complete_export_path:"Save Better BibTeX JSON exports into:",complete_next:"Recommended next steps",complete_step1:"Open Dashboard",complete_step1_desc:'Press Ctrl+P and run "PaperForge: Open Main Panel", or click the PaperForge icon in the left sidebar.',complete_step2:"Sync Literature",complete_step2_desc:"In the main panel, click Sync Library to bring papers from Zotero into Obsidian and generate notes.",complete_step3:"Run OCR",complete_step3_desc:"In the Obsidian Base view, mark do_ocr:true on papers, then run OCR in the main panel.",complete_step4:"Configure Better BibTeX Auto-export",complete_step4_desc:'In Zotero, right-click the library or collection you want to sync -> Export -> Better BibTeX JSON -> enable "Keep updated".',complete_summary:"Saved Configuration",complete_title:"Setup Complete",copied:"Copied!",copy_pf_deep_cmd:"Copy /pf-deep Command",dashboard_drift_warning:"PaperForge CLI (v{0}) differs from plugin (v{1}). Open Settings \u2192 Runtime Health to sync.",deep_reading_not_found:"Deep reading file not found",desc:"Obsidian + Zotero literature pipeline. Sync papers, generate notes, run OCR, and read deeply in one place.",dir_base:"Base Dir",dir_index:"Index Dir",dir_notes:"Notes Dir",dir_resources:"Resource Dir",dir_system:"System Dir",dir_vault:"Vault Path",error_copied:"Copied!",error_copy_diagnostic:"Copy diagnostic",feat_agent_platform:"Agent Platform",feat_agent_platform_desc:"Select which agent platform to manage skills for.",feat_api_base_url:"API Base URL",feat_api_base_url_desc:"Custom OpenAI-compatible API endpoint. Leave empty for default.",feat_api_model:"API Model",feat_api_model_desc:"Embedding model name for this endpoint.",feat_build_btn:"Build",feat_build_complete:"Vector build complete.",feat_build_failed:"Build failed. See terminal output.",feat_building:"Building...",feat_cache_remove_failed:"Failed: {0}",feat_cache_removed:"Model cache removed.",feat_checking:"Checking...",feat_checking_btn:"Checking...",feat_deps_checking:"Checking dependencies...",feat_deps_missing:"Dependencies not installed. Required: chromadb, openai.",feat_enter_key:"Enter a valid OpenAI API key.",feat_install_btn:"Install",feat_install_deps:"Install Dependencies",feat_install_deps_desc:"pip install chromadb openai (~35MB).",feat_install_done:"Dependencies installed. Building vectors...",feat_install_failed:"Install failed: ",feat_installing:"Installing...",feat_installing_pkgs:"Installing {pkgs}...",feat_key_rejected:"API key rejected.",feat_memory_desc:"The Memory Layer is the core data engine of PaperForge, powered by SQLite. It integrates literature metadata (papers, assets, aliases, reading events), provides FTS5 metadata search across titles, abstracts, authors, domains, and collections, and powers agent-context and paper-status. Always active \u2014 no toggle needed.",feat_memory_rebuild_btn:"Rebuild",feat_memory_rebuild_done:"Memory DB rebuilt.",feat_memory_rebuild_failed:"Rebuild failed.",feat_memory_rebuilding:"Rebuilding...",feat_model_changed_warn:"Model changed ({0} -> {1}). Existing vectors are incompatible \u2014 rebuild required.",feat_network_error:"Network error: ",feat_no_python:"No Python found. Check Installation tab.",feat_not_cached:"Not cached",feat_openai_key:"OpenAI API Key",feat_openai_key_desc:"Used for API embedding calls. Model is defined below.",feat_output_copied:"Output copied to clipboard.",feat_rebuild_btn:"Rebuild",feat_rebuild_vectors:"Rebuild Vectors",feat_rebuild_vectors_changed:"Model changed \u2014 rebuild to update all vectors.",feat_rebuild_vectors_desc:"Rebuild all OCR fulltext vectors. Required after model or mode change.",feat_removing:"Removing...",feat_retry_btn:"Retry",feat_skills_desc:"Manage and enable/disable agent skills installed in your vault. Each row corresponds to a SKILL.md file \u2014 toggle off to prevent the agent from auto-invoking that skill.",feat_skills_system:"System Skills ship with PaperForge and are updated alongside PaperForge.",feat_skills_user:"User Skills are custom skills you install from community or create yourself.",feat_uninstall_btn:"Uninstall",feat_valid_key:"API key valid.",feat_vector_config_label:"Vector Settings",feat_vector_corrupted:"Vector index corrupted \u2014 needs force rebuild.",feat_vector_desc:"Vector Database enables semantic search across OCR-extracted fulltext via API embedding. Documents are split into chunks, embedded via OpenAI-compatible API, and stored in ChromaDB.",feat_vector_enable:"Enable Vector Retrieval",feat_vector_enable_desc:"Semantic search across OCR fulltext. Requires: pip install openai chromadb (~35MB).",feat_vector_rebuild_force_btn:"Force Rebuild",feat_verify:"Verify",feat_verify_btn:"Verify",field_paddleocr:"PaddleOCR API Key",field_python_custom:"Custom Path",field_python_interp:"Python Interpreter",field_zotero_data:"Zotero Data Dir",field_zotero_placeholder:"Required. Path to Zotero data directory for PDF attachment resolution.",guide_ocr:"Run OCR",guide_ocr_desc:"In the main panel, click Run OCR to extract full text and figures from PDFs for later reading and analysis.",guide_open:"Open Main Panel",guide_open_desc:'Press Ctrl+P and run "PaperForge: Open Main Panel", or click the PaperForge icon in the left sidebar.',guide_sync:"Sync Literature",guide_sync_desc:"After Better BibTeX JSON export is configured, click Sync Library to import papers from Zotero into Obsidian and generate notes automatically.",header_title:"PaperForge",install_bootstrapping:"PaperForge Python package not found. Installing automatically...",install_btn:"Start Install",install_btn_retry:"Retry",install_btn_running:"Installing...",install_complete:"Installation complete!",install_failed:"Installation failed: ",install_validating:"Validating setup...",jump_to_deep_reading:"Open Deep Reading",label_agent:"Agent Platform",nav_close:"Close",nav_next:"Next",nav_prev:"Back",not_set:"Not entered",notice_check_fail:"Missing: ",notice_python_missing:"Python was not detected. Install Python 3.10+ and add it to PATH.",ocr_privacy_title:"OCR Privacy Notice",ocr_privacy_warning:"OCR will upload PDFs to the PaddleOCR API. Do not upload sensitive or confidential documents.",ocr_queue_add:"Add to OCR Queue",ocr_queue_added:"Added to OCR queue",ocr_queue_remove:"Remove from OCR Queue",ocr_queue_removed:"Removed from OCR queue",ocr_understand:"I understand, continue",optional_later:"(can be set later in Settings)",orphan_delete_failed:"Prune failed",orphan_delete_selected:"Delete {count} selected",orphan_deleted:"Deleted {count} orphan workspace(s)",orphan_desc:"These papers are no longer in your Zotero library.",orphan_deselect_all:"Deselect all",orphan_explain:"Removed from Zotero. Workspace files remain on disk.",orphan_keep_all:"Keep all",orphan_none_selected:"No papers selected for deletion",orphan_select_all:"Select all",orphan_title:"Found {count} orphan paper(s)",panel_actions:"Quick Actions",prep_bbt:"Better BibTeX",prep_bbt_desc:"In Zotero: Tools -> Add-ons -> install Better BibTeX.",prep_export:"Better BibTeX Auto-export",prep_export_desc:'In Zotero, right-click the collection you want to sync -> Export Collection -> BetterBibTeX JSON -> enable "Keep updated" -> save the JSON file into the exports folder shown below. Obsidian Base views will use the JSON filename as the Base name:',prep_export_path_label:"Save the exported JSON file into this folder:",prep_key:"PaddleOCR Key",prep_key_desc:"Get your API key from https://aistudio.baidu.com/paddleocr",prep_python:"Python 3.10+",prep_python_desc:"Python must be available from the command line. If you are not sure, click below to auto-detect.",prep_zotero:"Zotero Desktop",prep_zotero_desc:"Install Zotero from https://www.zotero.org",run_in_agent:"Run in {0}",runtime_health:"Runtime Health",runtime_health_checking:"Checking...",runtime_health_desc:"Check whether the installed paperforge Python package matches the plugin version and whether the deployed skill contract is current.",runtime_health_match:"Match",runtime_health_mismatch:"Mismatch",runtime_health_package_ver:"Python package v{0}",runtime_health_plugin_ver:"Plugin v{0}",runtime_health_sync:"Sync Runtime",runtime_health_sync_done:"Runtime synced to v{0}",runtime_health_sync_fail:"Sync failed: {0}",runtime_health_syncing:"Syncing...",section_config:"Current Configuration",section_guide:"How To Use",section_prep:"Preparation",section_prep_desc:"Before first use, finish these 4 preparation items. Better BibTeX auto-export is configured after setup:",setup_done:"PaperForge environment is ready",setup_pending:"Not installed yet. Finish the preparation items below, then open the wizard.",tab_overview:"Overview",tab_modules:"Module Detail",tab_help:"Help",tab_setup:"Installation",tab_features:"Features",tab_maintenance:"Maintenance",validate_base:"Base directory is required",validate_fail:"Please complete the required fields below",validate_index:"Index directory is required",validate_key:"PaddleOCR API key (optional, needed for OCR)",validate_notes:"Notes directory is required",validate_resources:"Resources directory is required",validate_system:"System directory is required",validate_vault:"Vault path is required",validate_zotero:"Zotero data directory (optional, needed for PDF linking)",wizard_agent_hint:"Choose the AI agent platform you use most often. PaperForge will place the matching command and skill files in the correct location.",wizard_dir_hint:"PaperForge stores user-facing literature data under the resources directory. These folders will live there:",wizard_dir_sub_hint:"Resolved folder preview based on the names below:",wizard_intro:"This wizard walks you through the full setup. In most cases, the default values are fine to keep.",wizard_keys_hint:"Enter your PaddleOCR API key below. If you want PaperForge to auto-locate Zotero PDFs, you can also fill in the Zotero data directory.",wizard_preview:"After installation, system files stay at the vault root while literature data stays under the resources directory.",wizard_safety:"Safety: if the selected folders already contain files, setup preserves existing files and only creates missing PaperForge folders and files.",wizard_step1:"Overview",wizard_step2:"Directory Setup",wizard_step3:"Platform & Keys",wizard_step4:"Install",wizard_step5:"Done",wizard_skip_ocr_desc:"OCR will not be available until you configure a valid PaddleOCR API key. You can continue setup now and configure it later in Settings.",wizard_skip_ocr_continue:"Continue without OCR key",wizard_skip_ocr_back:"Back to configure",wizard_api_hint_skip:"OCR key is optional \u2014 you may skip it and configure later.",wizard_sys_hint:"These folders live at the vault root, outside the resources directory:",wizard_title:"PaperForge Setup Wizard",ocr_maint_no_action:"No Action Needed",ocr_maint_rebuild:"Rebuild Recommended",ocr_maint_failed:"OCR Failed",ocr_maint_limited:"Result Limited",ocr_maint_needs_attention:"Needs Attention",ocr_maint_limitations:"Result Limitations",ocr_maint_hero_ok:"OCR looks usable overall.",ocr_maint_hero_warn:"OCR needs attention: {rebuild} rebuild recommended, {failed} failed.",ocr_maint_hero_note:"This page only promotes issues where maintenance is likely to help. Some papers may have limitations that maintenance will not improve.",ocr_maint_limitations_intro:"These papers look less certain, but PaperForge does not currently have a high-confidence maintenance action to recommend.",ocr_maint_all_papers:"All Papers",ocr_maint_rebuild_btn:"Rebuild results",ocr_maint_redo_btn:"Rerun OCR",ocr_maint_redo_confirm:"Rerun OCR for {n} paper(s)? Existing derived OCR artifacts will be replaced.",maintenance_group_retry:"Needs Retry",maintenance_group_rebuild:"Can Rebuild",maintenance_group_legacy:"Upgrade Available (Optional)",maintenance_btn_retry:"Retry",maintenance_btn_rebuild:"Rebuild",maintenance_btn_upgrade:"Upgrade",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...",version_papers_count:"{n} papers",version_current:"current",version_restore_btn:"Restore",version_compare_btn:"Compare",version_restore_selected:"Restore selected",version_clear_old:"Clear old versions (free {size})",version_no_backups:"No version history available",version_restore_confirm:"Restore {label} for {paper}?",version_restore_done:"Restored {label}",version_compare_title:"{vA} vs {vB}",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 paper content...",retrieval_search_idle_hint:"Type a keyword or @ to search paper content",retrieval_searching_metadata:"Searching metadata...",retrieval_searching_deep:"Deep searching...",retrieval_search_cancel:"Cancel",retrieval_results_count:"{n} result(s)",retrieval_empty:"No matching papers found.",retrieval_empty_tips:"Try broader terms or use @ deep search.",retrieval_vectors_not_built:"Vector index not built",retrieval_vectors_not_built_desc:"Build vectors to enable @ deep search with semantic matching.",retrieval_open_vector_settings:"Open Vector Settings",retrieval_backend_unavailable:"Search backend unavailable",retrieval_backend_unavailable_desc:"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_desc:"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. Rebuild to use deep search.",retrieval_rebuild_vectors:"Rebuild Vectors",retrieval_build_idle:"Vector database ready",retrieval_build_ready:"{n} vector(s) built",retrieval_build_stopping:"Stopping...",retrieval_build_stopped:"Build stopped ({n}/{t} papers)",retrieval_build_failed:"Build failed",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?",md_select_installation:"Installation",md_select_library:"Library",md_select_ocr:"OCR",md_select_memory:"Memory",installation_detail_heading:"Installation Details",btn_back_to_overview:"\u2190 Back to Overview",agent_integration_section:"Agent Integration",module_detail_open_installation:"Open Installation",module_detail_open_help:"Help & Docs",module_detail_open_maintenance:"Maintenance",module_detail_open_library:"Open Library",module_detail_open_ocr:"Open OCR",module_detail_open_memory:"Open Memory",action_unknown_pair:"Unknown action: {verb}",ocr_stop_batch:"Stop OCR batch",runtime_not_available:"No Python runtime available",md_unavailable_module:"Not available yet",library_detail_heading:"Library Details",ocr_detail_heading:"OCR Details",memory_detail_heading:"Memory Details",managed_runtime_status:"Runtime Status",managed_runtime_install:"Install Runtime",managed_runtime_repair:"Repair Runtime",managed_runtime_rollback:"Rollback",managed_runtime_update:"Update Runtime",managed_runtime_check_status:"Check Status",managed_runtime_refresh:"Refresh Status",managed_runtime_manual_setup:"Manual Setup",managed_runtime_stop:"Stop",managed_runtime_unknown_state:"Unknown",managed_runtime_ok_state:"Ready",managed_runtime_not_installed:"Not Installed",managed_runtime_needs_repair:"Needs Repair",managed_runtime_unavailable:"Unavailable",managed_runtime_last_verified:"Last verified: {time}",managed_runtime_running:"Runtime operation in progress...",managed_runtime_action_complete:"Runtime operation completed.",managed_runtime_action_failed:"Runtime operation failed: {error}",managed_runtime_action_cancelled:"Runtime operation cancelled.",cc_summary_ok:"All systems ready",cc_summary_core_ok:"Core environment ready; {n} modules pending detection",cc_summary_attention:"Some modules need attention",cc_summary_ok_body:"PaperForge environment is fully operational. Installation and documentation are verified.",cc_summary_core_ok_body:"Installation and Help modules are active. Library, OCR, Memory, and Maintenance will show live status once their backends are connected.",cc_summary_attention_body:"One or more core modules require your attention to function properly.",cc_badge_ok:"Ready",cc_badge_pending:"Pending",cc_badge_setup:"Setup needed",cc_badge_attention:"Needs attention",cc_diagnostic_toggle:"Details",cc_n_ready:"{n} ready",cc_n_pending:"{n} pending",cc_title:"System Status",cc_desc:"Real-time status of PaperForge core modules. Modules with a pending action need your attention.",cc_zone_attention:"Needs Attention",cc_zone_modules:"All Modules",cc_module_installation:"Installation",cc_module_help:"Help & Docs",cc_module_library:"Library Index",cc_module_ocr:"OCR Engine",cc_module_memory:"Memory Layer",cc_module_maintenance:"Maintenance",cc_state_ready:"Ready",cc_state_limited:"Limited",cc_state_unavailable:"Unavailable",cc_state_unknown:"Unknown",cc_severity_ok:"OK",cc_severity_unknown:"Unknown",cc_severity_warning:"Warning",cc_severity_error:"Error",cc_state_missing_input:"Missing Input",cc_state_needs_action:"Needs Action",cc_action_setup:"Open Setup Wizard",cc_action_probe:"Check",cc_action_set_config:"Set Config",cc_action_update:"Update",cc_reason_installation_ready:"PaperForge environment is set up correctly.",cc_reason_config_missing:"Configuration file is missing. Run setup to create one.",cc_reason_config_corrupt:"Configuration file is corrupt. Run setup to repair.",cc_reason_python_version_unsupported:"Python version is not supported. Install Python 3.11+.",cc_reason_help_ready:"Help documentation is available.",cc_reason_docs_missing:"Help documentation is not yet installed.",cc_reason_placeholder:"Detection pending \u2014 will show live status once connected.",cc_reason_library_ready:"Library is synced and indexed.",cc_reason_library_config_missing:"Configuration not found \u2014 run setup to configure library.",cc_reason_library_config_corrupt:"Configuration file is corrupt \u2014 library cannot proceed.",cc_reason_library_zotero_missing:"Zotero data directory is not configured.",cc_reason_library_zotero_not_found:"Zotero data directory path does not exist.",cc_reason_library_index_missing:"Library index has not been built yet \u2014 run sync.",cc_reason_library_index_stale:"Library index is stale \u2014 sync to refresh.",cc_reason_ocr_ready:"OCR pipeline is configured and functional.",cc_reason_ocr_config_missing:"Configuration not found \u2014 run setup to configure OCR.",cc_reason_ocr_config_corrupt:"Configuration file is corrupt \u2014 OCR cannot proceed.",cc_reason_ocr_api_key_missing:"No OCR API key configured \u2014 add one in setup.",cc_reason_ocr_artifacts_missing:"No OCR output found \u2014 run OCR on papers.",cc_reason_memory_ready:"Memory database is healthy and indexed.",cc_reason_memory_db_missing:"Memory database not built \u2014 run memory build.",cc_reason_memory_db_corrupt:"Memory database is corrupted \u2014 restore from backup.",cc_reason_memory_index_stale:"Memory index needs rebuild to match current library.",cc_diag_module:"Module",cc_diag_state:"State",cc_diag_severity:"Severity",cc_diag_activity:"Activity",cc_diag_reason:"Reason",cc_diag_ttl:"TTL",cc_diag_updated:"Updated",cc_reason_no_probe:"{module} has not been probed yet.",cc_reason_stale:"Cached probe data for {module} is stale.",cc_reason_invalid_response:"Probe response for {module} was invalid.",cc_reason_probing:"Checking {module} status...",cc_notice_placeholder:"{module} module probing is not yet available; it will be added in a future update.",cc_notice_refreshed:"Module status refreshed.",cc_notice_refresh_failed:"Failed to refresh module status.",cc_activity_idle:"Idle",cc_activity_running:"Running"},zh:{action_running:"\u6B63\u5728\u6267\u884C ",api_key_missing:"\u672A\u914D\u7F6E \u2717",api_key_set:"\u5DF2\u914D\u7F6E \u2713",btn_install:"\u6253\u5F00\u5B89\u88C5\u5411\u5BFC",btn_install_desc:"\u81EA\u52A8\u68C0\u6D4B Python + \u524D\u7F6E\u73AF\u5883\uFF0C\u901A\u8FC7\u540E\u6253\u5F00\u5206\u6B65\u5B89\u88C5\u5411\u5BFC",btn_reconfig:"\u91CD\u65B0\u914D\u7F6E",btn_reconfig_desc:"\u91CD\u65B0\u8FD0\u884C\u5B89\u88C5\u5411\u5BFC\uFF0C\u4FEE\u6539\u76EE\u5F55\u6216\u5BC6\u94A5\u914D\u7F6E",btn_validate:"\u9A8C\u8BC1",check_bbt_fail:"\u672A\u68C0\u6D4B\u5230",check_bbt_ok:"\u5DF2\u5B89\u88C5",check_python_fail:"\u672A\u5B89\u88C5",check_python_ok:"\u5DF2\u5C31\u7EEA",check_zotero_fail:"\u672A\u68C0\u6D4B\u5230",check_zotero_ok:"\u5DF2\u5B89\u88C5",complete_next:"\u4E0B\u4E00\u6B65\u64CD\u4F5C",complete_step1:"\u6253\u5F00 PaperForge Dashboard",complete_step1_desc:"Ctrl+P \u2192 \u8F93\u5165 PaperForge: Open Dashboard\uFF0C\u6216\u70B9\u5DE6\u4FA7\u4E66\u672C\u56FE\u6807",complete_step2:"\u540C\u6B65\u6587\u732E",complete_step2_desc:"Dashboard \u4E2D\u70B9 Sync Library\uFF0C\u4ECE Zotero \u62C9\u53D6\u6587\u732E\u751F\u6210\u7B14\u8BB0",complete_step3:"\u8FD0\u884C OCR",complete_step3_desc:"Dashboard \u4E2D\u70B9 Run OCR\uFF0C\u63D0\u53D6 PDF \u5168\u6587\u4E0E\u56FE\u8868",complete_step4:"\u914D\u7F6E BBT \u81EA\u52A8\u5BFC\u51FA",complete_summary:"\u5F53\u524D\u5B8C\u6574\u914D\u7F6E",complete_title:"\u2713 PaperForge \u5B89\u88C5\u5B8C\u6210",copied:"\u5DF2\u590D\u5236\uFF01",copy_pf_deep_cmd:"\u590D\u5236 /pf-deep \u547D\u4EE4",dashboard_drift_warning:'\u63D2\u4EF6\u7248\u672C\u4E0E Python \u8FD0\u884C\u65F6\u7248\u672C\u4E0D\u5339\u914D\u3002\u8BF7\u5728\u8BBE\u7F6E\u4E2D\u70B9\u51FB"\u540C\u6B65\u8FD0\u884C\u65F6"\u3002',deep_reading_not_found:"\u7CBE\u8BFB\u6587\u4EF6\u672A\u627E\u5230",desc:"Obsidian + Zotero \u6587\u732E\u7BA1\u7406\u6D41\u6C34\u7EBF\u3002\u81EA\u52A8\u540C\u6B65\u6587\u732E\u3001\u751F\u6210\u7B14\u8BB0\u3001OCR \u63D0\u53D6\u5168\u6587\uFF0C\u4E00\u7AD9\u5F0F\u6587\u732E\u7CBE\u8BFB\u5DE5\u4F5C\u6D41\u3002",dir_base:"Base \u76EE\u5F55",dir_index:"\u7D22\u5F15\u76EE\u5F55",dir_notes:"\u6B63\u6587\u76EE\u5F55",dir_resources:"\u8D44\u6E90\u76EE\u5F55",dir_system:"\u7CFB\u7EDF\u76EE\u5F55",dir_vault:"Vault \u8DEF\u5F84",error_copied:"\u5DF2\u590D\u5236\uFF01",error_copy_diagnostic:"\u590D\u5236\u8BCA\u65AD\u4FE1\u606F",feat_agent_platform:"Agent \u5E73\u53F0",feat_agent_platform_desc:"\u9009\u62E9\u8981\u7BA1\u7406\u7684 Agent \u5E73\u53F0\u3002",feat_api_base_url:"API \u5730\u5740",feat_api_base_url_desc:"\u81EA\u5B9A\u4E49 OpenAI \u517C\u5BB9 API \u7AEF\u70B9\u3002\u7559\u7A7A\u4F7F\u7528\u9ED8\u8BA4\u5730\u5740\u3002",feat_api_model:"API \u6A21\u578B",feat_api_model_desc:"\u8BE5\u7AEF\u70B9\u4F7F\u7528\u7684\u5D4C\u5165\u6A21\u578B\u540D\u79F0\u3002",feat_build_btn:"\u6784\u5EFA",feat_build_complete:"\u5411\u91CF\u6784\u5EFA\u5B8C\u6210\u3002",feat_build_failed:"\u6784\u5EFA\u5931\u8D25\u3002\u8BF7\u67E5\u770B\u7EC8\u7AEF\u8F93\u51FA\u3002",feat_building:"\u6784\u5EFA\u4E2D\u2026",feat_cache_remove_failed:"\u5931\u8D25\uFF1A{0}",feat_cache_removed:"\u6A21\u578B\u7F13\u5B58\u5DF2\u6E05\u9664\u3002",feat_checking:"\u68C0\u6D4B\u4E2D\u2026",feat_checking_btn:"\u68C0\u6D4B\u4E2D\u2026",feat_deps_checking:"\u6B63\u5728\u68C0\u6D4B\u4F9D\u8D56\u2026",feat_deps_missing:"\u4F9D\u8D56\u672A\u5B89\u88C5\u3002\u9700\u8981\uFF1Achromadb, openai\u3002",feat_enter_key:"\u8BF7\u8F93\u5165\u6709\u6548\u7684 OpenAI API Key\u3002",feat_install_btn:"\u5B89\u88C5",feat_install_deps:"\u5B89\u88C5\u4F9D\u8D56",feat_install_done:"\u4F9D\u8D56\u5DF2\u5B89\u88C5\u3002\u6B63\u5728\u6784\u5EFA\u5411\u91CF\u2026",feat_install_failed:"\u5B89\u88C5\u5931\u8D25\uFF1A",feat_installing:"\u5B89\u88C5\u4E2D\u2026",feat_installing_pkgs:"\u6B63\u5728\u5B89\u88C5 {pkgs}...",feat_key_rejected:"API Key \u88AB\u62D2\u7EDD\u3002",feat_memory_desc:"\u8BB0\u5FC6\u5C42\u662F PaperForge \u7684\u6838\u5FC3\u6570\u636E\u5F15\u64CE\uFF0C\u57FA\u4E8E SQLite \u6784\u5EFA\u3002\u5B83\u6574\u5408\u4E86\u6587\u732E\u5143\u6570\u636E\uFF08\u8BBA\u6587\u3001\u8D44\u6E90\u6587\u4EF6\u3001\u522B\u540D\u3001\u9605\u8BFB\u4E8B\u4EF6\uFF09\uFF0C\u652F\u6301 FTS5 \u5143\u6570\u636E\u68C0\u7D22\uFF08\u6807\u9898\u3001\u6458\u8981\u3001\u4F5C\u8005\u3001domain\u3001collection\uFF09\uFF0C\u5E76\u4E3A agent-context \u548C paper-status \u547D\u4EE4\u63D0\u4F9B\u6570\u636E\u652F\u6491\u3002\u59CB\u7EC8\u8FD0\u884C\uFF0C\u65E0\u9700\u624B\u52A8\u5F00\u542F\u3002",feat_memory_rebuild_btn:"\u91CD\u5EFA\u6570\u636E\u5E93",feat_memory_rebuild_done:"\u8BB0\u5FC6\u6570\u636E\u5E93\u91CD\u5EFA\u5B8C\u6210\u3002",feat_memory_rebuild_failed:"\u91CD\u5EFA\u5931\u8D25\u3002",feat_memory_rebuilding:"\u91CD\u5EFA\u4E2D\u2026",feat_model:"\u6A21\u578B",feat_model_changed_warn:"\u6A21\u578B\u5DF2\u66F4\u6362\uFF08{0} -> {1}\uFF09\u3002\u5DF2\u6709\u5411\u91CF\u4E0D\u517C\u5BB9\u2014\u2014\u9700\u8981\u91CD\u5EFA\u3002",feat_network_error:"\u7F51\u7EDC\u9519\u8BEF\uFF1A",feat_no_python:"\u672A\u627E\u5230 Python\u3002\u8BF7\u67E5\u770B\u5B89\u88C5\u6807\u7B7E\u9875\u3002",feat_not_cached:"\u672A\u7F13\u5B58",feat_openai_key:"OpenAI API Key",feat_openai_key_desc:"\u7528\u4E8E API \u5D4C\u5165\u8C03\u7528\uFF0C\u6A21\u578B\u5728\u4E0B\u65B9\u5B9A\u4E49\u3002",feat_output_copied:"\u8F93\u51FA\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F\u3002",feat_rebuild_btn:"\u91CD\u5EFA",feat_rebuild_vectors:"\u91CD\u5EFA\u5411\u91CF",feat_rebuild_vectors_changed:"\u6A21\u578B\u5DF2\u66F4\u6362 \u2014 \u9700\u8981\u91CD\u5EFA\u5411\u91CF\u3002",feat_rebuild_vectors_desc:"\u91CD\u5EFA\u6240\u6709 OCR \u5168\u6587\u5411\u91CF\u3002\u66F4\u6362\u6A21\u578B\u6216\u6A21\u5F0F\u540E\u9700\u8981\u91CD\u5EFA\u3002",feat_removing:"\u5220\u9664\u4E2D\u2026",feat_retry_btn:"\u91CD\u8BD5",feat_skills_desc:"\u7BA1\u7406 Vault \u4E2D\u5DF2\u5B89\u88C5\u7684 Agent \u6280\u80FD\u3002\u6BCF\u884C\u5BF9\u5E94\u4E00\u4E2A SKILL.md \u6587\u4EF6\uFF0C\u5173\u95ED\u5F00\u5173\u53EF\u963B\u6B62 Agent \u81EA\u52A8\u8C03\u7528\u8BE5\u6280\u80FD\u3002",feat_skills_system:"\u7CFB\u7EDF\u6280\u80FD\u968F PaperForge \u4E00\u540C\u53D1\u5E03\uFF0C\u4F1A\u8DDF\u968F PaperForge \u7248\u672C\u66F4\u65B0\u3002",feat_skills_user:"\u7528\u6237\u6280\u80FD\u662F\u4F60\u81EA\u884C\u5B89\u88C5\u6216\u521B\u5EFA\u7684\u81EA\u5B9A\u4E49\u6280\u80FD\u3002",feat_uninstall_btn:"\u5378\u8F7D",feat_valid_key:"API Key \u6709\u6548\u3002",feat_vector_config_label:"\u5411\u91CF\u5E93\u914D\u7F6E",feat_vector_corrupted:"\u5411\u91CF\u7D22\u5F15\u5DF2\u635F\u574F \u2014 \u9700\u8981\u5F3A\u5236\u91CD\u5EFA\u3002",feat_vector_desc:"\u5411\u91CF\u6570\u636E\u5E93\u901A\u8FC7\u5D4C\u5165\u6A21\u578B\u5B9E\u73B0 OCR \u5168\u6587\u7684\u8BED\u4E49\u641C\u7D22\u3002\u6587\u6863\u88AB\u5207\u5206\u4E3A\u6587\u672C\u5757\uFF08chunk\uFF09\uFF0C\u7F16\u7801\u4E3A\u5411\u91CF\u5B58\u5165 ChromaDB\u3002\u652F\u6301\u672C\u5730\u6A21\u578B\uFF08\u514D\u8D39\uFF0CCPU \u8FD0\u884C\uFF09\u6216 OpenAI API\uFF08\u4ED8\u8D39\uFF0C\u66F4\u5FEB\u901F\uFF09\u3002",feat_vector_enable:"\u542F\u7528\u5411\u91CF\u68C0\u7D22",feat_vector_enable_desc:"\u5BF9 OCR \u5168\u6587\u8FDB\u884C\u8BED\u4E49\u641C\u7D22\u3002\u9700\u5B89\u88C5: pip install chromadb sentence-transformers openai (~500MB)\u3002",feat_vector_rebuild_force_btn:"\u5F3A\u5236\u91CD\u5EFA",feat_verify:"\u9A8C\u8BC1",feat_verify_btn:"\u9A8C\u8BC1",field_paddleocr:"PaddleOCR API \u5BC6\u94A5",field_python_custom:"\u81EA\u5B9A\u4E49 Python \u8DEF\u5F84",field_python_interp:"\u5F53\u524D Python \u89E3\u91CA\u5668",field_zotero_data:"Zotero \u6570\u636E\u76EE\u5F55",field_zotero_placeholder:"\u53EF\u9009\uFF0C\u7528\u4E8E\u81EA\u52A8\u68C0\u6D4B PDF",guide_ocr:"\u8FD0\u884C OCR",guide_ocr_desc:"Dashboard \u4E2D\u70B9 Run OCR\uFF0C\u63D0\u53D6 PDF \u5168\u6587\u4E0E\u56FE\u8868",guide_open:"\u6253\u5F00 Dashboard",guide_open_desc:"Ctrl+P \u2192 \u8F93\u5165 PaperForge: Open Dashboard\uFF0C\u6216\u70B9\u5DE6\u4FA7\u4E66\u672C\u56FE\u6807",guide_sync:"\u540C\u6B65\u6587\u732E",guide_sync_desc:"Dashboard \u4E2D\u70B9 Sync Library\uFF0C\u4ECE Zotero \u62C9\u53D6\u6587\u732E\u751F\u6210\u7B14\u8BB0",header_title:"PaperForge",install_bootstrapping:"\u672A\u68C0\u6D4B\u5230 PaperForge Python \u5305\uFF0C\u6B63\u5728\u81EA\u52A8\u5B89\u88C5\u2026",install_btn:"\u5F00\u59CB\u5B89\u88C5",install_btn_retry:"\u91CD\u8BD5",install_btn_running:"\u6B63\u5728\u5B89\u88C5...",install_complete:"\u2713 \u5B89\u88C5\u5B8C\u6210\uFF01",install_failed:"\u2717 \u5B89\u88C5\u5931\u8D25\uFF1A",install_validating:"\u6B63\u5728\u6821\u9A8C\u5B89\u88C5\u73AF\u5883\u2026",jump_to_deep_reading:"\u8DF3\u8F6C\u5230\u7CBE\u8BFB",label_agent:"Agent \u5E73\u53F0",nav_close:"\u5173\u95ED",nav_next:"\u4E0B\u4E00\u6B65 \u2192",nav_prev:"\u2190 \u4E0A\u4E00\u6B65",no_pending_ocr:"\u6240\u6709 OCR \u4EFB\u52A1\u5DF2\u5B8C\u6210",not_set:"\u672A\u8BBE\u7F6E",notice_check_fail:"\u672A\u901A\u8FC7: ",notice_python_missing:"Python \u672A\u68C0\u6D4B\u5230\uFF0C\u8BF7\u5148\u5B89\u88C5 Python 3.10+ \u5E76\u52A0\u5165 PATH",ocr_privacy_title:"OCR \u9690\u79C1\u63D0\u793A",ocr_privacy_warning:"OCR \u4F1A\u5C06 PDF \u4E0A\u4F20\u5230 PaddleOCR API \u8FDB\u884C\u5904\u7406\u3002\u8BF7\u4E0D\u8981\u4E0A\u4F20\u5305\u542B\u654F\u611F\u4FE1\u606F\u6216\u65E0\u6CD5\u5916\u4F20\u7684\u6587\u732E\u3002",ocr_queue_add:"\u52A0\u5165 OCR \u961F\u5217",ocr_queue_added:"\u5DF2\u52A0\u5165 OCR \u961F\u5217",ocr_queue_remove:"\u79FB\u51FA OCR \u961F\u5217",ocr_queue_removed:"\u5DF2\u79FB\u51FA OCR \u961F\u5217",ocr_understand:"\u6211\u4E86\u89E3\uFF0C\u7EE7\u7EED",optional_later:"\uFF08\u7A0D\u540E\u53EF\u5728\u8BBE\u7F6E\u4E2D\u8865\u5145\uFF09",orphan_delete_failed:"\u6E05\u7406\u5931\u8D25",orphan_delete_selected:"\u5220\u9664 {count} \u7BC7",orphan_deleted:"\u5DF2\u5220\u9664 {count} \u7BC7\u6B8B\u7559\u6587\u732E",orphan_desc:"\u8FD9\u4E9B\u6587\u732E\u5DF2\u4ECE Zotero \u4E2D\u79FB\u9664\u3002",orphan_deselect_all:"\u53D6\u6D88\u5168\u9009",orphan_explain:"\u5DF2\u4ECE Zotero \u4E2D\u79FB\u9664\u3002\u5DE5\u4F5C\u533A\u6587\u4EF6\u4ECD\u4FDD\u7559\u5728\u78C1\u76D8\u4E0A\u3002",orphan_keep_all:"\u4FDD\u7559\u5168\u90E8",orphan_none_selected:"\u672A\u9009\u62E9\u4EFB\u4F55\u6587\u732E",orphan_select_all:"\u5168\u9009",orphan_title:"\u53D1\u73B0 {count} \u7BC7\u6B8B\u7559\u6587\u732E",panel_actions:"\u5FEB\u6377\u64CD\u4F5C",prep_bbt:"Better BibTeX",prep_bbt_desc:"Zotero \u2192 \u5DE5\u5177 \u2192 \u63D2\u4EF6 \u2192 \u5B89\u88C5 Better BibTeX",prep_export:"BBT \u81EA\u52A8\u5BFC\u51FA",prep_export_desc:"\u53F3\u952E\u6587\u732E\u5B50\u5206\u7C7B \u2192 \u5BFC\u51FA\u5206\u7C7B \u2192 BetterBibTeX JSON \u2192 \u52FE\u9009\u4FDD\u6301\u66F4\u65B0 \u2192 \u5BFC\u51FA\u5230\uFF08JSON \u6587\u4EF6\u540D\u5373\u4E3A Base \u540D\uFF09\uFF1A",prep_key:"PaddleOCR Key",prep_key_desc:"\u5728 https://aistudio.baidu.com/paddleocr \u83B7\u53D6 API Key",prep_python:"Python 3.10+",prep_python_desc:"\u786E\u4FDD Python \u53EF\u547D\u4EE4\u884C\u8C03\u7528\u3002\u70B9\u51FB\u4E0B\u65B9\u6309\u94AE\u81EA\u52A8\u68C0\u6D4B\u3002",prep_zotero:"Zotero \u684C\u9762\u7248",prep_zotero_desc:"\u5B89\u88C5 Zotero (https://www.zotero.org)",run_in_agent:"\u5728 {0} \u4E2D\u8FD0\u884C",runtime_health:"\u8FD0\u884C\u65F6\u72B6\u6001",runtime_health_checking:"\u6B63\u5728\u68C0\u6D4B\u2026",runtime_health_desc:"\u68C0\u67E5\u63D2\u4EF6\u4E0E Python \u8FD0\u884C\u65F6\u7248\u672C\u7684\u5339\u914D\u60C5\u51B5\uFF0C\u5E76\u786E\u8BA4\u5DF2\u90E8\u7F72\u7684 skill contract \u662F\u5426\u4E3A\u5F53\u524D\u7248\u672C\u3002",runtime_health_match:"\u5339\u914D",runtime_health_mismatch:"\u4E0D\u5339\u914D",runtime_health_package_ver:"Python \u5305 v{0}",runtime_health_plugin_ver:"\u63D2\u4EF6 v{0}",runtime_health_sync:"\u540C\u6B65\u8FD0\u884C\u65F6",runtime_health_sync_done:"\u8FD0\u884C\u65F6\u5DF2\u540C\u6B65\u81F3 v{0}",runtime_health_sync_fail:"\u8FD0\u884C\u65F6\u540C\u6B65\u5931\u8D25\uFF1A{0}",runtime_health_syncing:"\u6B63\u5728\u540C\u6B65\u2026",section_config:"\u5F53\u524D\u914D\u7F6E",section_guide:"\u64CD\u4F5C\u65B9\u5F0F",section_prep:"\u5B89\u88C5\u51C6\u5907",section_prep_desc:"\u9996\u6B21\u4F7F\u7528\u524D\uFF0C\u8BF7\u4F9D\u6B21\u5B8C\u6210\u4EE5\u4E0B\u51C6\u5907\uFF1A",setup_done:"\u2713 PaperForge \u73AF\u5883\u5DF2\u914D\u7F6E\u5B8C\u6210",setup_pending:"\u5C1A\u672A\u5B89\u88C5\uFF0C\u5B8C\u6210\u5B89\u88C5\u51C6\u5907\u540E\u70B9\u51FB\u5B89\u88C5\u5411\u5BFC",tab_overview:"\u6982\u89C8",tab_modules:"\u6A21\u5757\u8BE6\u60C5",tab_help:"\u5E2E\u52A9",tab_features:"\u529F\u80FD",tab_setup:"\u5B89\u88C5",tab_maintenance:"\u7EF4\u62A4",validate_base:"Base \u76EE\u5F55\u672A\u586B\u5199",validate_fail:"\u914D\u7F6E\u9A8C\u8BC1\u5931\u8D25",validate_index:"\u7D22\u5F15\u76EE\u5F55\u672A\u586B\u5199",validate_key:"PaddleOCR API \u5BC6\u94A5\u672A\u586B\u5199",validate_notes:"\u6B63\u6587\u76EE\u5F55\u672A\u586B\u5199",validate_resources:"\u8D44\u6E90\u76EE\u5F55\u672A\u586B\u5199",validate_system:"\u7CFB\u7EDF\u76EE\u5F55\u672A\u586B\u5199",validate_vault:"Vault \u8DEF\u5F84\u672A\u586B\u5199",validate_zotero:"Zotero \u6570\u636E\u76EE\u5F55\u4E3A\u5FC5\u586B\u9879",wizard_agent_hint:"\u9009\u62E9\u4F60\u4F7F\u7528\u7684 AI Agent \u5E73\u53F0\uFF0C\u5B89\u88C5\u65F6\u5C06\u6309\u5BF9\u5E94\u683C\u5F0F\u90E8\u7F72\u6280\u80FD\u6587\u4EF6\uFF1A",wizard_dir_hint:"\u8D44\u6E90\u76EE\u5F55\u662F\u6587\u732E\u6570\u636E\u7684\u7EDF\u4E00\u6839\u76EE\u5F55\uFF0C\u4EE5\u4E0B\u5B50\u76EE\u5F55\u5C06\u521B\u5EFA\u5728\u5176\u5185\u90E8\uFF1A",wizard_dir_sub_hint:"\u8D44\u6E90\u76EE\u5F55\u5185\u7684\u4E24\u4E2A\u5B50\u76EE\u5F55\uFF1A",wizard_intro:"\u672C\u5411\u5BFC\u5C06\u5F15\u5BFC\u60A8\u5B8C\u6210 PaperForge \u73AF\u5883\u7684\u5B8C\u6574\u914D\u7F6E\u3002\u5B89\u88C5\u8FC7\u7A0B\u4F1A\u81EA\u52A8\u521B\u5EFA\u6240\u6709\u76EE\u5F55\u7ED3\u6784\uFF0C\u65E0\u9700\u624B\u52A8\u64CD\u4F5C\u3002",wizard_keys_hint:"\u4EE5\u4E0B\u4E3A API \u5BC6\u94A5\u4E0E Zotero \u914D\u7F6E\uFF1A",wizard_preview:"\u7CFB\u7EDF\u6587\u4EF6\u548C Agent \u914D\u7F6E\u4F4D\u4E8E Vault \u6839\u76EE\u5F55\u4E0B\u3002\u6587\u732E\u6570\u636E\uFF08\u6B63\u6587\u3001\u7D22\u5F15\uFF09\u7EDF\u4E00\u5B58\u653E\u5728\u8D44\u6E90\u76EE\u5F55\u5185\u3002\u5B89\u88C5\u540E\u4ECD\u53EF\u5728\u8BBE\u7F6E\u4E2D\u4FEE\u6539\u3002",wizard_safety:"\u5B89\u5168\u8BF4\u660E\uFF1A\u5982\u679C\u4F60\u9009\u62E9\u7684\u76EE\u5F55\u91CC\u5DF2\u7ECF\u6709\u6587\u4EF6\uFF0C\u5B89\u88C5\u5411\u5BFC\u4F1A\u4FDD\u7559\u5DF2\u6709\u5185\u5BB9\uFF0C\u53EA\u8865\u5145\u7F3A\u5931\u7684 PaperForge \u6587\u4EF6\u548C\u76EE\u5F55\u3002",wizard_step1:"\u6982\u89C8",wizard_step2:"\u76EE\u5F55",wizard_step3:"Agent",wizard_step4:"\u5B89\u88C5",wizard_step5:"\u5B8C\u6210",wizard_skip_ocr_desc:"OCR \u529F\u80FD\u5728\u914D\u7F6E\u6709\u6548\u7684 PaddleOCR API \u5BC6\u94A5\u4E4B\u524D\u4E0D\u53EF\u7528\u3002\u60A8\u53EF\u4EE5\u7EE7\u7EED\u5B8C\u6210\u8BBE\u7F6E\uFF0C\u7A0D\u540E\u5728\u8BBE\u7F6E\u4E2D\u914D\u7F6E\u3002",wizard_skip_ocr_continue:"\u7EE7\u7EED\uFF0C\u7A0D\u540E\u914D\u7F6E\u5BC6\u94A5",wizard_skip_ocr_back:"\u8FD4\u56DE\u914D\u7F6E",wizard_api_hint_skip:"OCR \u5BC6\u94A5\u4E3A\u9009\u586B\u9879 \u2014 \u53EF\u8DF3\u8FC7\uFF0C\u7A0D\u540E\u5728\u8BBE\u7F6E\u4E2D\u914D\u7F6E\u3002",wizard_sys_hint:"\u72EC\u7ACB\u4E8E\u8D44\u6E90\u76EE\u5F55\u7684\u7CFB\u7EDF\u6587\u4EF6\uFF1A",wizard_title:"PaperForge \u5B89\u88C5\u5411\u5BFC",ocr_maint_no_action:"\u65E0\u9700\u5904\u7406",ocr_maint_rebuild:"\u5EFA\u8BAE\u91CD\u5EFA",ocr_maint_failed:"OCR \u5931\u8D25",ocr_maint_limited:"\u7ED3\u679C\u4E00\u822C",ocr_maint_needs_attention:"\u9700\u8981\u5904\u7406",ocr_maint_limitations:"\u7ED3\u679C\u8BF4\u660E",ocr_maint_hero_ok:"OCR \u6574\u4F53\u6B63\u5E38\u3002",ocr_maint_hero_warn:"OCR \u9700\u8981\u5173\u6CE8\uFF1A{rebuild} \u7BC7\u5EFA\u8BAE\u91CD\u5EFA\uFF0C{failed} \u7BC7\u5904\u7406\u5931\u8D25\u3002",ocr_maint_hero_note:"\u672C\u9875\u53EA\u63D0\u793A\u7EF4\u62A4\u540E\u5927\u6982\u7387\u4F1A\u6539\u5584\u7684\u95EE\u9898\u3002\u90E8\u5206\u8BBA\u6587\u6548\u679C\u4E00\u822C\uFF0C\u7EF4\u62A4\u672A\u5FC5\u80FD\u6539\u5584\u3002",ocr_maint_limitations_intro:"\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",ocr_maint_redo_confirm:"\u786E\u8BA4\u91CD\u65B0 OCR {n} \u7BC7\u8BBA\u6587\uFF1F\u73B0\u6709\u7684\u6D3E\u751F OCR \u7ED3\u679C\u5C06\u88AB\u66FF\u6362\u3002",maintenance_group_retry:"\u9700\u8981\u91CD\u8BD5",maintenance_group_rebuild:"\u53EF\u91CD\u5EFA\u7ED3\u679C",maintenance_group_legacy:"\u53EF\u5347\u7EA7\u65E7\u7ED3\u679C\uFF08\u53EF\u9009\uFF09",maintenance_btn_retry:"\u91CD\u8BD5",maintenance_btn_rebuild:"\u91CD\u5EFA",maintenance_btn_upgrade:"\u5347\u7EA7",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...",version_papers_count:"{n} \u7BC7\u8BBA\u6587",version_current:"\u5F53\u524D",version_restore_btn:"\u6062\u590D",version_compare_btn:"\u5BF9\u6BD4",version_restore_selected:"\u6062\u590D\u9009\u4E2D\u7248\u672C",version_clear_old:"\u6E05\u9664\u65E7\u7248\u672C (\u91CA\u653E {size})",version_no_backups:"\u6CA1\u6709\u53EF\u6062\u590D\u7684\u5386\u53F2\u7248\u672C",md_select_installation:"\u5B89\u88C5",md_select_library:"\u6587\u732E\u5E93",md_select_ocr:"OCR",md_select_memory:"\u8BB0\u5FC6\u5C42",installation_detail_heading:"\u5B89\u88C5\u8BE6\u60C5",btn_back_to_overview:"\u2190 \u8FD4\u56DE\u6982\u89C8",agent_integration_section:"Agent \u96C6\u6210",module_detail_open_installation:"\u6253\u5F00\u5B89\u88C5",module_detail_open_help:"\u5E2E\u52A9\u4E0E\u6587\u6863",module_detail_open_maintenance:"\u7EF4\u62A4",module_detail_open_library:"\u6253\u5F00\u6587\u732E\u5E93",module_detail_open_ocr:"\u6253\u5F00 OCR",module_detail_open_memory:"\u6253\u5F00\u8BB0\u5FC6",action_unknown_pair:"\u672A\u77E5\u64CD\u4F5C: {verb}",ocr_stop_batch:"\u505C\u6B62 OCR \u6279\u5904\u7406",runtime_not_available:"Python \u8FD0\u884C\u65F6\u4E0D\u53EF\u7528",md_unavailable_module:"\u6682\u4E0D\u53EF\u7528",library_detail_heading:"\u6587\u732E\u5E93\u8BE6\u60C5",ocr_detail_heading:"OCR \u8BE6\u60C5",memory_detail_heading:"\u8BB0\u5FC6\u5C42\u8BE6\u60C5",managed_runtime_status:"\u8FD0\u884C\u65F6\u72B6\u6001",managed_runtime_install:"\u5B89\u88C5\u8FD0\u884C\u65F6",managed_runtime_repair:"\u4FEE\u590D\u8FD0\u884C\u65F6",managed_runtime_rollback:"\u56DE\u6EDA",managed_runtime_update:"\u66F4\u65B0\u8FD0\u884C\u65F6",managed_runtime_check_status:"\u68C0\u67E5\u72B6\u6001",managed_runtime_refresh:"\u5237\u65B0\u72B6\u6001",managed_runtime_manual_setup:"\u624B\u52A8\u914D\u7F6E",managed_runtime_stop:"\u505C\u6B62",managed_runtime_unknown_state:"\u672A\u77E5",managed_runtime_ok_state:"\u5C31\u7EEA",managed_runtime_not_installed:"\u672A\u5B89\u88C5",managed_runtime_needs_repair:"\u9700\u8981\u4FEE\u590D",managed_runtime_unavailable:"\u4E0D\u53EF\u7528",managed_runtime_last_verified:"\u6700\u540E\u9A8C\u8BC1\uFF1A{time}",managed_runtime_running:"\u8FD0\u884C\u65F6\u64CD\u4F5C\u8FDB\u884C\u4E2D...",managed_runtime_action_complete:"\u8FD0\u884C\u65F6\u64CD\u4F5C\u5DF2\u5B8C\u6210\u3002",managed_runtime_action_cancelled:"\u8FD0\u884C\u65F6\u64CD\u4F5C\u5DF2\u53D6\u6D88\u3002",managed_runtime_action_failed:"\u8FD0\u884C\u65F6\u64CD\u4F5C\u5931\u8D25\uFF1A{error}",version_restore_confirm:"\u786E\u8BA4\u5C06 {paper} \u6062\u590D\u5230 {label}\uFF1F",version_restore_done:"\u5DF2\u6062\u590D\u5230 {label}",version_compare_title:"{vA} vs {vB}",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...\uFF08@ \u542F\u52A8\u6DF1\u5EA6\u641C\u7D22\uFF09",retrieval_search_placeholder_deep:"\u641C\u7D22\u8BBA\u6587\u5185\u5BB9...",retrieval_search_idle_hint:"\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:"{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 @ \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:"\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:"\u641C\u7D22\u540E\u7AEF\u4E0D\u53EF\u7528",retrieval_backend_unavailable_desc:"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\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\u91CD\u5EFA\u5411\u91CF\u540E\u624D\u80FD\u4F7F\u7528\u6DF1\u5EA6\u641C\u7D22\u3002",retrieval_rebuild_vectors:"\u91CD\u5EFA\u5411\u91CF",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...",cc_summary_attention:"\u90E8\u5206\u6A21\u5757\u9700\u8981\u5173\u6CE8",cc_summary_ok_body:"PaperForge \u73AF\u5883\u5DF2\u5B8C\u5168\u5C31\u7EEA\u3002\u5B89\u88C5\u548C\u5E2E\u52A9\u6587\u6863\u5747\u6B63\u5E38\u53EF\u7528\u3002",cc_summary_core_ok_body:"\u5B89\u88C5\u548C\u5E2E\u52A9\u6A21\u5757\u5DF2\u53EF\u7528\u3002\u6587\u732E\u7D22\u5F15\u3001OCR\u3001\u8BB0\u5FC6\u5C42\u548C\u7EF4\u62A4\u6A21\u5757\u5C06\u5728\u540E\u7AEF\u63A5\u5165\u540E\u663E\u793A\u5B9E\u65F6\u72B6\u6001\u3002",cc_summary_attention_body:"\u4E00\u4E2A\u6216\u591A\u4E2A\u6838\u5FC3\u6A21\u5757\u9700\u8981\u60A8\u7684\u5173\u6CE8\u624D\u80FD\u6B63\u5E38\u8FD0\u884C\u3002",retrieval_build_stopped:"\u6784\u5EFA\u5DF2\u505C\u6B62\uFF08{n}/{t} \u7BC7\uFF09",retrieval_build_failed:"\u6784\u5EFA\u5931\u8D25",retrieval_build_stale:"\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:"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\u73B0\u6709\u6587\u672C\u5757\uFF0C\u662F\u5426\u7EE7\u7EED\uFF1F",cc_summary_ok:"\u5168\u90E8\u6B63\u5E38",cc_summary_core_ok:"\u6838\u5FC3\u73AF\u5883\u6B63\u5E38\uFF1B{n} \u4E2A\u6A21\u5757\u72B6\u6001\u68C0\u6D4B\u5F85\u63A5\u5165",cc_badge_ok:"\u5DF2\u5C31\u7EEA",cc_badge_pending:"\u5F85\u63A5\u5165",cc_badge_setup:"\u9700\u8981\u5B89\u88C5",cc_badge_attention:"\u9700\u8981\u6CE8\u610F",cc_diagnostic_toggle:"\u8BE6\u60C5",cc_n_ready:"{n} \u5DF2\u5C31\u7EEA",cc_n_pending:"{n} \u5F85\u63A5\u5165",cc_title:"\u7CFB\u7EDF\u72B6\u6001",cc_desc:"PaperForge \u6838\u5FC3\u6A21\u5757\u7684\u5B9E\u65F6\u72B6\u6001\u3002\u6709\u5F85\u5904\u7406\u64CD\u4F5C\u7684\u6A21\u5757\u9700\u8981\u60A8\u7684\u5173\u6CE8\u3002",cc_zone_attention:"\u9700\u8981\u5173\u6CE8",cc_zone_modules:"\u6240\u6709\u6A21\u5757",cc_module_installation:"\u5B89\u88C5",cc_module_help:"\u5E2E\u52A9\u4E0E\u6587\u6863",cc_module_library:"\u6587\u732E\u7D22\u5F15",cc_module_ocr:"OCR \u5F15\u64CE",cc_module_memory:"\u8BB0\u5FC6\u5C42",cc_module_maintenance:"\u7EF4\u62A4",cc_state_ready:"\u5C31\u7EEA",cc_state_limited:"\u53D7\u9650",cc_state_unavailable:"\u4E0D\u53EF\u7528",cc_state_unknown:"\u672A\u77E5",cc_severity_ok:"\u6B63\u5E38",cc_severity_unknown:"\u672A\u77E5",cc_severity_warning:"\u8B66\u544A",cc_severity_error:"\u9519\u8BEF",cc_state_missing_input:"\u7F3A\u5C11\u8F93\u5165",cc_state_needs_action:"\u9700\u8981\u64CD\u4F5C",cc_action_setup:"\u6253\u5F00\u5B89\u88C5\u5411\u5BFC",cc_action_probe:"\u68C0\u6D4B",cc_action_set_config:"\u914D\u7F6E\u8BBE\u7F6E",cc_action_update:"\u66F4\u65B0",cc_reason_installation_ready:"PaperForge \u73AF\u5883\u5DF2\u6B63\u786E\u914D\u7F6E\u3002",cc_reason_config_missing:"\u914D\u7F6E\u6587\u4EF6\u7F3A\u5931\uFF0C\u8BF7\u8FD0\u884C\u5B89\u88C5\u5411\u5BFC\u3002",cc_reason_config_corrupt:"\u914D\u7F6E\u6587\u4EF6\u635F\u574F\uFF0C\u8BF7\u8FD0\u884C\u5B89\u88C5\u5411\u5BFC\u4FEE\u590D\u3002",cc_reason_python_version_unsupported:"Python \u7248\u672C\u4E0D\u53D7\u652F\u6301\uFF0C\u8BF7\u5B89\u88C5 Python 3.11+\u3002",cc_reason_help_ready:"\u5E2E\u52A9\u6587\u6863\u5DF2\u53EF\u7528\u3002",cc_reason_docs_missing:"\u5E2E\u52A9\u6587\u6863\u5C1A\u672A\u5B89\u88C5\u3002",cc_reason_placeholder:"\u72B6\u6001\u68C0\u6D4B\u5F85\u63A5\u5165\uFF0C\u63A5\u5165\u540E\u5C06\u663E\u793A\u5B9E\u65F6\u72B6\u6001\u3002",cc_diag_module:"\u6A21\u5757",cc_diag_state:"\u72B6\u6001",cc_diag_severity:"\u4E25\u91CD\u7A0B\u5EA6",cc_diag_activity:"\u6D3B\u52A8",cc_diag_reason:"\u539F\u56E0",cc_diag_ttl:"TTL",cc_diag_updated:"\u66F4\u65B0\u65F6\u95F4",cc_reason_no_probe:"{module} \u5C1A\u672A\u68C0\u6D4B\u3002",cc_reason_stale:"{module} \u7684\u68C0\u6D4B\u6570\u636E\u5DF2\u8FC7\u671F\u3002",cc_reason_invalid_response:"{module} \u7684\u68C0\u6D4B\u54CD\u5E94\u65E0\u6548\u3002",cc_reason_probing:"\u6B63\u5728\u68C0\u6D4B {module} \u72B6\u6001...",cc_notice_placeholder:"{module} \u6A21\u5757\u68C0\u6D4B\u529F\u80FD\u5C1A\u672A\u53EF\u7528\uFF0C\u5C06\u5728\u540E\u7EED\u7248\u672C\u4E2D\u6DFB\u52A0\u3002",cc_notice_refreshed:"\u6A21\u5757\u72B6\u6001\u5DF2\u5237\u65B0\u3002",cc_notice_refresh_failed:"\u6A21\u5757\u72B6\u6001\u5237\u65B0\u5931\u8D25\u3002",cc_activity_idle:"\u7A7A\u95F2",cc_activity_running:"\u8FD0\u884C\u4E2D"}},ht=null;function Er(d){try{let p=d.vault;if(typeof p.getConfig=="function"){let e=p.getConfig("language");if(e&&String(e).startsWith("zh"))return"zh"}}catch(p){}try{if(typeof localStorage!="undefined"){let p=localStorage.getItem("language");if(p&&String(p).startsWith("zh"))return"zh"}}catch(p){}return"en"}function Bt(d){ht=Er(d)==="zh"?_t.zh:_t.en}function i(d){return ht&&ht[d]||_t.en[d]||d}var C=require("obsidian"),W=K(require("fs")),ee=K(require("path")),nr=K(require("os")),Y=require("child_process");var sr=K(ft());var he=K(require("fs")),_e=K(require("path")),It=K(require("os")),me=require("child_process"),gt=null,Mt=!1;function z(d,p,e,t){let r=e||he,n=t||me.execFileSync;if(p&&p.python_path&&p.python_path.trim()){let o=p.python_path.trim();if(r.existsSync(o))return{path:o,source:"manual",extraArgs:[]}}let a=[_e.join(d,".paperforge-test-venv","Scripts","python.exe"),_e.join(d,".venv","Scripts","python.exe"),_e.join(d,"venv","Scripts","python.exe")];for(let o of a)try{if(r.existsSync(o))return{path:o,source:"auto-detected",extraArgs:[]}}catch(l){}let s=[{path:"py",extraArgs:["-3"]},{path:"python",extraArgs:[]},{path:"python3",extraArgs:[]}];for(let o of s)try{let l=n(o.path,[...o.extraArgs,"--version"],{encoding:"utf-8",timeout:5e3,windowsHide:!0});if(l&&l.toLowerCase().includes("python"))return{path:o.path,source:"auto-detected",extraArgs:o.extraArgs}}catch(l){}return{path:"python",source:"auto-detected",extraArgs:[]}}function Lt(d,p,e,t,r){t===void 0&&(t=1e4);let n=r||me.execFile;return new Promise(a=>{n(d,["-c","import paperforge; print(paperforge.__version__)"],{cwd:e,timeout:t},(s,o)=>{if(s){a({status:"not-installed",pyVersion:null,pluginVersion:p,error:s.message});return}let l=o&&o.trim()||null;a(l===p?{status:"match",pyVersion:l,pluginVersion:p,error:null}:{status:"mismatch",pyVersion:l,pluginVersion:p,error:null})})})}function mt(d){let p=String(d),t={ENOENT:{type:"python_missing",message:"Python executable not found",recoverable:!0},"python-missing":{type:"python_missing",message:"Python executable not found",recoverable:!0},MODULE_NOT_FOUND:{type:"import_failed",message:"PaperForge package not installed",recoverable:!0},"import-failed":{type:"import_failed",message:"PaperForge package not installed",recoverable:!0},"version-mismatch":{type:"version_mismatch",message:"Plugin and package versions differ",recoverable:!0,action:"sync-runtime"},"pip-failed":{type:"pip_install_failure",message:"pip install command failed",recoverable:!0},ETIMEDOUT:{type:"timeout",message:"Subprocess timed out",recoverable:!0,action:"retry"},timeout:{type:"timeout",message:"Subprocess timed out",recoverable:!0,action:"retry"},NO_PYTHON:{type:"no_python",message:"Python executable not found",recoverable:!0,action:"open-setup"},VECTOR_NOT_BUILT:{type:"vectors_not_built",message:"Vector index has not been built yet",recoverable:!0,action:"open-vector-settings"},VECTOR_CORRUPTED:{type:"vectors_corrupted",message:"Vector index is corrupted",recoverable:!0,action:"force-rebuild"},MODEL_CHANGED:{type:"model_changed",message:"Embedding model has changed since vectors were built",recoverable:!0,action:"rebuild-vectors"},BACKEND_UNAVAILABLE:{type:"backend_unavailable",message:"Python CLI search backend is not responding",recoverable:!0,action:"run-doctor"},TIMEOUT:{type:"timeout",message:"Search timed out",recoverable:!0,action:"retry"},INTERNAL_ERROR:{type:"internal_error",message:"An internal error occurred",recoverable:!1}}[p];return t?{...t}:{type:"unknown",message:String(d),recoverable:!1}}function Nt(d,p,e){e===void 0&&(e=[]);let t=`paperforge==${p}`,r=`git+https://github.com/LLLin000/PaperForge.git@${p}`,n=[...e,"-m","pip","install","--upgrade",t],a=[...e,"-m","pip","install","--upgrade",r];return{cmd:d,url:r,args:a,pypiArgs:n,gitArgs:a,timeout:12e4}}function Vt(d,p,e,t,r,n){let a=r||me.spawn;return new Promise(s=>{let o=Date.now(),l={cwd:e,timeout:t,windowsHide:!0};n&&(l.env=n);let c=a(d,p,l),u=[],h=[];c.stdout.on("data",_=>{u.push(_.toString("utf-8"))}),c.stderr.on("data",_=>{h.push(_.toString("utf-8"))}),c.on("close",_=>{s({stdout:u.join(""),stderr:h.join(""),exitCode:_,elapsed:Date.now()-o})}),c.on("error",_=>{s({stdout:u.join(""),stderr:h.join("")+` +`+_.message,exitCode:-1,elapsed:Date.now()-o})})})}function yt(){if(Mt)return gt;Mt=!0;try{let d;if(process.platform==="win32"){let p=process.env.ComSpec||"C:\\Windows\\System32\\cmd.exe";d=(0,me.execFileSync)(p,["/c","where","git"],{timeout:5e3,windowsHide:!0,encoding:"utf-8"})}else d=(0,me.execFileSync)("which",["git"],{timeout:5e3,encoding:"utf-8"});if(d){let p=d.split(` +`)[0].trim();p&&(gt=_e.dirname(p))}}catch(d){}return gt}function Ce(){let d={...process.env},p=process.platform,e=It.homedir(),t=[],r=yt();r&&t.push(r),p==="darwin"?t.push("/opt/homebrew/bin","/usr/local/bin","/usr/bin",`${e}/.local/bin`):p==="linux"&&t.push("/usr/local/bin","/usr/bin",`${e}/.local/bin`);let n=d.PATH||"";return d.PATH=[...t,n].filter(Boolean).join(_e.delimiter),d}function $t(d){return String(d).toLowerCase().replace(/[^a-z0-9]/g,"").includes("betterbibtex")}function vt(d){if(!d)return!1;try{if(!he.existsSync(d))return!1;for(let p of he.readdirSync(d))if($t(p))return!0}catch(p){}return!1}function Ge(d){if(!d)return!1;try{if(!he.existsSync(d))return!1;for(let p of he.readdirSync(d)){let e=_e.join(d,p,"extensions");try{if(!he.existsSync(e))continue;for(let t of he.readdirSync(e))if($t(t))return!0}catch(t){}}}catch(p){}return!1}var xe=K(require("fs")),U=K(require("path")),bt=require("child_process"),fe=null;function kr(d,p){let e=p||xe,t=U.join(d,"paperforge.json"),r={system_dir:"System",resources_dir:"Resources",literature_dir:"Literature",base_dir:"Bases"};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||{};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,_warning:null}}catch(n){return console.warn("PaperForge: Failed to read paperforge.json, using defaults",n),{...r,_warning:"paperforge.json invalid; using defaults"}}}function ce(d,p){let e=kr(d,p),t=U.join(d,e.system_dir,"PaperForge");return{vault:d,systemDir:t,indexesDir:U.join(t,"indexes"),logsDir:U.join(t,"logs"),dbPath:U.join(t,"indexes","paperforge.db"),memoryStatePath:U.join(t,"indexes","memory-runtime-state.json"),vectorStatePath:U.join(t,"indexes","vector-runtime-state.json"),healthStatePath:U.join(t,"indexes","runtime-health.json"),buildStatePath:U.join(t,"indexes","vector-build-state.json"),orphanStatePath:U.join(t,"indexes","sync-orphan-state.json"),exportsDir:U.join(t,"exports"),ocrDir:U.join(t,"ocr"),pluginDataPath:U.join(d,".obsidian","plugins","paperforge","data.json"),pfJsonPath:U.join(d,"paperforge.json"),configWarning:e._warning}}function Et(d){try{return xe.existsSync(d)?JSON.parse(xe.readFileSync(d,"utf-8")):null}catch(p){return null}}function wr(d){let p=ce(d);return Et(p.memoryStatePath)}var Ae=null;function Ye(d){let p=ce(d),e=Date.now();if(Ae&&Ae.vaultPath===d&&e-Ae.ts<2e3)return Ae.result;let t="",r=[U.join(d,".paperforge-test-venv","Scripts","python.exe"),U.join(d,".venv","Scripts","python.exe"),U.join(d,"venv","Scripts","python.exe")];for(let a=0;a({...a,_selected:!0,_idx:s})),this.vaultPath=r,this.py=n}_updateUI(){let e=this.orphans.filter(t=>t._selected);this._countEl.setText(i("orphan_delete_selected").replace("{count}",String(e.length))),this._selectAllBtn.setText(e.length===this.orphans.length?i("orphan_deselect_all"):i("orphan_select_all"));for(let t of this.orphans){let r=this._rowEls[t._idx];r&&r.toggleClass("paperforge-orphan-dimmed",!t._selected)}}onOpen(){let{contentEl:e}=this;e.addClass("paperforge-modal"),e.createEl("h2",{text:i("orphan_title").replace("{count}",String(this.orphans.length))}),e.createEl("p",{cls:"paperforge-modal-desc",text:i("orphan_desc")}),this._rowEls=[];let t=e.createEl("div",{cls:"paperforge-orphan-list"});for(let n of this.orphans){let a=t.createEl("div",{cls:"paperforge-orphan-row"+(n._selected?"":" paperforge-orphan-dimmed")});this._rowEls.push(a);let s=a.createEl("div",{cls:"paperforge-orphan-info"}),o=s.createEl("div",{cls:"paperforge-orphan-header"});o.createEl("span",{cls:"paperforge-orphan-key",text:n.citation_key||n.key});let l=o.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"}),n.collection_path&&l.createEl("span",{cls:"paperforge-tag tag-collection",text:n.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",{cls:"paperforge-orphan-meta",text:c.join(" \xB7 ")}),s.createEl("div",{cls:"paperforge-orphan-explain",text:i("orphan_explain")}),a.addEventListener("click",()=>{n._selected=!n._selected,this._updateUI()})}let r=e.createEl("div",{cls:"paperforge-modal-actions"});this._selectAllBtn=r.createEl("button",{cls:"paperforge-step-btn",text:"Deselect all"}),this._selectAllBtn.addEventListener("click",()=>{let n=this.orphans.every(a=>a._selected);for(let a of this.orphans)a._selected=!n;this._updateUI()}),this._countEl=r.createEl("button",{cls:"paperforge-step-btn mod-cta",text:"Delete "+this.orphans.length+" selected"}),r.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 ne.Notice(i("orphan_none_selected"));return}if(this._countEl.setText("Deleting..."),this._countEl.setAttr("disabled",""),this._selectAllBtn.setAttr("disabled",""),!this.py||!this.py.path){new ne.Notice("PaperForge: Python not found"),this.close();return}let a=n.map(s=>s.key);(0,Ke.execFile)(this.py.path,[...this.py.extraArgs,"-m","paperforge","--vault",this.vaultPath,"prune","--force","--json",...a],{cwd:this.vaultPath,timeout:6e4},(s,o)=>{if(s){new ne.Notice("PaperForge: prune failed"),this.close();return}try{let l=JSON.parse(o),c=l.data&&l.data.deleted||[];new ne.Notice("Deleted "+c.length+" orphan workspace(s)")}catch(l){new ne.Notice("PaperForge: prune done")}this.close()})})}onClose(){this.contentEl.empty()}};function Qe(d,p,e){console.log("[PF] checkOrphanState called");try{let r=ce(e).orphanStatePath;if(!pe.existsSync(r)){console.log("[PF] orphan file NOT FOUND");return}console.log("[PF] orphan file FOUND");let n=pe.readFileSync(r,"utf-8"),s=JSON.parse(n).orphans||[];if(console.log("[PF] orphans count:",s.length),s.length===0)return;let o=ye(e,p.settings);console.log("[PF] py.path:",o?o.path:"null"),new xt(d,s,e,o).open(),pe.unlinkSync(r),console.log("[PF] orphan file cleaned")}catch(t){console.log("[PF] checkOrphanState exception:",t.message||t)}}var Re=class extends ne.Modal{constructor(e,t,r){super(e);this._pendingSave=null;this._showSkipConfirm=!1;this.plugin=t,this._step=1,this._onComplete=r}onOpen(){this._render()}onClose(){this.contentEl.empty()}_render(){let{contentEl:e}=this;e.empty(),e.addClass("paperforge-modal"),this._renderStepIndicator(),this._renderStepContent(),this._renderNavigation()}_renderStepIndicator(){let e=[i("wizard_step1"),i("wizard_step2"),i("wizard_step3"),i("wizard_step4"),i("wizard_step5")],t=this.contentEl.createEl("div",{cls:"paperforge-step-bar"});e.forEach((r,n)=>{let a=n+1,s=t.createEl("div",{cls:`paperforge-step-dot ${a===this._step?"active":""} ${a1&&e.createEl("button",{cls:"paperforge-step-btn",text:i("nav_prev")}).addEventListener("click",()=>{this._step--,this._showSkipConfirm=!1,this._render()}),this._step<5?e.createEl("button",{cls:"paperforge-step-btn mod-cta",text:i("nav_next")}).addEventListener("click",()=>{if(this._step===3){let r=this._validateStep3();if(r.blocked){if(r.reason==="zotero")return;if(r.reason==="ocr"){this._showSkipConfirm=!0,this._render();return}}}this._step++,this._showSkipConfirm=!1,this._render()}):e.createEl("button",{cls:"paperforge-step-btn",text:i("nav_close")}).addEventListener("click",()=>this.close())}_validateStep3(){let e=this.plugin.settings,t=Ht(this._apiKeyValidated,e.zotero_data_dir);if(t.reason==="ocr")return t;let r=(e.zotero_data_dir||"").trim();if(!r)return new ne.Notice("Zotero \u6570\u636E\u76EE\u5F55\u4E3A\u5FC5\u586B\u9879\uFF0C\u8BF7\u586B\u5199\u8DEF\u5F84"),{blocked:!0,reason:"zotero"};if(!pe.existsSync(r))return new ne.Notice("Zotero \u6570\u636E\u76EE\u5F55\u8DEF\u5F84\u4E0D\u5B58\u5728"),{blocked:!0,reason:"zotero"};if(!pe.statSync(r).isDirectory())return new ne.Notice("Zotero \u6570\u636E\u76EE\u5F55\u8DEF\u5F84\u4E0D\u662F\u4E00\u4E2A\u76EE\u5F55"),{blocked:!0,reason:"zotero"};let n=zt.join(r,"storage");return!pe.existsSync(n)||!pe.statSync(n).isDirectory()?(new ne.Notice("Zotero \u6570\u636E\u76EE\u5F55\u4E2D\u672A\u627E\u5230 storage/ \u5B50\u76EE\u5F55"),{blocked:!0,reason:"zotero"}):{blocked:!1}}_stepOverview(e){e.createEl("h2",{text:i("wizard_title")}),e.createEl("p",{text:i("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"});a.textContent=`\u{1F4C1} Vault (${r})`;let s=n.createEl("div",{cls:"paperforge-dir-children"}),o=s.createEl("div",{cls:"paperforge-dir-node folder"});o.textContent=`\u{1F4C1} ${t.resources_dir||"Resources"}/ \u2014 \u6587\u732E\u5361\u7247\u76EE\u5F55\uFF08Base \u6570\u636E\u6765\u6E90\uFF09`,o.createEl("div",{cls:"paperforge-dir-children"}).createEl("div",{cls:"paperforge-dir-node file",text:`\u{1F4C1} ${t.literature_dir||"Literature"}/ \u2014 \u6587\u732E\u5361\u7247`}),s.createEl("div",{cls:"paperforge-dir-node folder",text:`\u{1F4C1} ${t.base_dir||"Bases"}/ \u2014 \u6570\u636E\u7BA1\u7406\u9762\u677F`}),s.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`}),e.createEl("p",{text:i("wizard_preview"),cls:"paperforge-modal-hint"}),e.createEl("p",{text:i("wizard_safety"),cls:"paperforge-modal-hint"});let c=e.createEl("div",{cls:"paperforge-summary"}),u=[{label:i("dir_resources"),val:`${r}/${t.resources_dir||"Resources"}`},{label:i("dir_notes"),val:`${r}/${t.resources_dir||"Resources"}/${t.literature_dir||"Literature"}`},{label:i("dir_base"),val:`${r}/${t.base_dir||"Bases"}`},{label:i("dir_system"),val:`${r}/${t.system_dir||"System"}`}];for(let h of u){let _=c.createEl("div",{cls:"paperforge-summary-row"});_.createEl("span",{cls:"paperforge-summary-label",text:h.label}),_.createEl("span",{cls:"paperforge-summary-value",text:h.val})}}_stepDirectories(e){e.createEl("h2",{text:i("wizard_step2")}),e.createEl("p",{text:i("wizard_intro")});let t=this.plugin.settings,r=this.app.vault.adapter.basePath;this._modalField(e,i("dir_vault"),r,!0),e.createEl("p",{text:i("wizard_dir_hint"),cls:"paperforge-modal-hint"}),this._modalInput(e,"\u8D44\u6E90\u76EE\u5F55\uFF08\u521B\u5EFA\u6587\u732E\u5361\u7247\u76EE\u5F55\u7684\u5730\u65B9\uFF09","resources_dir",t.resources_dir,"Resources"),e.createEl("p",{text:i("wizard_dir_sub_hint"),cls:"paperforge-modal-hint"}),this._modalInput(e,"\u6587\u732E\u5361\u7247\u76EE\u5F55\uFF08\u5B58\u653E\u6587\u732E\u5361\u7247\u7684\u5730\u65B9\uFF0CBase \u6570\u636E\u6765\u6E90\uFF09","literature_dir",t.literature_dir,"Literature"),e.createEl("p",{text:i("wizard_sys_hint"),cls:"paperforge-modal-hint"}),this._modalInput(e,"\u7CFB\u7EDF\u76EE\u5F55\uFF08\u5B58\u653E Zotero \u8F6F\u94FE\u63A5\u548C PaperForge \u7CFB\u7EDF\u6587\u4EF6\uFF09","system_dir",t.system_dir,"System"),this._modalInput(e,"Base \u76EE\u5F55\uFF08\u5B58\u653E\u6570\u636E\u7BA1\u7406\u9762\u677F\u7684\u5730\u65B9\uFF09","base_dir",t.base_dir,"Bases"),e.createEl("p",{text:i("wizard_safety"),cls:"paperforge-modal-hint"});let n=e.createEl("div",{cls:"paperforge-summary"}),a=[{label:i("dir_resources"),val:`${r}/${t.resources_dir||""}`},{label:i("dir_notes"),val:`${r}/${t.resources_dir||""}/${t.literature_dir||""}`},{label:i("dir_system"),val:`${r}/${t.system_dir||""}`},{label:i("dir_base"),val:`${r}/${t.base_dir||""}`}];for(let s of a){let o=n.createEl("div",{cls:"paperforge-summary-row"});o.createEl("span",{cls:"paperforge-summary-label",text:s.label}),o.createEl("span",{cls:"paperforge-summary-value",text:s.val})}}_stepKeys(e){if(e.createEl("h2",{text:i("wizard_step3")}),this._showSkipConfirm){this._renderSkipConfirm(e);return}let t=this.plugin.settings;e.createEl("p",{text:i("wizard_agent_hint"),cls:"paperforge-modal-hint"});let r=[{key:"opencode",name:"OpenCode"},{key:"claude",name:"Claude Code"},{key:"cursor",name:"Cursor"},{key:"github_copilot",name:"GitHub Copilot"},{key:"windsurf",name:"Windsurf"},{key:"codex",name:"Codex"},{key:"gemini",name:"Gemini CLI"},{key:"cline",name:"Cline"}],n=e.createEl("div",{cls:"paperforge-modal-field"});n.createEl("label",{cls:"paperforge-modal-label",text:i("label_agent")});let a=n.createEl("select",{cls:"paperforge-modal-select"});for(let h of r){let _=a.createEl("option",{text:h.name,attr:{value:h.key}});h.key===(t.agent_platform||"opencode")&&(_.selected=!0)}a.addEventListener("change",()=>{t.agent_platform=a.value,this._pendingSave&&clearTimeout(this._pendingSave),this._pendingSave=setTimeout(()=>{this.plugin.saveSettings(),this._pendingSave=null},500)}),e.createEl("p",{text:i("wizard_keys_hint"),cls:"paperforge-modal-hint"});let s=e.createEl("div",{cls:"paperforge-modal-field"});s.createEl("label",{cls:"paperforge-modal-label",text:i("field_paddleocr")});let o=s.createEl("input",{cls:"paperforge-modal-input",attr:{type:"password",placeholder:"API Key"}});o.value=t.paddleocr_api_key||"",this._apiKeyValidated=!1,this._apiKeyStatus=s.createEl("span",{cls:"paperforge-apikey-status",text:""});let l=s.createEl("button",{cls:"paperforge-step-btn",text:"\u9A8C\u8BC1"});l.addEventListener("click",()=>this._validateApiKey(o.value,l)),o.addEventListener("input",()=>{t.paddleocr_api_key=o.value,this._apiKeyValidated=!1,this._apiKeyStatus.textContent="",this._apiKeyStatus.className="paperforge-apikey-status"}),this._pendingSave&&clearTimeout(this._pendingSave),this._pendingSave=setTimeout(()=>{this.plugin.saveSettings(),this._pendingSave=null},500),e.createEl("p",{text:i("wizard_api_hint_skip"),cls:"paperforge-modal-hint"});let c=e.createEl("div",{cls:"paperforge-modal-field"});c.createEl("label",{cls:"paperforge-modal-label",text:i("field_zotero_data")});let u=c.createEl("input",{cls:"paperforge-modal-input",attr:{type:"text",placeholder:i("field_zotero_placeholder")}});u.value=t.zotero_data_dir||"",u.addEventListener("input",()=>{t.zotero_data_dir=u.value,this._pendingSave&&clearTimeout(this._pendingSave),this._pendingSave=setTimeout(()=>{this.plugin.saveSettings(),this._pendingSave=null},500)})}_validateApiKey(e,t){if(!e||e.length<10){this._apiKeyStatus.textContent="\u5BC6\u94A5\u683C\u5F0F\u4E0D\u6B63\u786E\u3002\u53EF\u70B9\u4E0B\u4E00\u6B65\u8DF3\u8FC7\uFF0C\u7B49\u4F1A\u513F\u518D\u914D\u7F6E\u3002",this._apiKeyStatus.className="paperforge-apikey-status error";return}t.disabled=!0,t.textContent="\u9A8C\u8BC1\u4E2D\u2026",this._apiKeyStatus.textContent="\u6B63\u5728\u9A8C\u8BC1\u2026",this._apiKeyStatus.className="paperforge-apikey-status";let r=JSON.stringify({model:"PaddleOCR-VL-1.5"}),n={hostname:"paddleocr.aistudio-app.com",path:"/api/v2/ocr/jobs",method:"POST",headers:{Authorization:"bearer "+e,"Content-Type":"application/json","Content-Length":Buffer.byteLength(r)},timeout:1e4},a=Kt.request(n,s=>{t.disabled=!1,t.textContent="\u9A8C\u8BC1";let o="";s.on("data",l=>o+=l),s.on("end",()=>{try{let l=JSON.parse(o);s.statusCode===400&&l.code===10001?(this._apiKeyStatus.textContent="\u2713 \u5BC6\u94A5\u6709\u6548",this._apiKeyStatus.className="paperforge-apikey-status ok",this._apiKeyValidated=!0):s.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="paperforge-apikey-status error",this._apiKeyValidated=!1):(this._apiKeyStatus.textContent="\u9A8C\u8BC1\u5931\u8D25\uFF1AAPI \u8FD4\u56DE "+s.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){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="paperforge-apikey-status error",this._apiKeyValidated=!1}})});a.on("error",s=>{t.disabled=!1,t.textContent="\u9A8C\u8BC1",this._apiKeyStatus.textContent="\u9A8C\u8BC1\u5931\u8D25\uFF1A\u65E0\u6CD5\u8FDE\u63A5 ("+s.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}),a.write(r),a.end()}_renderSkipConfirm(e){e.createEl("p",{text:i("wizard_skip_ocr_desc"),cls:"paperforge-modal-desc"});let t=e.createEl("div",{cls:"paperforge-modal-actions"});t.createEl("button",{cls:"paperforge-step-btn mod-cta",text:i("wizard_skip_ocr_continue")}).addEventListener("click",()=>{this._showSkipConfirm=!1,this._step++,this._render()}),t.createEl("button",{cls:"paperforge-step-btn",text:i("wizard_skip_ocr_back")}).addEventListener("click",()=>{this._showSkipConfirm=!1,this._render()})}_modalField(e,t,r,n){let a=e.createEl("div",{cls:"paperforge-modal-field"});a.createEl("label",{cls:"paperforge-modal-label",text:t});let s=a.createEl("input",{cls:"paperforge-modal-input",attr:{type:"text"}});s.value=r,s.disabled=!!n}_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 o=s.createEl("input",{cls:"paperforge-modal-input",attr:{type:"text",placeholder:a||""}});o.value=n;let l=this.plugin.settings;o.addEventListener("input",()=>{l[r]=o.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 o=s.createEl("input",{cls:"paperforge-modal-input",attr:{type:"password",placeholder:a||""}});o.value=n;let l=this.plugin.settings;o.addEventListener("input",()=>{l[r]=o.value,this._pendingSave&&clearTimeout(this._pendingSave),this._pendingSave=setTimeout(()=>{this.plugin.saveSettings(),this._pendingSave=null},500)})}_stepInstall(e){e.createEl("h2",{text:i("wizard_step4")}),this._installLog=e.createEl("div",{cls:"paperforge-install-log"});let t=e.createEl("button",{cls:"paperforge-step-btn mod-cta",text:i("install_btn")});t.addEventListener("click",()=>this._runInstall(t))}async _runInstall(e){var s,o,l,c,u,h;e.disabled=!0,e.textContent=i("install_btn_running"),this._installLog.setText(i("install_validating")+` +`),this._log(i("install_validating"));let t=this.plugin.settings,r=this._validate();if(r.length>0){this._log(i("validate_fail")+":"),r.forEach(_=>this._log(" \u2717 "+_)),e.disabled=!1,e.textContent=i("install_btn_retry");return}let n=(_,f={})=>new Promise((g,E)=>{let{path:m,extraArgs:b=[]}=z(t.vault_path.trim(),this.plugin.settings,void 0,void 0),x=(0,Ke.spawn)(m,[...b,..._],{cwd:t.vault_path.trim(),env:Ce(),timeout:12e4,...f}),k="",y="";x.stdout.on("data",v=>{let S=v.toString("utf-8");k+=S,f.logStdout&&this._processSetupOutput(S)}),x.stderr.on("data",v=>{let S=v.toString("utf-8");y+=S,this._log("[stderr] "+S.trim())}),x.on("close",v=>{v===0?g({stdout:k,stderr:y}):E(new Error(y.trim()||k.trim()||`exit code ${v}`))}),x.on("error",v=>E(v))}),a=["-m","paperforge","--vault",t.vault_path.trim(),"setup","--headless","--system-dir",t.system_dir.trim(),"--resources-dir",t.resources_dir.trim(),"--literature-dir",t.literature_dir.trim(),"--base-dir",t.base_dir.trim(),"--agent",t.agent_platform||"opencode"];t.zotero_data_dir&&t.zotero_data_dir.trim()&&a.push("--zotero-data",t.zotero_data_dir.trim()),t.paddleocr_api_key&&t.paddleocr_api_key.trim()&&a.push("--paddleocr-key",t.paddleocr_api_key.trim());try{let _=!0;try{await n(["-c","import paperforge"])}catch(f){_=!1}if(!_){this._log(i("install_bootstrapping"));let f=this.plugin.manifest.version;this._log(`[install] Trying PyPI: pip install paperforge==${f}`);let g=["-m","pip","install","--upgrade"];process.platform!=="win32"&&g.push("--user"),g.push(`paperforge==${f}`);try{await n(g,{logStdout:!0})}catch(E){this._log(`[install] PyPI failed, falling back to git: git+https://...@v${f}`),console.warn("[PaperForge] PyPI install failed, falling back to git:",(s=E.message)==null?void 0:s.slice(0,200));let m=["-m","pip","install","--upgrade"];process.platform!=="win32"&&m.push("--user"),m.push(`git+https://github.com/LLLin000/PaperForge.git@v${f}`),await n(m,{logStdout:!0})}}await n(a,{logStdout:!0,env:Ce()}),this._log(i("install_complete")),t.setup_complete=!0,await this.plugin.saveSettings(),this._onComplete&&this._onComplete(),setTimeout(()=>{this._step=5,this._render()},800)}catch(_){console.error("PaperForge setup failed:",_.message);let f=this._formatSetupError(_.message);this._log(i("install_failed")+f);let g=(o=this._installLog.parentElement)==null?void 0:o.createEl("button",{cls:"paperforge-copy-diag-btn",text:i("error_copy_diagnostic")||"Copy diagnostic"});if(g){let E=_.message,m=((c=(l=this.plugin)==null?void 0:l.settings)==null?void 0:c.python_path)||"auto",b=((h=(u=this.plugin)==null?void 0:u.manifest)==null?void 0:h.version)||"?",x=process.platform+" "+process.arch,k,y;try{k=yt()||"(not found)"}catch(P){k="(error)"}try{y=z(t.vault_path.trim(),this.plugin.settings,void 0,void 0)}catch(P){y=null}let v=(process.env.PATH||"").length,S=(process.env.PATH||"").toLowerCase().includes("git"),R=["[PaperForge Diagnostic]","Category: "+f,"Plugin version: "+b,"Python: "+m,"Resolved Python: "+((y==null?void 0:y.path)||"?"),"OS: "+x,"Vault path: "+(t.vault_path||"?"),"--- Git ---","Git dir (resolved): "+k,"PATH length: "+v+" chars","PATH contains git: "+S,"--- Raw error ---",E.slice(0,2e3)].join(` +`);g.addEventListener("click",()=>{navigator.clipboard.writeText(R).then(()=>{g.setText(i("error_copied")||"Copied!"),setTimeout(()=>{g.setText(i("error_copy_diagnostic")||"Copy diagnostic")},3e3)}).catch(()=>{new ne.Notice("[!!] Clipboard write failed",6e3)})})}e.disabled=!1,e.textContent=i("install_btn_retry")}}_log(e){this._installLog&&this._installLog.setText(this._installLog.textContent+e+` +`)}_validate(){let e=[],t=this.plugin.settings;return(!t.vault_path||!t.vault_path.trim())&&e.push(i("validate_vault")),(!t.resources_dir||!t.resources_dir.trim())&&e.push(i("validate_resources")),(!t.literature_dir||!t.literature_dir.trim())&&e.push(i("validate_notes")),(!t.base_dir||!t.base_dir.trim())&&e.push(i("validate_base")),(!t.paddleocr_api_key||!t.paddleocr_api_key.trim())&&this._log(" ! "+i("validate_key")+" "+i("optional_later")),(!t.zotero_data_dir||!t.zotero_data_dir.trim())&&this._log(" ! "+i("validate_zotero")+" "+i("optional_later")),e}_processSetupOutput(e){let t=e.split(` `).filter(Boolean);for(let r of t)if(r.includes("[*]")||r.includes("[OK]")||r.includes("[FAIL]")){let n=r.replace(/^\[\*\].*\d+:?\s*/,"").replace(/^\[OK\]\s*/,"").replace(/^\[FAIL\]\s*/,"");this._log(" "+n)}}_formatSetupError(e){if(process.platform==="darwin"&&/No module named ['"]?paperforge/i.test(e))return"PaperForge not installed \u2014 install Python from Homebrew or python.org (Apple CLT /Library/Developer/CommandLineTools python often fails); then: python3 -m pip install --user git+https://github.com/LLLin000/PaperForge.git";let t=[{match:/pip.*not found|No module named.*pip|command not found.*pip/i,msg:"pip not found"},{match:/command not found|No such file|not recognized/i,msg:"Python not found"},{match:/resolve host|getaddrinfo.*nodename|connect ETIMEDOUT|connect ECONNREFUSED|fetch failed|Network error|ENOTFOUND|ECONNREFUSED|ECONNRESET/i,msg:"Network error"},{match:/certificate verify failed|SSL.*certificate|self.signed.cert|CERTIFICATE_VERIFY_FAILED/i,msg:"SSL certificate error"},{match:/No space left on device|disk full|ENOSPC/i,msg:"Disk full"},{match:/paperforge.*not found|cannot import|ModuleNotFoundError|No module named/i,msg:"PaperForge not installed"},{match:/permission denied|EACCES|EPERM/i,msg:"Permission denied"},{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;return e.split(` -`).filter(Boolean).slice(0,3).join(" | ").slice(0,200)||"Unknown error"}_stepComplete(e){e.createEl("h2",{text:o("complete_title")});let t=e.createEl("div",{cls:"paperforge-summary"});t.createEl("div",{cls:"paperforge-summary-title",text:o("complete_summary")});let r=this.plugin.settings,n=this.app.vault.adapter.basePath,a=[{label:o("dir_vault"),val:n},{label:o("dir_resources"),val:`${n}/${r.resources_dir}`},{label:o("dir_notes"),val:`${n}/${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:"API Key",val:r.paddleocr_api_key?o("api_key_set"):o("api_key_missing")},{label:o("field_zotero_data"),val:r.zotero_data_dir||o("not_set")}];for(let _ of a){let h=t.createEl("div",{cls:"paperforge-summary-row"});h.createEl("span",{cls:"paperforge-summary-label",text:_.label}),h.createEl("span",{cls:"paperforge-summary-value",text:_.val})}let s=t.createEl("div",{cls:"paperforge-summary-row"});s.createEl("span",{cls:"paperforge-summary-label",text:"PaperForge"});let i=s.createEl("span",{cls:"paperforge-summary-value",text:"\u2014"});{let _=n,{path:h,extraArgs:u=[]}=z(_,this.plugin.settings,void 0,void 0);(0,He.execFile)(h,[...u,"-c","import paperforge; print(paperforge.__version__)"],{cwd:_,timeout:1e4},(m,f)=>{!m&&f&&(i.textContent="v"+f.trim())})}for(let _ of a){let h=t.createEl("div",{cls:"paperforge-summary-row"});h.createEl("span",{cls:"paperforge-summary-label",text:_.label}),h.createEl("span",{cls:"paperforge-summary-value",text:_.val})}e.createEl("h3",{text:o("complete_next")});let l=e.createEl("div",{cls:"paperforge-nextsteps"}),c=[[o("complete_step4"),o("complete_step4_desc")],["",`${o("complete_export_path")} ${n}/${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[_,h]of c){let u=l.createEl("div",{cls:"paperforge-nextstep-item"});_&&u.createEl("strong",{text:_}),u.createEl("span",{text:h})}}};var Oe=K(require("fs")),Qe=K(require("path")),Wt=require("child_process");function xt(d){return d.display_action==="rebuild_result"?"rebuild":d.display_action==="retry_ocr"||d.display_action==="upgrade_legacy"?"redo":null}function kt(d){return d==="redo"}function Pr(d,p,e){let t={manifest:d,papers:{},cached_at:new Date().toISOString()};if(e!=null&&e.papers)for(let r of Object.keys(d))e.papers[r]&&(t.papers[r]=e.papers[r]);for(let r of p)t.papers[r.key]=r;return t}function qt(d){return Qe.join(d,"System","PaperForge","cache","ocr_maintenance.json")}function et(d){try{let p=qt(d),e=Oe.readFileSync(p,"utf-8");return JSON.parse(e)}catch(p){return null}}function Sr(d,p){let e=qt(d),t=Qe.dirname(e);Oe.mkdirSync(t,{recursive:!0}),Oe.writeFileSync(e,JSON.stringify(p,null,2),"utf-8")}function Ut(d,p,e){return new Promise((t,r)=>{(0,Wt.execFile)(d,p,e,(n,a)=>{n?r(n):t(a)})})}async function wt(d,p,e,t){let r=await Ut(p,[...e,"-m","paperforge","ocr","list","--manifest"],{cwd:d,timeout:3e4}),n=JSON.parse(r);if(t){let _=Object.keys(t.manifest),h=Object.keys(n);if(_.length===h.length&&_.every(m=>t.manifest[m]===n[m])&&Object.values(t.papers).every(f=>typeof f.needs_derived_rebuild=="boolean"))return{data:Object.values(t.papers),changed:!1}}let a=Object.keys(n),s=await Ut(p,[...e,"-m","paperforge","ocr","list","--json","--keys",...a],{cwd:d,timeout:3e4}),i=JSON.parse(s),l=Pr(n,i,t);return Sr(d,l),{data:Object.values(l.papers),changed:!0}}var Pt=K(require("fs")),H=K(require("path")),st=require("child_process"),Xt=K(require("os")),Zt=300*1e3,Be="3.11",Jt="3.10";function tt(){let d,p;return{promise:new Promise((t,r)=>{d=t,p=r}),resolve:d,reject:p}}function Gt(d){let p=d.match(/Python\s+(\d+\.\d+(?:\.\d+)?)/);if(p)return p[1];let e=d.match(/Python\s+(\d+\.\d+)/);return e?e[1]+".0":null}function Qt(d,p){var r,n;let e=d.split(".").map(Number),t=p.split(".").map(Number);for(let a=0;a=0}function Cr(){var d;return process.env.FLATPAK_ID!==void 0||((d=process.env.XDG_DATA_DIRS)!=null?d:"").includes("flatpak")||!1}function Rr(){return process.env.SNAP!==void 0||process.env.SNAP_NAME!==void 0||!1}function Yt(d,p){var t;return`${(t={win32:"windows",darwin:"macos",linux:"linux"}[d])!=null?t:d}-${p}`}function er(d,p,e){if(p!==void 0||e!==void 0){if(e)return[{verb:"stop",label:"Stop"}];switch(d.state){case"not_installed":return[{verb:"install",label:"Install Runtime"}];case"needs_repair":{let t=[{verb:"repair",label:"Repair Runtime"}];return d.pythonPath&&t.push({verb:"rollback",label:"Rollback"}),t}case"ready":{let t=[{verb:"status",label:"Check Status"},{verb:"update",label:"Update Runtime"}];return d.previousVersion&&t.push({verb:"rollback",label:"Rollback"}),t}case"unknown":return[{verb:"retry",label:"Retry"}];case"unavailable":return[{verb:"setup",label:"Manual Setup"}];default:return[{verb:"retry",label:"Retry"}]}}switch(d.state){case"not_installed":return[{id:"install",label:"Install Runtime",primary:!0,destructive:!1}];case"needs_repair":{let t=[{id:"repair",label:"Repair Runtime",primary:!0,destructive:!1}];return d.pythonPath&&t.push({id:"rollback",label:"Rollback",primary:!1,destructive:!1}),t}case"ready":return[{id:"status",label:"Check Status",primary:!1,destructive:!1},{id:"update",label:"Update Runtime",primary:!1,destructive:!1}];case"unknown":return[{id:"probe",label:"Refresh Status",primary:!0,destructive:!1}];case"unavailable":return[{id:"setup",label:"Manual Setup",primary:!0,destructive:!1}];default:return[{id:"probe",label:"Refresh Status",primary:!0,destructive:!1}]}}function tr(d){return d.state!=="ready"||!d.pythonPath?null:{command:d.pythonPath,args:[]}}var nt=class{constructor(p){this._cache=null;this._cacheTime=0;var r,n,a,s,i,l,c,_,h,u,m;let e=(n=(r=p.osPlatform)!=null?r:p.platform)!=null?n:process.platform,t=(s=(a=p.osArch)!=null?a:p.arch)!=null?s:process.arch;if(this.osPlatform=e,this.osArch=t,this.triplet=`${e}-${t}`,p.runtimeDir)this.runtimeDir=p.runtimeDir,this.rootDir=H.dirname(p.runtimeDir),this.pluginVersion=(l=(i=p.pluginVersion)!=null?i:p.version)!=null?l:"0.0.0";else{let f=Xt.homedir();this.rootDir=H.join(f,".paperforge","runtime"),this.runtimeDir=H.join(this.rootDir,Yt(e,t)),this.pluginVersion=(_=(c=p.version)!=null?c:p.pluginVersion)!=null?_:"0.0.0"}this.pointerPath=H.join(this.rootDir,"active-runtime.json"),this._fs=(h=p.fs)!=null?h:Pt,this._execFile=(u=p.execFile)!=null?u:st.execFile,this._execFileSync=(m=p.execFileSync)!=null?m:st.execFileSync}current(){return this._cache?Date.now()-this._cacheTime>Zt?{...this._cache,state:"unknown",stale:!0}:{...this._cache,stale:!1}:{state:"unknown",pythonPath:null,version:null,source:"none",error:null,lastVerifiedAt:null,stale:!0,warnings:[],previousVersion:null,previousPythonPath:null}}async status(p){var s;if(this._cache){let i=Date.now()-this._cacheTime>Zt;if(!i&&this._cache.state==="ready")return{...this._cache,stale:!1};if(i&&(p!=null&&p.allowStale))return{...this._cache,stale:!0}}let e=null,t=null,r=null,n=null,a=[];try{let i=this._fs.readFileSync(this.pointerPath,"utf-8"),l=JSON.parse(i);e=typeof l.version=="string"?l.version:null;let c=typeof l.pythonPath=="string"?l.pythonPath:null;t=c?H.resolve(H.dirname(this.pointerPath),c):null,r=typeof l.previousVersion=="string"?l.previousVersion:null,n=typeof l.previousPythonPath=="string"?l.previousPythonPath:null,a=Array.isArray(l.warnings)?l.warnings:[]}catch(i){return this._setCache({state:"not_installed",pythonPath:null,version:null,source:"none",error:null,lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null})}if(!t)return this._setCache({state:"needs_repair",pythonPath:null,version:e,source:"none",error:{code:"POINTER_MISSING_PATH",message:"Active runtime pointer has no pythonPath",platformAction:"Reinstall runtime"},lastVerifiedAt:null,stale:!1,warnings:a,previousVersion:r,previousPythonPath:n});if(!this._fs.existsSync(t))return this._setCache({state:"needs_repair",pythonPath:t,version:e,source:"none",error:{code:"PYTHON_NOT_FOUND",message:"Python executable not found at pointer path",platformAction:"Reinstall runtime"},lastVerifiedAt:null,stale:!1,warnings:a,previousVersion:r,previousPythonPath:n});try{let i=await this._probe(t),l=[...a];try{let c=this._execFileSync(t,["--version"],{encoding:"utf-8",timeout:5e3}),_=Gt(c);_&&rt(_,Jt)&&!rt(_,Be)&&(l.some(h=>h.code==="PYTHON_310_DEPRECATED")||l.push({code:"PYTHON_310_DEPRECATED",message:`Python ${_} is running. Python 3.10 support enters legacy phase in Release N \u2014 upgrade to Python ${Be}+ recommended.`,platformAction:`Upgrade to Python ${Be}+`}))}catch(c){}return this._setCache({state:"ready",pythonPath:t,version:(s=i.version)!=null?s:e,source:"venv",error:null,lastVerifiedAt:new Date().toISOString(),stale:!1,warnings:l,previousVersion:r,previousPythonPath:n})}catch(i){let l=i instanceof Error?i.message:String(i);return this._setCache({state:"needs_repair",pythonPath:t,version:e,source:"venv",error:{code:"PROBE_FAILED",message:l,platformAction:"Repair runtime"},lastVerifiedAt:null,stale:!1,warnings:a,previousVersion:r,previousPythonPath:n})}}async ensure(p){var g,b;let e=(g=p==null?void 0:p.version)!=null?g:this.pluginVersion,t=(b=p==null?void 0:p.force)!=null?b:!1,r=p==null?void 0:p.signal;if(r!=null&&r.aborted)return this._abortedHealth();if(!t){let E=this.current();if(E.state==="ready"&&!E.stale){let k=await this.status();if(k.state==="ready")return k}}if(r!=null&&r.aborted)return this._abortedHealth();let n;try{n=this._resolveBootstrapPython()}catch(E){if(Cr()||Rr())return this._setCache({state:"unavailable",pythonPath:null,version:null,source:"none",error:{code:"FLATPAK_SNAP_UNSUPPORTED",message:"Flatpak and Snap are not supported. Install Python 3.11+ natively.",platformAction:"Install Python 3.11+ from python.org or package manager"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null});let k=Yt(this.osPlatform,this.osArch),y=this.osPlatform==="darwin",v=["macos-x64","macos-arm64"],S=["windows-x64","linux-x64"];return y&&v.includes(k)?this._setCache({state:"unavailable",pythonPath:null,version:null,source:"none",error:{code:"NO_PYTHON",message:"No Python 3.11+ found. macOS auto-download disabled until signed/notarized artifacts exist.",platformAction:"Install Python 3.11+ from python.org or Homebrew"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null}):S.includes(k)?this._setCache({state:"unavailable",pythonPath:null,version:null,source:"none",error:{code:"NO_PYTHON",message:"No Python 3.11+ found and automatic download failed.",platformAction:"Install Python 3.11+ manually"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null}):this._setCache({state:"unavailable",pythonPath:null,version:null,source:"none",error:{code:"FALLBACK_UNAVAILABLE",message:"No Python found and this platform has no validated fallback.",platformAction:"Install Python 3.11+ manually from python.org"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null})}if(r!=null&&r.aborted)return this._abortedHealth();let a=this._currentSlotExists(e);if(!rt(n.version,Jt))return this._setCache({state:"unavailable",pythonPath:null,version:n.version,source:"none",error:{code:"PYTHON_TOO_OLD",message:`Python ${n.version} is too old. Python 3.10+ required.`,platformAction:"Install Python 3.10+"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null});if(!a&&!rt(n.version,Be))return this._setCache({state:"needs_repair",pythonPath:null,version:n.version,source:"none",error:{code:"PYTHON_VERSION_WARNING",message:`Python ${n.version} found. New installations require Python ${Be}+.`,platformAction:`Upgrade to Python ${Be}+`},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null});if(a&&!t){let E=!1;try{let k=this._fs.readFileSync(this.pointerPath,"utf-8"),y=JSON.parse(k),v=typeof y.version=="string"?y.version:null;E=v!==null&&v!==e}catch(k){}if(E){let k=H.join(this.runtimeDir,`v${e}`),y=H.join(k,"venv"),v=this.osPlatform==="win32"?H.join(y,"Scripts","python.exe"):H.join(y,"bin","python");try{await this._probe(v,r)}catch(B){if(B instanceof Error&&B.name==="AbortError")return this._abortedHealth();let w=B instanceof Error?B.message:String(B);return this._setCache({state:"needs_repair",pythonPath:v,version:e,source:"venv",error:{code:"RETAINED_SLOT_PROBE_FAILED",message:`Retained slot v${e} failed verification: ${w}`,platformAction:"Repair runtime"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null})}let S=null,C=null;try{let B=this._fs.readFileSync(this.pointerPath,"utf-8"),w=JSON.parse(B);S=typeof w.version=="string"?w.version:null,C=typeof w.pythonPath=="string"?w.pythonPath:null}catch(B){}let P=H.dirname(this.pointerPath);this._fs.existsSync(P)||this._fs.mkdirSync(P,{recursive:!0});let R=H.relative(H.dirname(this.pointerPath),v),I=JSON.stringify({schema_version:1,version:e,pythonPath:R,activatedAt:new Date().toISOString(),previousVersion:S,previousPythonPath:C},null,2),M=this.pointerPath+".tmp";this._fs.writeFileSync(M,I,"utf-8"),this._fs.renameSync(M,this.pointerPath);let O={state:"ready",pythonPath:v,version:e,source:"venv",error:null,lastVerifiedAt:new Date().toISOString(),stale:!1,warnings:[],previousVersion:S,previousPythonPath:C};return this._cache=O,this._cacheTime=Date.now(),this._cleanupOldSlots(e),O}}if(r!=null&&r.aborted)return this._abortedHealth();let s=t?H.join(this.runtimeDir,`v${e}_build2`):H.join(this.runtimeDir,`v${e}`),i=H.join(s,"venv"),l=this.osPlatform==="win32"?H.join(i,"Scripts","python.exe"):H.join(i,"bin","python");try{this._fs.mkdirSync(s,{recursive:!0});let{promise:E,reject:k,resolve:y}=tt();this._execFile(n.path,["-m","venv",i],{timeout:6e4,signal:r},v=>{v?k(v):y()}),await E}catch(E){if(E instanceof Error&&E.name==="AbortError"){try{this._fs.rmSync(s,{recursive:!0,force:!0})}catch(k){}return this._abortedHealth()}return this._slotFailed(e,"VENV_CREATION_FAILED",E,s)}if(r!=null&&r.aborted)return this._abortedHealth();try{let{promise:E,reject:k,resolve:y}=tt();this._execFile(l,["-m","pip","install",`paperforge==${e}`],{timeout:12e4,signal:r},v=>{v?k(v):y()}),await E}catch(E){if(E instanceof Error&&E.name==="AbortError"){try{this._fs.rmSync(s,{recursive:!0,force:!0})}catch(k){}return this._abortedHealth()}return this._slotFailed(e,"PIP_INSTALL_FAILED",E,s)}if(r!=null&&r.aborted)return this._abortedHealth();try{let{promise:E,reject:k,resolve:y}=tt();this._execFile(l,["-I","-c","import paperforge; print(paperforge.__version__)"],{timeout:3e4,signal:r},v=>{v?k(v):y()}),await E}catch(E){if(E instanceof Error&&E.name==="AbortError"){try{this._fs.rmSync(s,{recursive:!0,force:!0})}catch(k){}return this._abortedHealth()}return this._slotFailed(e,"VERIFY_FAILED",E,s)}let c=null,_=null;try{let E=this._fs.readFileSync(this.pointerPath,"utf-8"),k=JSON.parse(E);c=typeof k.version=="string"?k.version:null,_=typeof k.pythonPath=="string"?k.pythonPath:null}catch(E){}let h=H.dirname(this.pointerPath);this._fs.existsSync(h)||this._fs.mkdirSync(h,{recursive:!0});let u=H.relative(H.dirname(this.pointerPath),l),m=JSON.stringify({schema_version:1,version:e,pythonPath:u,activatedAt:new Date().toISOString(),previousVersion:c,previousPythonPath:_},null,2),f=this.pointerPath+".tmp";this._fs.writeFileSync(f,m,"utf-8"),this._fs.renameSync(f,this.pointerPath);let x={state:"ready",pythonPath:l,version:e,source:"venv",error:null,lastVerifiedAt:new Date().toISOString(),stale:!1,warnings:[],previousVersion:c,previousPythonPath:_};return this._cache=x,this._cacheTime=Date.now(),this._cleanupOldSlots(e),x}_setCache(p){return this._cache=p,this._cacheTime=Date.now(),p}_abortedHealth(){return{state:"needs_repair",pythonPath:null,version:null,source:"none",error:{code:"ABORTED",message:"Operation was cancelled",platformAction:"Retry operation"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null}}_slotFailed(p,e,t,r){try{this._fs.rmSync(r,{recursive:!0,force:!0})}catch(a){}let n=t instanceof Error?t.message:String(t);return this._setCache({state:"needs_repair",pythonPath:null,version:p,source:"none",error:{code:e,message:n,platformAction:"Retry installation"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null})}_currentSlotExists(p){let e=H.join(this.runtimeDir,`v${p}`);return this._fs.existsSync(e)}_resolveBootstrapPython(){let p=[];this.osPlatform==="win32"?p.push({path:"py",args:["-3.11"]},{path:"py",args:["-3.10"]},{path:"py",args:["-3"]},{path:"python",args:[]}):this.osPlatform==="darwin"?p.push({path:"/usr/bin/python3",args:[]},{path:"python3",args:[]},{path:"python",args:[]}):p.push({path:"/usr/bin/python3",args:[]},{path:"python3",args:[]},{path:"python",args:[]});for(let e of p)try{let t=this._execFileSync(e.path,[...e.args,"--version"],{encoding:"utf-8",timeout:5e3}),r=Gt(t);if(r)return{path:e.path,version:r}}catch(t){}throw new Error("No Python 3.10+ found on system")}_probe(p,e){let{promise:t,resolve:r,reject:n}=tt();return this._execFile(p,["-I","-c","import paperforge; print(paperforge.__version__)"],{timeout:3e4,signal:e},(a,s)=>{if(a)n(a);else{let i=(s!=null?s:"").trim()||null;r({version:i})}}),t}_cleanupOldSlots(p,e=2){try{let r=this._fs.readdirSync(this.runtimeDir,{withFileTypes:!0}).filter(n=>n.isDirectory()&&n.name.startsWith("v")).map(n=>{let a=n.name.replace(/^v/,"").replace(/_build\d+$/,"");return{name:n.name,version:a}}).filter(n=>n.version!==p).sort((n,a)=>Qt(a.version,n.version));for(let n=e;n{!f&&g&&(o.textContent="v"+g.trim())})}for(let u of a){let h=t.createEl("div",{cls:"paperforge-summary-row"});h.createEl("span",{cls:"paperforge-summary-label",text:u.label}),h.createEl("span",{cls:"paperforge-summary-value",text:u.val})}e.createEl("h3",{text:i("complete_next")});let l=e.createEl("div",{cls:"paperforge-nextsteps"}),c=[[i("complete_step4"),i("complete_step4_desc")],["",`${i("complete_export_path")} ${n}/${r.system_dir}/PaperForge/exports/`],[i("complete_step1"),i("complete_step1_desc")],[i("complete_step2"),i("complete_step2_desc")],[i("complete_step3"),i("complete_step3_desc")]];for(let[u,h]of c){let _=l.createEl("div",{cls:"paperforge-nextstep-item"});u&&_.createEl("strong",{text:u}),_.createEl("span",{text:h})}}};var Oe=K(require("fs")),et=K(require("path")),Wt=require("child_process");function kt(d){return d.display_action==="rebuild_result"?"rebuild":d.display_action==="retry_ocr"||d.display_action==="upgrade_legacy"?"redo":null}function wt(d){return d==="redo"}function Pr(d,p,e){let t={manifest:d,papers:{},cached_at:new Date().toISOString()};if(e!=null&&e.papers)for(let r of Object.keys(d))e.papers[r]&&(t.papers[r]=e.papers[r]);for(let r of p)t.papers[r.key]=r;return t}function qt(d){return et.join(d,"System","PaperForge","cache","ocr_maintenance.json")}function tt(d){try{let p=qt(d),e=Oe.readFileSync(p,"utf-8");return JSON.parse(e)}catch(p){return null}}function Sr(d,p){let e=qt(d),t=et.dirname(e);Oe.mkdirSync(t,{recursive:!0}),Oe.writeFileSync(e,JSON.stringify(p,null,2),"utf-8")}function Ut(d,p,e){return new Promise((t,r)=>{(0,Wt.execFile)(d,p,e,(n,a)=>{n?r(n):t(a)})})}async function Pt(d,p,e,t){let r=await Ut(p,[...e,"-m","paperforge","ocr","list","--manifest"],{cwd:d,timeout:3e4}),n=JSON.parse(r);if(t){let u=Object.keys(t.manifest),h=Object.keys(n);if(u.length===h.length&&u.every(f=>t.manifest[f]===n[f])&&Object.values(t.papers).every(g=>typeof g.needs_derived_rebuild=="boolean"))return{data:Object.values(t.papers),changed:!1}}let a=Object.keys(n),s=await Ut(p,[...e,"-m","paperforge","ocr","list","--json","--keys",...a],{cwd:d,timeout:3e4}),o=JSON.parse(s),l=Pr(n,o,t);return Sr(d,l),{data:Object.values(l.papers),changed:!0}}var St=K(require("fs")),H=K(require("path")),at=require("child_process"),Xt=K(require("os")),Zt=300*1e3,Be="3.11",Jt="3.10";function rt(){let d,p;return{promise:new Promise((t,r)=>{d=t,p=r}),resolve:d,reject:p}}function Gt(d){let p=d.match(/Python\s+(\d+\.\d+(?:\.\d+)?)/);if(p)return p[1];let e=d.match(/Python\s+(\d+\.\d+)/);return e?e[1]+".0":null}function Qt(d,p){var r,n;let e=d.split(".").map(Number),t=p.split(".").map(Number);for(let a=0;a=0}function Cr(){var d;return process.env.FLATPAK_ID!==void 0||((d=process.env.XDG_DATA_DIRS)!=null?d:"").includes("flatpak")||!1}function Rr(){return process.env.SNAP!==void 0||process.env.SNAP_NAME!==void 0||!1}function Yt(d,p){var t;return`${(t={win32:"windows",darwin:"macos",linux:"linux"}[d])!=null?t:d}-${p}`}function er(d,p,e){if(p!==void 0||e!==void 0){if(e)return[{verb:"stop",label:"Stop"}];switch(d.state){case"not_installed":return[{verb:"install",label:"Install Runtime"}];case"needs_repair":{let t=[{verb:"repair",label:"Repair Runtime"}];return d.pythonPath&&t.push({verb:"rollback",label:"Rollback"}),t}case"ready":{let t=[{verb:"status",label:"Check Status"},{verb:"update",label:"Update Runtime"}];return d.previousVersion&&t.push({verb:"rollback",label:"Rollback"}),t}case"unknown":return[{verb:"retry",label:"Retry"}];case"unavailable":return[{verb:"setup",label:"Manual Setup"}];default:return[{verb:"retry",label:"Retry"}]}}switch(d.state){case"not_installed":return[{id:"install",label:"Install Runtime",primary:!0,destructive:!1}];case"needs_repair":{let t=[{id:"repair",label:"Repair Runtime",primary:!0,destructive:!1}];return d.pythonPath&&t.push({id:"rollback",label:"Rollback",primary:!1,destructive:!1}),t}case"ready":return[{id:"status",label:"Check Status",primary:!1,destructive:!1},{id:"update",label:"Update Runtime",primary:!1,destructive:!1}];case"unknown":return[{id:"probe",label:"Refresh Status",primary:!0,destructive:!1}];case"unavailable":return[{id:"setup",label:"Manual Setup",primary:!0,destructive:!1}];default:return[{id:"probe",label:"Refresh Status",primary:!0,destructive:!1}]}}function tr(d){return d.state!=="ready"||!d.pythonPath?null:{command:d.pythonPath,args:[]}}var st=class{constructor(p){this._cache=null;this._cacheTime=0;var r,n,a,s,o,l,c,u,h,_,f;let e=(n=(r=p.osPlatform)!=null?r:p.platform)!=null?n:process.platform,t=(s=(a=p.osArch)!=null?a:p.arch)!=null?s:process.arch;if(this.osPlatform=e,this.osArch=t,this.triplet=`${e}-${t}`,p.runtimeDir)this.runtimeDir=p.runtimeDir,this.rootDir=H.dirname(p.runtimeDir),this.pluginVersion=(l=(o=p.pluginVersion)!=null?o:p.version)!=null?l:"0.0.0";else{let g=Xt.homedir();this.rootDir=H.join(g,".paperforge","runtime"),this.runtimeDir=H.join(this.rootDir,Yt(e,t)),this.pluginVersion=(u=(c=p.version)!=null?c:p.pluginVersion)!=null?u:"0.0.0"}this.pointerPath=H.join(this.rootDir,"active-runtime.json"),this._fs=(h=p.fs)!=null?h:St,this._execFile=(_=p.execFile)!=null?_:at.execFile,this._execFileSync=(f=p.execFileSync)!=null?f:at.execFileSync}current(){return this._cache?Date.now()-this._cacheTime>Zt?{...this._cache,state:"unknown",stale:!0}:{...this._cache,stale:!1}:{state:"unknown",pythonPath:null,version:null,source:"none",error:null,lastVerifiedAt:null,stale:!0,warnings:[],previousVersion:null,previousPythonPath:null}}async status(p){var s;if(this._cache){let o=Date.now()-this._cacheTime>Zt;if(!o&&this._cache.state==="ready")return{...this._cache,stale:!1};if(o&&(p!=null&&p.allowStale))return{...this._cache,stale:!0}}let e=null,t=null,r=null,n=null,a=[];try{let o=this._fs.readFileSync(this.pointerPath,"utf-8"),l=JSON.parse(o);e=typeof l.version=="string"?l.version:null;let c=typeof l.pythonPath=="string"?l.pythonPath:null;t=c?H.resolve(H.dirname(this.pointerPath),c):null,r=typeof l.previousVersion=="string"?l.previousVersion:null,n=typeof l.previousPythonPath=="string"?l.previousPythonPath:null,a=Array.isArray(l.warnings)?l.warnings:[]}catch(o){return this._setCache({state:"not_installed",pythonPath:null,version:null,source:"none",error:null,lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null})}if(!t)return this._setCache({state:"needs_repair",pythonPath:null,version:e,source:"none",error:{code:"POINTER_MISSING_PATH",message:"Active runtime pointer has no pythonPath",platformAction:"Reinstall runtime"},lastVerifiedAt:null,stale:!1,warnings:a,previousVersion:r,previousPythonPath:n});if(!this._fs.existsSync(t))return this._setCache({state:"needs_repair",pythonPath:t,version:e,source:"none",error:{code:"PYTHON_NOT_FOUND",message:"Python executable not found at pointer path",platformAction:"Reinstall runtime"},lastVerifiedAt:null,stale:!1,warnings:a,previousVersion:r,previousPythonPath:n});try{let o=await this._probe(t),l=[...a];try{let c=this._execFileSync(t,["--version"],{encoding:"utf-8",timeout:5e3}),u=Gt(c);u&&nt(u,Jt)&&!nt(u,Be)&&(l.some(h=>h.code==="PYTHON_310_DEPRECATED")||l.push({code:"PYTHON_310_DEPRECATED",message:`Python ${u} is running. Python 3.10 support enters legacy phase in Release N \u2014 upgrade to Python ${Be}+ recommended.`,platformAction:`Upgrade to Python ${Be}+`}))}catch(c){}return this._setCache({state:"ready",pythonPath:t,version:(s=o.version)!=null?s:e,source:"venv",error:null,lastVerifiedAt:new Date().toISOString(),stale:!1,warnings:l,previousVersion:r,previousPythonPath:n})}catch(o){let l=o instanceof Error?o.message:String(o);return this._setCache({state:"needs_repair",pythonPath:t,version:e,source:"venv",error:{code:"PROBE_FAILED",message:l,platformAction:"Repair runtime"},lastVerifiedAt:null,stale:!1,warnings:a,previousVersion:r,previousPythonPath:n})}}async ensure(p){var m,b;let e=(m=p==null?void 0:p.version)!=null?m:this.pluginVersion,t=(b=p==null?void 0:p.force)!=null?b:!1,r=p==null?void 0:p.signal;if(r!=null&&r.aborted)return this._abortedHealth();if(!t){let x=this.current();if(x.state==="ready"&&!x.stale){let k=await this.status();if(k.state==="ready")return k}}if(r!=null&&r.aborted)return this._abortedHealth();let n;try{n=this._resolveBootstrapPython()}catch(x){if(Cr()||Rr())return this._setCache({state:"unavailable",pythonPath:null,version:null,source:"none",error:{code:"FLATPAK_SNAP_UNSUPPORTED",message:"Flatpak and Snap are not supported. Install Python 3.11+ natively.",platformAction:"Install Python 3.11+ from python.org or package manager"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null});let k=Yt(this.osPlatform,this.osArch),y=this.osPlatform==="darwin",v=["macos-x64","macos-arm64"],S=["windows-x64","linux-x64"];return y&&v.includes(k)?this._setCache({state:"unavailable",pythonPath:null,version:null,source:"none",error:{code:"NO_PYTHON",message:"No Python 3.11+ found. macOS auto-download disabled until signed/notarized artifacts exist.",platformAction:"Install Python 3.11+ from python.org or Homebrew"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null}):S.includes(k)?this._setCache({state:"unavailable",pythonPath:null,version:null,source:"none",error:{code:"NO_PYTHON",message:"No Python 3.11+ found and automatic download failed.",platformAction:"Install Python 3.11+ manually"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null}):this._setCache({state:"unavailable",pythonPath:null,version:null,source:"none",error:{code:"FALLBACK_UNAVAILABLE",message:"No Python found and this platform has no validated fallback.",platformAction:"Install Python 3.11+ manually from python.org"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null})}if(r!=null&&r.aborted)return this._abortedHealth();let a=this._currentSlotExists(e);if(!nt(n.version,Jt))return this._setCache({state:"unavailable",pythonPath:null,version:n.version,source:"none",error:{code:"PYTHON_TOO_OLD",message:`Python ${n.version} is too old. Python 3.10+ required.`,platformAction:"Install Python 3.10+"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null});if(!a&&!nt(n.version,Be))return this._setCache({state:"needs_repair",pythonPath:null,version:n.version,source:"none",error:{code:"PYTHON_VERSION_WARNING",message:`Python ${n.version} found. New installations require Python ${Be}+.`,platformAction:`Upgrade to Python ${Be}+`},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null});if(a&&!t){let x=!1;try{let k=this._fs.readFileSync(this.pointerPath,"utf-8"),y=JSON.parse(k),v=typeof y.version=="string"?y.version:null;x=v!==null&&v!==e}catch(k){}if(x){let k=H.join(this.runtimeDir,`v${e}`),y=H.join(k,"venv"),v=this.osPlatform==="win32"?H.join(y,"Scripts","python.exe"):H.join(y,"bin","python");try{await this._probe(v,r)}catch(T){if(T instanceof Error&&T.name==="AbortError")return this._abortedHealth();let w=T instanceof Error?T.message:String(T);return this._setCache({state:"needs_repair",pythonPath:v,version:e,source:"venv",error:{code:"RETAINED_SLOT_PROBE_FAILED",message:`Retained slot v${e} failed verification: ${w}`,platformAction:"Repair runtime"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null})}let S=null,R=null;try{let T=this._fs.readFileSync(this.pointerPath,"utf-8"),w=JSON.parse(T);S=typeof w.version=="string"?w.version:null,R=typeof w.pythonPath=="string"?w.pythonPath:null}catch(T){}let P=H.dirname(this.pointerPath);this._fs.existsSync(P)||this._fs.mkdirSync(P,{recursive:!0});let F=H.relative(H.dirname(this.pointerPath),v),M=JSON.stringify({schema_version:1,version:e,pythonPath:F,activatedAt:new Date().toISOString(),previousVersion:S,previousPythonPath:R},null,2),I=this.pointerPath+".tmp";this._fs.writeFileSync(I,M,"utf-8"),this._fs.renameSync(I,this.pointerPath);let D={state:"ready",pythonPath:v,version:e,source:"venv",error:null,lastVerifiedAt:new Date().toISOString(),stale:!1,warnings:[],previousVersion:S,previousPythonPath:R};return this._cache=D,this._cacheTime=Date.now(),this._cleanupOldSlots(e),D}}if(r!=null&&r.aborted)return this._abortedHealth();let s=t?H.join(this.runtimeDir,`v${e}_build2`):H.join(this.runtimeDir,`v${e}`),o=H.join(s,"venv"),l=this.osPlatform==="win32"?H.join(o,"Scripts","python.exe"):H.join(o,"bin","python");try{this._fs.mkdirSync(s,{recursive:!0});let{promise:x,reject:k,resolve:y}=rt();this._execFile(n.path,["-m","venv",o],{timeout:6e4,signal:r},v=>{v?k(v):y()}),await x}catch(x){if(x instanceof Error&&x.name==="AbortError"){try{this._fs.rmSync(s,{recursive:!0,force:!0})}catch(k){}return this._abortedHealth()}return this._slotFailed(e,"VENV_CREATION_FAILED",x,s)}if(r!=null&&r.aborted)return this._abortedHealth();try{let{promise:x,reject:k,resolve:y}=rt();this._execFile(l,["-m","pip","install",`paperforge==${e}`],{timeout:12e4,signal:r},v=>{v?k(v):y()}),await x}catch(x){if(x instanceof Error&&x.name==="AbortError"){try{this._fs.rmSync(s,{recursive:!0,force:!0})}catch(k){}return this._abortedHealth()}return this._slotFailed(e,"PIP_INSTALL_FAILED",x,s)}if(r!=null&&r.aborted)return this._abortedHealth();try{let{promise:x,reject:k,resolve:y}=rt();this._execFile(l,["-I","-c","import paperforge; print(paperforge.__version__)"],{timeout:3e4,signal:r},v=>{v?k(v):y()}),await x}catch(x){if(x instanceof Error&&x.name==="AbortError"){try{this._fs.rmSync(s,{recursive:!0,force:!0})}catch(k){}return this._abortedHealth()}return this._slotFailed(e,"VERIFY_FAILED",x,s)}let c=null,u=null;try{let x=this._fs.readFileSync(this.pointerPath,"utf-8"),k=JSON.parse(x);c=typeof k.version=="string"?k.version:null,u=typeof k.pythonPath=="string"?k.pythonPath:null}catch(x){}let h=H.dirname(this.pointerPath);this._fs.existsSync(h)||this._fs.mkdirSync(h,{recursive:!0});let _=H.relative(H.dirname(this.pointerPath),l),f=JSON.stringify({schema_version:1,version:e,pythonPath:_,activatedAt:new Date().toISOString(),previousVersion:c,previousPythonPath:u},null,2),g=this.pointerPath+".tmp";this._fs.writeFileSync(g,f,"utf-8"),this._fs.renameSync(g,this.pointerPath);let E={state:"ready",pythonPath:l,version:e,source:"venv",error:null,lastVerifiedAt:new Date().toISOString(),stale:!1,warnings:[],previousVersion:c,previousPythonPath:u};return this._cache=E,this._cacheTime=Date.now(),this._cleanupOldSlots(e),E}_setCache(p){return this._cache=p,this._cacheTime=Date.now(),p}_abortedHealth(){return{state:"needs_repair",pythonPath:null,version:null,source:"none",error:{code:"ABORTED",message:"Operation was cancelled",platformAction:"Retry operation"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null}}_slotFailed(p,e,t,r){try{this._fs.rmSync(r,{recursive:!0,force:!0})}catch(a){}let n=t instanceof Error?t.message:String(t);return this._setCache({state:"needs_repair",pythonPath:null,version:p,source:"none",error:{code:e,message:n,platformAction:"Retry installation"},lastVerifiedAt:null,stale:!1,warnings:[],previousVersion:null,previousPythonPath:null})}_currentSlotExists(p){let e=H.join(this.runtimeDir,`v${p}`);return this._fs.existsSync(e)}_resolveBootstrapPython(){let p=[];this.osPlatform==="win32"?p.push({path:"py",args:["-3.11"]},{path:"py",args:["-3.10"]},{path:"py",args:["-3"]},{path:"python",args:[]}):this.osPlatform==="darwin"?p.push({path:"/usr/bin/python3",args:[]},{path:"python3",args:[]},{path:"python",args:[]}):p.push({path:"/usr/bin/python3",args:[]},{path:"python3",args:[]},{path:"python",args:[]});for(let e of p)try{let t=this._execFileSync(e.path,[...e.args,"--version"],{encoding:"utf-8",timeout:5e3}),r=Gt(t);if(r)return{path:e.path,version:r}}catch(t){}throw new Error("No Python 3.10+ found on system")}_probe(p,e){let{promise:t,resolve:r,reject:n}=rt();return this._execFile(p,["-I","-c","import paperforge; print(paperforge.__version__)"],{timeout:3e4,signal:e},(a,s)=>{if(a)n(a);else{let o=(s!=null?s:"").trim()||null;r({version:o})}}),t}_cleanupOldSlots(p,e=2){try{let r=this._fs.readdirSync(this.runtimeDir,{withFileTypes:!0}).filter(n=>n.isDirectory()&&n.name.startsWith("v")).map(n=>{let a=n.name.replace(/^v/,"").replace(/_build\d+$/,"");return{name:n.name,version:a}}).filter(n=>n.version!==p).sort((n,a)=>Qt(a.version,n.version));for(let n=e;n{t.createEl("button",{cls:"paperforge-settings-tab"+(a.id===this.activeTab?" paperforge-settings-tab--active":""),text:a.label}).addEventListener("click",()=>{this.activeTab=a.id,this.display()})}),r.forEach(a=>{n[a.id]=e.createDiv({cls:"paperforge-tab-content"+(a.id===this.activeTab?" paperforge-tab-content--active":"")})}),this.activeTab==="overview"?this._renderOverviewTab(n.overview):this.activeTab==="module-detail"?this._renderModuleDetailTab(n["module-detail"]):this.activeTab==="maintenance"?this._renderMaintenanceTab(n.maintenance):this.activeTab==="help"&&this._renderHelpTab(n.help),this._focusTargetId&&this.activeTab!=="help"){let a=e.querySelector(this._focusTargetId);if(a){try{a.focus()}catch(s){}this._focusTargetId=null}}}_renderOverviewTab(e){var r;let t=this._getVaultBasePath();this.plugin.settings.vault_path||(this.plugin.settings.vault_path=t,this._debouncedSave()),e.createEl("h2",{text:o("header_title")||"PaperForge"}),e.createEl("p",{text:o("desc"),cls:"paperforge-settings-desc"}),this._renderControlCenter(e),this._renderAdvancedSection(e);for(let n of Ce){let a=(r=this._capabilityState)==null?void 0:r[n];a&&a.capability_state==="unknown"&&a.updated_at===new Date(0).toISOString()&&!this._attemptedProbes.has(n)&&(this._attemptedProbes.add(n),(n==="installation"||n==="help")&&this._probeModule(n))}}_renderAdvancedSection(e){this._advCollapsed===void 0&&(this._advCollapsed=!0);let t=e.createEl("div",{cls:"paperforge-collapsible-header"}),r=t.createEl("span",{text:"\u25B6",cls:"paperforge-collapsible-arrow"});r.style.transform=this._advCollapsed?"rotate(0deg)":"rotate(90deg)";let n=t.createEl("span",{cls:"paperforge-collapsible-title",text:"Advanced"}),a=t.createEl("span",{cls:"paperforge-collapsible-sub",text:"Memory + Vector DB + Embedding"}),s=e.createEl("div",{cls:"paperforge-collapsible-content"});s.style.display=this._advCollapsed?"none":"",t.addEventListener("click",()=>{this._advCollapsed=!this._advCollapsed,s.style.display=this._advCollapsed?"none":"",r.style.transform=this._advCollapsed?"rotate(0deg)":"rotate(90deg)"}),s.createEl("h4",{text:"Memory Layer"}),s.createEl("div",{cls:"paperforge-desc-box"}).setText(o("feat_memory_desc"));let l=s.createEl("div",{cls:"paperforge-memory-status"}),c=this.app.vault.adapter.basePath;this.plugin._lastSyncTime&&!this._lastSyncTime&&(this._lastSyncTime=this.plugin._lastSyncTime),this._memoryStatusText===null&&(this._memoryStatusText=Ye(c)),this._renderMemoryStatusText(l,this._memoryStatusText,this._lastSyncTime),this._renderVectorSection(s)}_getVaultBasePath(){let e=this.app.vault.adapter;if(e&&typeof e=="object"&&"basePath"in e){let t=e.basePath;return typeof t=="string"?t:""}return""}_ensureManagedRuntime(){return this._managedRuntime?this._managedRuntime:(this._managedRuntime=new nt({version:this.plugin.manifest.version}),this._managedRuntime)}_resolveRuntimeCommand(e){let t=tr(this._ensureManagedRuntime().current());if(t)return{path:t.command,args:[...t.args]};console.warn("[PaperForge] Release N: Managed runtime not ready (cold/stale), falling back to legacy resolver");let r=ye(e,this.plugin.settings);return r.path?{path:r.path,args:r.extraArgs}:null}_renderInstallationDetail(e){var S,C;e.createEl("button",{cls:"pf-back-btn",text:o("btn_back_to_overview")}).addEventListener("click",()=>{this.activeTab="overview",this._selectedDetailModule="",this._focusTargetId="button.pf-open-module-btn[data-module=installation]",this.display()});let r=e.createEl("h2",{cls:"pf-installation-detail-heading",text:o("installation_detail_heading")||"Installation Details",attr:{id:"pf-installation-detail-heading",tabindex:"-1"}}),n=[{id:"installation",labelKey:"md_select_installation",disabled:!1}],a=e.createEl("div",{cls:"pf-module-detail-selector"});for(let P of n){let R=a.createEl("button",{cls:"pf-module-detail-btn"+(P.id==="installation"?" pf-module-detail-btn--active":"")+(P.disabled?" pf-module-detail-btn--disabled":""),text:o(P.labelKey)});P.disabled&&(R.disabled=!0)}let s=(S=this._capabilityState)!=null?S:{},i="installation",l=(C=s[i])!=null?C:Re(i),c=this._sevClass(l.severity),_=qe(l),h=e.createEl("div",{cls:"pf-cc-card",attr:{style:"margin-bottom: 12px;"}}),u=h.createEl("div",{cls:"pf-cc-card-header"});u.createEl("span",{cls:"pf-cc-card-name",text:o("cc_module_installation")}),u.createEl("span",{cls:`pf-cc-card-badge pf-cc-card-badge--${c}`,text:o(this._ccBadgeKey(l,i))});let m=this._localizeReason(l.reason.code,"installation");if(h.createEl("div",{cls:"pf-cc-card-reason",text:m!=null?m:l.reason.text}),l.action.primary&&!_){let P=Ze(l),I=P.kind==="setup"?"pf-cc-card-action pf-cc-card-action--primary":"pf-cc-card-action";h.createEl("button",{cls:I,text:P.label}).addEventListener("click",()=>{P.kind==="setup"?new De(this.app,this.plugin,()=>{this._probeModule("installation"),this._probeModule("help")}).open():this._probeModule(i)})}e.createEl("h3",{text:o("managed_runtime_status")});let f=e.createEl("div",{cls:"pf-runtime-status-card"}),x=(P,R,I)=>{let M=f.createEl("div",{cls:"pf-runtime-actions"});for(let O of P){let B=M.createEl("button",{cls:"pf-runtime-action-btn",text:O.label});I&&O.verb!=="stop"&&(B.disabled=!0),B.addEventListener("click",async()=>{var Q;if(O.verb==="stop"){let $=this._runtimeAbortController;if(!$||$.signal.aborted)return;$.abort(),new F.Notice(o("managed_runtime_action_cancelled")),this.display(),this._probeModule("installation"),this._probeModule("help");return}let w=this._ensureManagedRuntime(),j=new AbortController;this._runtimeAbortController=j,this._runtimeBusy=!0,new F.Notice(o("managed_runtime_running"));try{O.verb==="install"||O.verb==="repair"||O.verb==="update"?await w.ensure({signal:j.signal,force:O.verb==="update"||O.verb==="repair"}):O.verb==="rollback"?await w.ensure({signal:j.signal,version:(Q=R.previousVersion)!=null?Q:void 0}):await w.status(),j.signal.aborted||new F.Notice(o("managed_runtime_action_complete"))}catch($){if(($==null?void 0:$.name)!=="AbortError"){let L=$ instanceof Error?$.message:String($);new F.Notice(o("managed_runtime_action_failed").replace("{error}",L),8e3)}}finally{this._runtimeAbortController=null,this._runtimeBusy=!1,this._probeModule("installation"),this._probeModule("help"),this.display()}})}},g=()=>{var w;f.empty();let R=this._ensureManagedRuntime().current(),I=f.createEl("div",{cls:"pf-runtime-status-header"});I.createEl("div",{cls:"pf-runtime-status-label",text:o("managed_runtime_status")});let M,O;switch(R.state){case"ready":M="ok",O=o("managed_runtime_ok_state");break;case"not_installed":M="warn",O=o("managed_runtime_not_installed");break;case"needs_repair":M="warn",O=o("managed_runtime_needs_repair");break;case"unavailable":M="error",O=o("managed_runtime_unavailable");break;default:M="unknown",O=o("managed_runtime_unknown_state")}if(I.createEl("span",{cls:`pf-runtime-status-state pf-runtime-status-state--${M}`,text:O}),R.version&&f.createEl("div",{cls:"pf-meta",text:`Python ${R.version}`}),R.pythonPath&&f.createEl("div",{cls:"pf-meta",text:R.pythonPath,attr:{style:"word-break: break-all;"}}),R.lastVerifiedAt&&f.createEl("div",{cls:"pf-meta",text:o("managed_runtime_last_verified").replace("{time}",new Date(R.lastVerifiedAt).toLocaleString())}),R.error&&f.createEl("div",{cls:"pf-runtime-error",text:`${R.error.code}: ${R.error.message}`}),R.warnings&&R.warnings.length>0)for(let j of R.warnings){let Q=f.createEl("div",{cls:"pf-runtime-warning",text:`\u26A0 ${j.message}`});j.platformAction&&Q.createEl("div",{cls:"pf-runtime-warning-action",text:j.platformAction})}(w=R.error)!=null&&w.platformAction&&f.createEl("div",{cls:"pf-runtime-error-action",text:R.error.platformAction});let B=er(R,this.plugin.manifest.version,this._runtimeBusy);x(B,R,this._runtimeBusy)};g();let b=this._ensureManagedRuntime().status();b&&b.then(()=>{e.isConnected&&g()}).catch(()=>{}),e.createEl("h3",{text:o("section_config")||"Current Configuration"});let E=this._getVaultBasePath(),k=z(E,this.plugin.settings,void 0,void 0),y=this._getPythonDesc(k.path,k.source);new F.Setting(e).setName(o("field_python_interp")||"Python Interpreter").setDesc(y).addExtraButton(P=>{P.setIcon("reset").setTooltip("Re-detect").onClick(()=>{this._pythonInterpDescEl=null,this._managedRuntime=null,this.display()})}).addButton(P=>{P.setButtonText(o("runtime_health_sync")||"Sync Runtime").onClick(()=>{this._syncRuntime(P)})});let v=e.createEl("div",{cls:"setting-item-description"});this._customPathDescEl=v,new F.Setting(e).setName(o("field_python_custom")||"Custom Python Path").setDesc(o("optional_later")).addText(P=>{P.setPlaceholder("e.g. C:\\Python311\\python.exe").setValue(this.plugin.settings.python_path||"").onChange(R=>{this.plugin.settings.python_path=R.trim(),this._debouncedSave(),this._managedRuntime=null})}).addButton(P=>{P.setButtonText(o("feat_verify")||"Validate").onClick(()=>{this._validatePythonOverride()})}),new F.Setting(e).setName(o("field_zotero_data")||"Zotero Data Dir").setDesc(o("field_zotero_placeholder")).addText(P=>{P.setPlaceholder("C:\\Users\\...\\Zotero").setValue(this.plugin.settings.zotero_data_dir||"").onChange(R=>{this.plugin.settings.zotero_data_dir=R.trim(),this._debouncedSave()})}),e.createEl("h3",{text:o("agent_integration_section")||"Agent Integration"}),this._renderSkillsList(e);try{r.focus()}catch(P){}}_renderSkillsList(e){let t={opencode:"OpenCode",claude:"Claude Code",codex:"Codex",cursor:"Cursor",windsurf:"Windsurf",github_copilot:"GitHub Copilot",gemini:"Gemini CLI"},r={opencode:".opencode/skills",claude:".claude/skills",codex:".codex/skills",cursor:".cursor/skills",windsurf:".windsurf/skills",github_copilot:".github/skills",gemini:".gemini/skills"},n=this._getVaultBasePath(),a=this.plugin.settings.agent_platform||"opencode";new F.Setting(e).setName(o("label_agent")||"Agent Platform").setDesc(o("feat_agent_platform_desc")).addDropdown(u=>{Object.entries(t).forEach(([m,f])=>u.addOption(m,f)),u.setValue(a).onChange(m=>{this.plugin.settings.agent_platform=m,this.plugin.saveSettings(),this.display()})}).addExtraButton(u=>{u.setIcon("folder").setTooltip("Open skills folder").onClick(()=>{let m=r[a]||".opencode/skills",f=ee.join(n,m);W.existsSync(f)?(0,G.exec)(`start "" "${f}"`):new F.Notice(`Skills folder not found: ${m}`)})}),e.createEl("h3",{text:"Skills"});let s=e.createEl("div",{cls:"paperforge-desc-box"});s.setText(o("feat_skills_desc")),s.createEl("br"),s.createEl("span",{text:o("feat_skills_system")});let i=ee.join(n,r[a]),l=[],c=[];W.existsSync(i)&&W.readdirSync(i,{withFileTypes:!0}).forEach(u=>{if(!u.isDirectory())return;let m=ee.join(i,u.name,"SKILL.md");if(!W.existsSync(m))return;let f=W.readFileSync(m,"utf-8"),x=f.match(/^name:\s*(.+)$/m),g=f.split(` -`),b=g.findIndex(C=>/^description:/.test(C)),E="";if(b>=0){let C=g[b].match(/^description:\s*(.+)$/);if(C&&C[1]&&C[1]!==">"&&C[1]!=="|-"&&C[1]!=="|")E=C[1].trim();else{for(let P=b+1;P{if(m.length===0)return;let x=_.createEl("div",{cls:"paperforge-skills-group"}),g=x.createEl("div",{cls:"paperforge-skills-collapse-header"}),b=x.createEl("div",{cls:"paperforge-skills-collapse-content"}),E=g.createEl("span",{text:"\u25BC",cls:"paperforge-skills-arrow"});g.createEl("h4",{text:`${u} (${m.length})`,cls:"paperforge-skills-subheader"}),m.forEach(v=>{let S=v.name+(v.version?" v"+v.version:""),C=f?" [system]":" [user]",P=v.desc||"",R=new F.Setting(b).setName(S+C).setDesc(P);R.settingEl.style.opacity=v.disabled?"0.4":"1",R.addToggle(I=>{I.setValue(!v.disabled).onChange(M=>{let O=!M,w=v.content.match(/^disable-model-invocation:\s*(.+)$/m)?v.content.replace(/^disable-model-invocation:\s*.+$/m,`disable-model-invocation: ${O}`):v.content.replace(/^(---\r?\n)/,`$1disable-model-invocation: ${O} -`);W.writeFileSync(v.path,w,"utf-8"),v.disabled=O,v.content=w,R.settingEl.style.opacity=v.disabled?"0.4":"1"})})});let k=f?"system":"user";(this._skillsCollapsed[k]||!1)&&(b.style.display="none",E.style.transform="rotate(-90deg)"),g.addEventListener("click",()=>{b.style.display!=="none"?(b.style.display="none",E.style.transform="rotate(-90deg)"):(b.style.display="",E.style.transform="rotate(0deg)"),this._skillsCollapsed[k]=b.style.display==="none"})};h("System Skills",l,!0),h("User Skills",c,!1),l.length===0&&c.length===0&&_.createEl("p",{text:`No skills found in ${r[a]}. Run setup to deploy skills.`,cls:"setting-item-description"})}_renderModuleDetailTab(e){this._selectedDetailModule||(this._selectedDetailModule="installation"),this._selectedDetailModule==="installation"?this._renderInstallationDetail(e):(this._selectedDetailModule="installation",this._renderInstallationDetail(e))}_renderHelpTab(e){var E,k;let t=(E=this._capabilityState)!=null?E:{},r="help",n=(k=t[r])!=null?k:Re(r),a=this._sevClass(n.severity),s=ke._REAL_PROBE.has(r);e.createEl("h2",{text:o("cc_module_help")||"Help & Docs"});let i=e.createEl("div",{cls:"pf-cc-card",attr:{style:"margin-bottom: 12px;"}}),l=i.createEl("div",{cls:"pf-cc-card-header"});l.createEl("span",{cls:"pf-cc-card-name",text:o("cc_module_help")}),l.createEl("span",{cls:`pf-cc-card-badge pf-cc-card-badge--${a}`,text:o(this._ccBadgeKey(n,r))});let c;if(!s)c=o("cc_reason_placeholder").replace("{module}",o("cc_module_"+r));else{let y=this._localizeReason(n.reason.code,r);c=y!=null?y:n.reason.text}if(i.createEl("div",{cls:"pf-cc-card-reason",text:c}),n.action.primary&&!qe(n)){let y=Ze(n),S=y.kind==="setup"?"pf-cc-card-action pf-cc-card-action--primary":"pf-cc-card-action";i.createEl("button",{cls:S,text:y.label,attr:{"aria-label":y.label}}).addEventListener("click",()=>{y.kind==="setup"?new De(this.app,this.plugin,()=>{this._probeModule("installation"),this._probeModule("help")}).open():this._probeModule(r)})}let _=i.createEl("details",{cls:"pf-cc-card-diagnostic"});_.createEl("summary",{text:o("cc_diagnostic_toggle")});let h=_.createEl("div",{cls:"pf-cc-card-diagnostic-body"}),u=o("cc_state_"+n.capability_state)||n.capability_state,m=o("cc_severity_"+n.severity)||n.severity,f=o("cc_activity_"+n.activity_state)||n.activity_state,x;try{x=new Date(n.updated_at).toLocaleString()}catch(y){x=n.updated_at}h.createEl("div",{text:`${o("cc_diag_module")}: ${n.module}`}),h.createEl("div",{text:`${o("cc_diag_state")}: ${u}`}),h.createEl("div",{text:`${o("cc_diag_severity")}: ${m}`}),h.createEl("div",{text:`${o("cc_diag_activity")}: ${f}`});let g=h.createEl("div");g.appendText(o("cc_diag_reason")+": "+c+" ");let b=g.createEl("code",{text:n.reason.code});h.createEl("div",{text:`${o("cc_diag_ttl")}: ${String(n.ttl_seconds)}s`}),h.createEl("div",{text:`${o("cc_diag_updated")}: ${x}`}),this._renderReleaseNotesTab(e)}_execMemoryStatus(e,t,r){(0,G.exec)(`"${e}" -m paperforge --vault "${t}" memory status --json`,{encoding:"utf-8",timeout:15e3},(n,a)=>{if(n){r("Status unavailable");return}try{let s=JSON.parse(a);if(s.ok){let i=s.data,l=i.fresh?"fresh":"stale";r(`Papers: ${i.paper_count_db} | ${l}${i.needs_rebuild?" - needs rebuild":""}`)}else r("DB not found. Run paperforge memory build.")}catch(s){r("Could not parse status.")}})}_execEmbedStatus(e,t,r){(0,G.exec)(`"${e}" -m paperforge --vault "${t}" embed status --json`,{encoding:"utf-8",timeout:15e3},(n,a)=>{if(n){r("Status unavailable");return}try{let s=JSON.parse(a);s.ok?r(`Chunks: ${s.data.chunk_count} | ${s.data.model} | ${s.data.mode}`):r("Could not parse status.")}catch(s){r("Could not parse status.")}})}_callPython(e,t){let r=this.app.vault.adapter.basePath,n=this._resolveRuntimeCommand(r);if(!n)return t&&t.onClose&&t.onClose(1,"","No python runtime available"),null;let a=[...n.args,"-m","paperforge","--vault",r,...e];if(t&&t.stream){let s=(0,G.spawn)(n.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}return(0,G.execFile)(n.path,a,{cwd:r,timeout:t&&t.timeout||6e4},(s,i,l)=>{t&&t.onClose&&t.onClose(s?1:0,i,l)}),null}_renderMemoryStatusText(e,t,r){e.innerHTML="",e.createEl("span",{text:t,cls:"paperforge-memory-text"}),r==="syncing"?e.createEl("span",{text:"Syncing...",cls:"paperforge-sync-status"}):r&&e.createEl("span",{text:r,cls:"paperforge-sync-status"});let n=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=ye(s,this.plugin.settings);if(!i.path){new F.Notice(o("feat_no_python"));return}console.log("[PaperForge] Rebuilding memory:",i.path),n.setText(o("feat_memory_rebuilding")),n.setAttr("disabled",""),this._callPython(["memory","build"],{timeout:6e4,onClose:(l,c,_)=>{console.log("[PaperForge] memory build exit:",l?"FAIL:"+l:"OK",(c||"").slice(0,200),(_||"").slice(0,200)),n.setText(o("feat_memory_rebuild_btn")),n.removeAttribute("disabled"),l===0?new F.Notice(o("feat_memory_rebuild_done")):new F.Notice(o("feat_memory_rebuild_failed")+(_?" "+_.slice(0,80):"")),this._memoryStatusText=Ye(s),this._refreshSnapshots(s)}})};let a=e.createEl("button",{cls:"paperforge-refresh-btn",text:"\u21BB"});a.title="Sync now",a.onclick=()=>{this._memoryStatusText=null,this._runManualSync()}}_getBuildCommand(e){let t=this.app.vault.adapter.basePath,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(!ye(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=>{this.plugin._autoSyncRunning=!1,this._memoryStatusText=null,n===0&&(this._lastSyncTime=new Date().toLocaleTimeString(),this.plugin._lastSyncTime=this._lastSyncTime),this.display(),this._refreshSnapshots(e),Xe(this.app,this.plugin,e)}})}_refreshSnapshots(e){let t=ye(e,this.plugin.settings),r=[...t.extraArgs,"-m","paperforge","--vault",e,"runtime-health","--json"];this._refreshPending=!0,(0,G.execFile)(t.path,r,{cwd:e,timeout:3e4,windowsHide:!0},(n,a,s)=>{this._refreshPending=!1,this._memoryStatusText=Ye(e),this._embedStatusText=Ae(e),this.display()})}_renderVectorSection(e){var l;if(e.createEl("h4",{text:"Vector Database"}),this.plugin.settings.features||(this.plugin.settings.features={memory_layer:!0,vector_db:!1}),e.createEl("div",{cls:"paperforge-desc-box"}).setText(o("feat_vector_desc")),new F.Setting(e).setName(o("feat_vector_enable")).setDesc(o("feat_vector_enable_desc")).addToggle(c=>{c.setValue(!!this.plugin.settings.features.vector_db).onChange(_=>{this.plugin.settings.features.vector_db=_,this.plugin.saveSettings(),this._vectorDepsOk=null,this._embedStatusText=null,this.display()})}),!this.plugin.settings.features.vector_db)return;let r=this.app.vault.adapter.basePath,n=e.createEl("div",{cls:"paperforge-vec-header"}),a=n.createEl("span",{text:"\u25BC",cls:"paperforge-skills-arrow"});n.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)"};if(i(St(this._featurePanelsCollapsed,"vectorConfig",!1)),n.addEventListener("click",()=>{let c=rr(this._featurePanelsCollapsed,"vectorConfig",!1);i(c)}),this._vectorDepsOk===!0){this._renderVectorReady(s,r);return}if(this._vectorDepsOk===!1){this._renderVectorNoDeps(s);return}if(this._vectorDepsOk===null){let c=Ge(r);this._vectorDepsOk=c&&(l=c.deps_installed)!=null?l:!1,this._vectorDepsOk&&(this._embedStatusText=Ae(r)),this.display()}}_renderApiConfig(e){new F.Setting(e).setName(o("feat_openai_key")).setDesc(o("feat_openai_key_desc")).addText(t=>{t.setPlaceholder("sk-...").setValue(this.plugin.settings.vector_db_api_key||"").onChange(r=>{this.plugin.settings.vector_db_api_key=r,this.plugin.saveSettings()})}),new F.Setting(e).setName(o("feat_api_base_url")).setDesc(o("feat_api_base_url_desc")).addText(t=>{t.setPlaceholder("https://api.openai.com/v1").setValue(this.plugin.settings.vector_db_api_base||"").onChange(r=>{this.plugin.settings.vector_db_api_base=r,this.plugin.saveSettings()})}),new F.Setting(e).setName(o("feat_api_model")).setDesc(o("feat_api_model_desc")).addText(t=>{t.setPlaceholder("text-embedding-3-small").setValue(this.plugin.settings.vector_db_api_model||"text-embedding-3-small").onChange(r=>{this.plugin.settings.vector_db_api_model=r,this.plugin.saveSettings()})})}_renderVectorNoDeps(e){e.createEl("div",{cls:"paperforge-desc-box"}).setText(o("feat_deps_missing")),new F.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=ye(n,this.plugin.settings);if(!a.path){new F.Notice(o("feat_no_python"));return}r.setButtonText(o("feat_installing")),r.setDisabled(!0);let s="chromadb openai",i=new F.Notice(o("feat_installing_pkgs").replace("{pkgs}",s),0);try{let l=Object.assign({},process.env,{PYTHONIOENCODING:"utf-8",PYTHONUTF8:"1"}),c=s.split(" ");await new Promise((_,h)=>{(0,G.execFile)(a.path,[...a.extraArgs,"-m","pip","install",...c],{cwd:n,timeout:3e5,env:l,windowsHide:!0},u=>{u?h(u):_()})}),i.hide(),new F.Notice(o("feat_install_done")),this._vectorDepsOk=!0,this._embedStatusText=Ae(n),this.display()}catch(l){i.hide(),new F.Notice(o("feat_install_failed")+(l.stderr||l.message||l)),r.setButtonText(o("feat_retry_btn")),r.setDisabled(!1)}})})}_renderVectorReady(e,t){e.createEl("div",{cls:"paperforge-desc-box"}).setText(Ae(t)),this._renderApiConfig(e);let n=e.createEl("div",{cls:"paperforge-embed-section"});n.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",{cls:"paperforge-embed-status-text",attr:{"aria-live":"polite"}});(()=>{s.empty(),i.empty();let c=Ge(t),_=c==null?void 0:c.build_state,h=_&&typeof _=="object"&&!Array.isArray(_)?_:{};this.plugin._embedProgress=this.plugin._embedProgress||{current:0,total:0,key:""},!this.plugin._embedProcess&&h.status==="running"&&(this.plugin._embedProgress={current:typeof h.current=="number"?h.current:0,total:typeof h.total=="number"?h.total:1,key:typeof h.paper_id=="string"?h.paper_id:""});let{current:u,total:m,key:f}=this.plugin._embedProgress,x=typeof(c==null?void 0:c.body_chunk_count)=="number"?c.body_chunk_count:0,g=typeof(c==null?void 0:c.object_chunk_count)=="number"?c.object_chunk_count:0,E=(typeof(c==null?void 0:c.chunk_count)=="number"?c.chunk_count:0)+x+g,k=E>0,y=c!==null&&typeof c.corrupted=="boolean"&&c.corrupted,v=!!this.plugin._embedProcess,S=!this.plugin._embedProcess&&h.status==="running",C=(c==null?void 0:c.deps_installed)!==void 0?!!c.deps_installed:!0,P=typeof h.status=="string"?h.status:"",R=typeof h.message=="string"?h.message:"",I=w=>{var $;if(w==="--resume"&&k&&!y){let L=o("retrieval_rebuild_warning").replace("{n}",String(E));if(!confirm(L))return}if(w==="--force"&&k&&!y){let L="Force rebuild will replace "+E+" existing chunk(s). Continue?";if(!confirm(L))return}if(!ye(t,this.plugin.settings).path){new F.Notice(o("retrieval_no_python"));return}let Q=Object.assign({},process.env,{PYTHONIOENCODING:"utf-8",PYTHONUTF8:"1",VECTOR_DB_API_KEY:this.plugin.settings.vector_db_api_key||"",VECTOR_DB_API_BASE:this.plugin.settings.vector_db_api_base||"",VECTOR_DB_API_MODEL:this.plugin.settings.vector_db_api_model||""});this.plugin._embedStderr="",this.plugin._embedProgress={current:0,total:0,key:""},this.plugin._embedProcess=this._callPython(["embed","build",w],{stream:!0,env:Q,onData:L=>{var ae;let oe=typeof L=="string"?L:Buffer.isBuffer(L)?L.toString("utf-8"):String(L),{events:se,buffer:de}=Ct(oe,(ae=this.plugin._embedBuffer)!=null?ae:"");this.plugin._embedBuffer=de;for(let Z of se)Z.event==="START"?this.plugin._embedProgress.total=Z.total||0:Z.event==="PROGRESS"?(this.plugin._embedProgress.current=Z.current||0,this.plugin._embedProgress.key=Z.key||""):Z.event==="DONE"&&(this.plugin._embedProcess=null,this.plugin._embedProgress.current=this.plugin._embedProgress.total);this.display()},onStderr:L=>{this.plugin._embedStderr||(this.plugin._embedStderr=""),this.plugin._embedStderr+=String(L)},onError:L=>{this.plugin._embedProcess=null,new F.Notice(o("feat_build_failed")+": "+(L.message||L)),this.display()},onClose:L=>{var oe;if(clearInterval((oe=this.plugin._embedPollInterval)!=null?oe:void 0),this.plugin._embedPollInterval=null,this.plugin._embedProcess=null,L===0)this.plugin._embedProgress.current=this.plugin._embedProgress.total,this.plugin.saveSettings(),this._embedStatusText=Ae(t),new F.Notice(o("feat_build_complete"));else{this._embedStatusText=null;let se=(this.plugin._embedStderr||"").slice(0,200);new F.Notice(o("feat_build_failed")+(se?": "+se:""),8e3)}this.plugin._embedStderr="",this.display(),this._refreshSnapshots(t)}}),clearInterval(($=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:(L,oe)=>{var se;if(this.plugin._embedPolling=!1,L===0&&oe)try{let ae=JSON.parse(oe).data;if(ae&&ae.build_state){let Z=ae.build_state;(Z.status==="stopping"||Z.status==="idle")&&this.plugin._embedProcess&&(this.plugin._embedProcess=null,clearInterval((se=this.plugin._embedPollInterval)!=null?se:void 0),this.plugin._embedPollInterval=null,this.display()),Z.current!==void 0&&Z.total!==void 0&&(this.plugin._embedProgress.current=Z.current,this.plugin._embedProgress.total=Z.total||1,this.plugin._embedProgress.key=Z.paper_id||"")}}catch(de){}}}))},2e3),this.display()},M=je(t),O=!1;M&&typeof M.summary=="object"&&M.summary!==null&&"status"in M.summary&&(O=M.summary.status==="version_mismatch");let B;switch(C?O?B="runtime-mismatch":P==="stopping"?B="stopping":v&&P==="running"?B="building":P==="failed"?B="failed":P==="stopped"?B="stopped":S?B="stale":y?B="corrupted":k?B="ready":B="idle":B="deps-missing",B){case"building":{let w=s.createEl("div",{cls:"paperforge-progress-track"});w.style.cssText="flex:1;";let j=m>0?(u/m*100).toFixed(1):"0",Q=w.createEl("div",{cls:"paperforge-progress-seg done"});if(Q.style.cssText=`width:${j}%; min-width:${u>0?"2px":"0"};`,u{this._callPython(["embed","stop","--json"],{timeout:8e3}),this.display()});break}case"stopping":{let w=s.createEl("div",{cls:"paperforge-progress-track"});w.style.cssText="flex:1; opacity:0.5;";let j=m>0?(u/m*100).toFixed(1):"0",Q=w.createEl("div",{cls:"paperforge-progress-seg done"});if(Q.style.cssText=`width:${j}%; min-width:${u>0?"2px":"0"};`,uI("--resume"));let j=s.createEl("button");j.setText(o("retrieval_force_rebuild")),j.style.marginLeft="6px",j.addEventListener("click",()=>I("--force"));break}case"stopped":{i.setText(o("retrieval_build_stopped"));let w=s.createEl("button");w.setText(o("retrieval_retry")),w.className="mod-cta",w.addEventListener("click",()=>I("--resume"));break}case"corrupted":{i.createEl("div",{cls:"paperforge-desc-box",text:o("feat_vector_corrupted"),attr:{style:"background:var(--background-modifier-warning);"}});let w=s.createEl("button");w.setText(o("retrieval_force_rebuild")),w.className="mod-cta",w.addEventListener("click",()=>I("--force"));break}case"stale":{i.createEl("div",{cls:"paperforge-desc-box",text:o("retrieval_build_stale"),attr:{style:"color:var(--text-warning);"}});let w=s.createEl("button");w.setText(o("retrieval_rebuild_vectors")),w.className="mod-cta",w.addEventListener("click",()=>I("--resume"));break}case"ready":{s.createEl("span",{text:E+" chunks embedded",cls:"setting-item-description"});let w=s.createEl("button");w.setText(o("retrieval_rebuild_vectors")),w.className="mod-cta",w.addEventListener("click",()=>I("--resume"));let j=s.createEl("button");j.setText(o("retrieval_force_rebuild")),j.style.marginLeft="6px",j.addEventListener("click",()=>I("--force"));break}case"deps-missing":{i.setText(o("retrieval_build_deps_missing"));let w=s.createEl("a");w.setText(o("feat_install_deps")),w.style.cssText="cursor:pointer; text-decoration:underline;",w.addEventListener("click",()=>{this.display()});break}case"runtime-mismatch":{i.createEl("div",{cls:"paperforge-desc-box",text:o("retrieval_build_runtime_mismatch"),attr:{style:"color:var(--text-warning);"}});let w=s.createEl("a");w.setText(o("runtime_health_sync")),w.style.cssText="cursor:pointer; text-decoration:underline;",w.addEventListener("click",()=>{this.display()});break}case"idle":default:{i.setText(o("retrieval_build_idle"));let w=s.createEl("button");w.setText(o("feat_build_btn")),w.className="mod-cta",w.addEventListener("click",()=>I("--resume"));break}}})()}_getCurrentModelKey(){return this.plugin.settings.vector_db_api_model||"text-embedding-3-small"}_parseEmbedStatus(e){let t={};return e&&(e.split(` -`).forEach(r=>{let n=r.match(/^\s*([^:]+):\s*(.*)/);n&&(t[n[1].trim()]=n[2].trim())}),t.db_exists!==void 0&&(t.db_exists=t.db_exists==="True"),t.chunk_count!==void 0&&(t.chunk_count=parseInt(t.chunk_count,10)||0)),t}_getPythonDesc(e,t){return t==="stale"?`[!!] ${e} (stale \u2014 path no longer exists, update or clear the override below)`:t==="manual"?`${e} (manual)`:`${e} (auto-detected)`}_refreshPythonInterpDesc(e,t){let r=this._pythonInterpDescEl;r&&(t==="stale"?r.textContent=`[!!] ${e} (stale \u2014 path no longer exists, update or clear the override below)`:t==="manual"?r.textContent=`${e} (manual)`:r.textContent=`${e} (auto-detected)`)}_validatePythonOverride(){let e=this.plugin.settings.python_path?this.plugin.settings.python_path.trim():"",t=this._customPathDescEl;if(!e){let r="\u8BF7\u8F93\u5165\u8DEF\u5F84 / Enter a path first";t&&(t.innerHTML=`\u2717 ${r}`),new F.Notice(r);return}if(!W.existsSync(e)){let r="\u8DEF\u5F84\u4E0D\u5B58\u5728 / Path does not exist";t&&(t.innerHTML=`\u2717 ${r}`),new F.Notice(r,4e3);return}try{W.accessSync(e,W.constants.X_OK)}catch(r){let n="\u4E0D\u53EF\u6267\u884C / Not executable";t&&(t.innerHTML=`\u2717 ${n}`),new F.Notice(n,4e3);return}(0,G.execFile)(e,["--version"],{timeout:8e3},(r,n)=>{if(r||!n){let l="\u65E0\u6CD5\u8FD0\u884C / Cannot run";t&&(t.innerHTML=`\u2717 ${l}`),new F.Notice(l,4e3);return}let a=n.match(/Python (\d+)\.(\d+)/);if(!a){let l="\u65E0\u6CD5\u89E3\u6790\u7248\u672C / Cannot parse version";t&&(t.innerHTML=`\u2717 ${l}`),new F.Notice(l,4e3);return}let s=parseInt(a[1],10),i=parseInt(a[2],10);if(s<3||s===3&&i<10){let l="Python \u7248\u672C\u8FC7\u4F4E\uFF0C\u9700\u8981 3.10+ / Python version too low, need 3.10+";t&&(t.innerHTML=`\u2717 ${l}`),new F.Notice(l,4e3);return}(0,G.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`;t&&(t.innerHTML=`\u26A0 ${c}`),new F.Notice(c,4e3)}else{let c=`\u2713 Python ${s}.${i} \u6709\u6548 / Valid`;t&&(t.innerHTML=`${c}`),new F.Notice(c,4e3)}})})}_syncRuntime(e){let t=this.app.vault.adapter.basePath,{path:r,extraArgs:n=[]}=z(t,this.plugin.settings,void 0,void 0),a=this.plugin.manifest.version,s=Nt(r,a,n);e.setDisabled(!0),e.setButtonText(o("runtime_health_syncing"));let i=(c,_)=>(console.log(`[PaperForge] Sync Runtime: trying ${_}`),Vt(s.cmd,c,t,s.timeout,void 0,Se())),l=()=>{let c="opencode";try{let m=W.readFileSync(ee.join(t,"paperforge.json"),"utf-8"),f=JSON.parse(m);f.agent_key&&(c=f.agent_key)}catch(m){}let _=[...n,"-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+'", overwrite=True); print("skills deployed" if r["skill_deployed"] else "skills skipped", flush=True)'],h=(0,G.spawn)(r,_,{cwd:t,timeout:3e4,windowsHide:!0}),u="";h.stdout.on("data",m=>{u+=m.toString("utf-8")}),h.on("close",m=>{console.log(`[PaperForge] Skill deploy: ${u.trim()} (exit ${m})`)})};i(s.pypiArgs,"PyPI").then(c=>{if(c.exitCode===0){console.log("[PaperForge] Sync Runtime: installed via PyPI"),l(),new F.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(_=>{_.exitCode===0?(console.log("[PaperForge] Sync Runtime: installed via git"),l(),new F.Notice(o("runtime_health_sync_done").replace("{0}",a),5e3),this.display()):(e.setDisabled(!1),e.setButtonText(o("runtime_health_sync")),console.error("[PaperForge] git fallback stderr:",_.stderr),new F.Notice(o("runtime_health_sync_fail").replace("{0}","pip exit code "+_.exitCode),8e3))})})}_debouncedSave(){clearTimeout(this._saveTimeout),this._saveTimeout=setTimeout(()=>this.plugin.saveSettings(),500)}_preCheck(e){var a;let t=this.app.vault.adapter.basePath,{path:r,extraArgs:n=[]}=z(t,(a=this.plugin)==null?void 0:a.settings,void 0,void 0);(0,G.execFile)(r,[...n,"--version"],{timeout:8e3},(s,i)=>{let l=[];l.push({label:"Python",ok:!s,detail:s?o("check_python_fail"):i.trim()});let c=!1,_=process.env.HOME||process.env.USERPROFILE||nr.homedir()||"";if(process.platform==="darwin")c=["/Applications/Zotero.app",ee.join(_,"Applications","Zotero.app")].some(b=>{try{return W.existsSync(b)}catch(E){return!1}});else if(process.platform==="win32"){let g=process.env.ProgramFiles||"",b=process.env.LOCALAPPDATA||"";c=[ee.join(g,"Zotero"),ee.join(g,"(x86)","Zotero"),ee.join(b,"Programs","Zotero"),ee.join(b,"Zotero"),ee.join(_,"AppData","Local","Programs","Zotero")].filter(Boolean).some(k=>{try{return W.existsSync(k)}catch(y){return!1}})}else c=[ee.join(_,".local","share","zotero","zotero"),"/usr/bin/zotero","/usr/local/bin/zotero"].some(b=>{try{return W.existsSync(b)}catch(E){return!1}});let h=this.plugin.settings.zotero_data_dir;if(!c&&h)try{c=W.existsSync(h)}catch(g){}l.push({label:"Zotero",ok:c,detail:c?o("check_zotero_ok"):o("check_zotero_fail")});let u=!1,m=process.env.APPDATA||"";process.platform==="win32"&&m&&(u=Je(ee.join(m,"Zotero","Zotero","Profiles"))),!u&&process.platform==="darwin"&&_&&(u=Je(ee.join(_,"Library","Application Support","Zotero","Profiles"))),!u&&process.platform!=="win32"&&process.platform!=="darwin"&&_&&(u=Je(ee.join(_,".zotero","zotero","Profiles"))),!u&&h&&String(h).trim()&&(u=yt(h.trim())),!u&&_&&(u=yt(ee.join(_,"Zotero"))),l.push({label:"Better BibTeX",ok:u,detail:u?o("check_bbt_ok"):o("check_bbt_fail")});let f={true:"\u2713",false:"\u2717"};if(this._checkEl){this._checkEl.setText(l.map(b=>`${f[String(b.ok)]} ${b.label}: ${b.detail}`).join(` -`));let g=l.some(b=>!b.ok);this._checkEl.className=`paperforge-message msg-${g?"error":"ok"}`}let x=l.filter(g=>!g.ok);x.length>0&&new F.Notice(`[!!] \u672A\u901A\u8FC7: ${x.map(g=>g.label).join(", ")}`,6e3),e()})}_renderMaintenanceTab(e){var _;e.createEl("h2",{text:o("tab_maintenance")||"\u7EF4\u62A4"});let r=(_=this.app.vault.adapter.basePath)!=null?_:"",n=e.createEl("div"),a={active:"all"},s=null;try{s=et(r)}catch(h){}let i=z(r,this.plugin.settings,W,G.execFileSync);if(!i.path){n.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 l=()=>!!this.plugin._ocrProcess,c=h=>{n.empty();let u=h,m=n.createEl("div",{cls:"pf-maint-filters"}),f=m.createEl("button",{cls:"pf-maint-filter"+(a.active==="all"?" active":""),text:o("maintenance_filter_all")||"All"});f.addEventListener("click",()=>{a.active="all",c(h)});let x=m.createEl("button",{cls:"pf-maint-filter"+(a.active==="recommended"?" active":""),text:o("maintenance_filter_recommended")||"Recommended"});x.addEventListener("click",()=>{a.active="recommended",c(h)});let g=a.active==="recommended"?u.filter(b=>b.needs_derived_rebuild===!0):u;if(g.length===0)n.createEl("p",{text:"\u5F53\u524D\u7B5B\u9009\u6761\u4EF6\u4E0B\u65E0\u6570\u636E",cls:"setting-item-description"});else{let b=i.path,E=i.extraArgs||[],k=n.createEl("div",{cls:"pf-maint-progress"});k.style.display="none";let y=k.createEl("div",{cls:"paperforge-progress-track"});y.style.cssText="flex:1;";let v=y.createEl("div",{cls:"paperforge-progress-seg done"}),S=y.createEl("div",{cls:"paperforge-progress-seg pending"}),C=k.createEl("span",{cls:"pf-maint-progress-text"}),P=k.createEl("span",{cls:"pf-maint-progress-key"}),R=k.createEl("button",{text:o("maintenance_stop")||"Stop"});R.className="mod-warning",R.addEventListener("click",()=>{let T=this.plugin._ocrProcess;T&&(T.stdin&&typeof T.stdin.write=="function"?T.stdin.write(`PAPERFORGE_STOP -`):typeof T.kill=="function"&&T.kill("SIGINT")),this.plugin._ocrWasStopped=!0,R.disabled=!0,R.textContent=(o("maintenance_stop")||"Stop")+"\u2026"});let I=()=>{let T=this.plugin._ocrProgress;if(!T||T.total===0||!this.plugin._ocrProcess){k.style.display="none";return}k.style.display="flex";let X=T.total>0?(T.current/T.total*100).toFixed(1):"0";v.style.width=`${X}%`,v.style.minWidth=T.current>0?"2px":"0",T.current{let X=document.createElement("th");X.textContent=T,Q.appendChild(X)});let $=l();for(let T of g){let X=j.insertRow(),A=X.insertCell();A.style.cssText="padding:3px 4px;text-align:center;width:24px;";let N=document.createElement("input");N.type="checkbox",N.className="pf-maint-sel",N.checked=M.get(T.key)||!1,N.addEventListener("change",()=>{M.set(T.key,N.checked),se()}),A.appendChild(N);let te=X.insertCell();te.style.cssText="padding:3px 4px;";let D=te.createEl("div",{cls:"pf-maint-paper-info"});D.createEl("div",{cls:"pf-maint-paper-title",text:T.title||T.key}),D.createEl("div",{cls:"pf-maint-paper-key",text:T.key});let re=X.insertCell();re.style.cssText="padding:3px 4px;",re.createEl("div",{cls:"pf-maint-reason",text:T.display_reason||""});let ue=X.insertCell();ue.style.cssText="padding:3px 4px;white-space:nowrap;";let Ee=ue.createEl("div",{cls:"pf-maint-actions"}),Le=xt(T);if(Le==="rebuild"){let le=Ee.createEl("button",{cls:"pf-maint-action-btn rebuild",text:o("maintenance_btn_rebuild")||"Rebuild"});$&&(le.disabled=!0),le.addEventListener("click",()=>{(0,G.execFile)(b,[...E,"-m","paperforge","ocr","rebuild",T.key],{cwd:r,timeout:12e4,windowsHide:!0},()=>{new F.Notice((o("maintenance_btn_rebuild")||"Rebuild")+" \u2014 "+T.key)})})}else if(Le==="redo"){let le=Ee.createEl("button",{cls:"pf-maint-action-btn redo",text:o("ocr_maint_redo_btn")||"Redo"});$&&(le.disabled=!0),le.addEventListener("click",()=>{kt("redo")&&!confirm((o("ocr_maint_redo_confirm")||"Rerun OCR for {n} paper(s)? Existing derived OCR artifacts will be replaced.").replace("{n}","1"))||(0,G.execFile)(b,[...E,"-m","paperforge","ocr","redo",T.key],{cwd:r,timeout:3e5,windowsHide:!0},()=>{new F.Notice((o("ocr_maint_redo_btn")||"Redo OCR")+" \u2014 "+T.key)})})}}let L=n.createEl("div",{cls:"pf-maint-batch-bar"}),oe=L.createEl("span",{cls:"pf-maint-batch-label",text:"0 selected"}),se=()=>{let T=g.filter(X=>M.get(X.key)).length;oe.textContent=T+" selected"},de=L.createEl("button",{cls:"mod-cta",text:o("maintenance_batch_rebuild")||"\u25B6 Rebuild selected"});de.disabled=$;let ae=L.createEl("button",{cls:"mod-cta",text:o("maintenance_batch_redo")||"\u25B6 Full OCR redo selected"});ae.disabled=$;let Z=T=>{let X=g.filter(D=>M.get(D.key)&&xt(D)===T);if(X.length===0){let D=T==="rebuild"?o("maintenance_btn_rebuild")||"Rebuild":o("ocr_maint_redo_btn")||"Redo";new F.Notice("Selected papers are not eligible for "+D+". Uncheck ineligible rows and try again.",6e3);return}if(kt(T)&&!confirm((o("ocr_maint_redo_confirm")||"Rerun OCR for {n} paper(s)? Existing derived OCR artifacts will be replaced.").replace("{n}",String(X.length))))return;let A=X.map(D=>D.key);this.plugin._ocrProgress={current:0,total:A.length,key:""},this.plugin._ocrBuffer="",this.plugin._ocrWasStopped=!1;let N=T==="rebuild"?"OCR_REBUILD":"OCR_REDO";de.disabled=!0,ae.disabled=!0,Array.from(O.querySelectorAll(".pf-maint-action-btn")).forEach(D=>{D.disabled=!0}),Array.from(O.querySelectorAll(".pf-maint-sel")).forEach(D=>{D.disabled=!0}),f.disabled=!0,x.disabled=!0,R.disabled=!1,R.textContent=o("maintenance_stop")||"Stop";let te=this._callPython(["ocr",T,...A],{stream:!0,onData:D=>{var Le;let re=typeof D=="string"?D:Buffer.isBuffer(D)?D.toString("utf-8"):String(D),{events:ue,buffer:Ee}=Ct(re,(Le=this.plugin._ocrBuffer)!=null?Le:"");this.plugin._ocrBuffer=Ee;for(let le of ue)le.event==="START"?this.plugin._ocrProgress&&(this.plugin._ocrProgress.total=le.total||A.length):le.event==="PROGRESS"&&(this.plugin._ocrProgress={current:le.current||0,total:le.total||A.length,key:le.key||""});I()},onError:D=>{this.plugin._ocrProcess=null,new F.Notice("Batch error: "+(D.message||D)),c(h)},onClose:D=>{this.plugin._ocrWasStopped||D===130?(this.plugin._ocrWasStopped=!1,this.plugin._ocrProcess=null,I(),new F.Notice("OCR batch stopped by user.")):D===0?(this.plugin._ocrProgress&&(this.plugin._ocrProgress.current=this.plugin._ocrProgress.total),this.plugin._ocrProcess=null,I(),new F.Notice((o("maintenance_batch_complete")||"Batch operation complete \u2014 {n} papers processed.").replace("{n}",String(A.length)))):(this.plugin._ocrProcess=null,I(),new F.Notice("Batch operation finished with exit code "+D+".",8e3)),wt(r,b,E,s).then(re=>{s=et(r),c(re.data)}).catch(()=>{c(u)})}});this.plugin._ocrProcess=te,I()};de.addEventListener("click",()=>Z("rebuild")),ae.addEventListener("click",()=>Z("redo")),se()}};if(s){let h=Object.values(s.papers);c(h)}else n.createEl("p",{text:"\u6B63\u5728\u52A0\u8F7D OCR \u7EF4\u62A4\u6570\u636E\u2026"});wt(r,i.path,i.extraArgs||[],s||null).then(h=>{s=et(r),(h.changed||!s)&&c(h.data)}).catch(()=>{s||(n.empty(),n.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"}))})}_renderReleaseNotesTab(e){e.createEl("h2",{text:"\u66F4\u65B0\u4E0E\u624B\u518C"}),e.createEl("h3",{text:"\u7248\u672C\u66F4\u65B0\u8BB0\u5F55"});let t=sr.default.versions||[];for(let a of t){let s=e.createEl("div",{cls:"paperforge-release-card"}),i=s.createEl("div",{cls:"paperforge-release-header"});if(i.createEl("strong",{text:`v${a.version} \u2014 ${a.title}`}),i.createEl("span",{cls:"paperforge-release-date",text:` (${a.date})`}),a.breaking_or_migration&&a.breaking_or_migration.length>0){let l=s.createEl("div",{cls:"paperforge-release-section"});l.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",{cls:"paperforge-release-item",text:`\u2022 ${c}`})}if(a.new_features&&a.new_features.length>0){let l=s.createEl("div",{cls:"paperforge-release-section"});l.createEl("div",{cls:"paperforge-release-label",text:"\u65B0\u529F\u80FD"});for(let c of a.new_features)l.createEl("div",{cls:"paperforge-release-item",text:`\u2022 ${c}`})}if(a.fixes&&a.fixes.length>0){let l=s.createEl("div",{cls:"paperforge-release-section"});l.createEl("div",{cls:"paperforge-release-label",text:"\u4FEE\u590D"});for(let c of a.fixes)l.createEl("div",{cls:"paperforge-release-item",text:`\u2022 ${c}`})}if(a.recommended_actions&&a.recommended_actions.length>0){let l=s.createEl("div",{cls:"paperforge-release-section paperforge-release-recommended"});l.createEl("div",{cls:"paperforge-release-label",text:"\u5EFA\u8BAE\u64CD\u4F5C"});for(let c of a.recommended_actions)l.createEl("div",{cls:"paperforge-release-item paperforge-release-item-bold",text:`\u2022 ${c}`})}}e.createEl("h3",{text:"\u4F7F\u7528\u624B\u518C"}),e.createEl("div",{cls:"paperforge-manual-links"}).createEl("a",{text:"\u2192 \u67E5\u770B\u5B8C\u6574\u4F7F\u7528\u624B\u518C\uFF08GitHub\uFF09",href:"https://github.com/LLLin000/PaperForge/blob/master/docs/user-manual.md"}).setAttr("target","_blank")}_initCapabilityState(){let e=this.plugin.settings.capabilityState;this._capabilityState=Ot(e!=null?e:{},Ce),this._persistCapabilityState()}_persistCapabilityState(){this._capabilityState&&(this.plugin.settings.capabilityState=this._capabilityState,this.plugin.saveSettings())}_probeModule(e){var i,l,c,_;if(this._probing.has(e))return;this._probing.add(e);let t=(i=this._capabilityState)==null?void 0:i[e],r={schema_version:1,module:e,capability_state:(l=t==null?void 0:t.capability_state)!=null?l:"unknown",activity_state:"running",activity_label:"Probing...",activity_progress:null,severity:"unknown",reason:{code:`${e}.probing`,text:`Checking ${e} status...`},action:{primary:$e(e)},notices:(c=t==null?void 0:t.notices)!=null?c:[],updated_at:new Date().toISOString(),ttl_seconds:(_=t==null?void 0:t.ttl_seconds)!=null?_:0};this._updateCapabilityEnvelope(e,r);let n=this.app.vault.adapter.basePath,a=this._resolveRuntimeCommand(n);if(!a){if(this._probing.delete(e),e==="installation"){let h={schema_version:1,module:"installation",capability_state:"unknown",activity_state:"idle",activity_label:null,activity_progress:null,severity:"error",reason:{code:"installation.no_python",text:"No Python found. Run the Setup Wizard to install the managed runtime."},action:{primary:Dt()},notices:[],updated_at:new Date().toISOString(),ttl_seconds:60};this._updateCapabilityEnvelope(e,h)}else this._updateCapabilityEnvelope(e,Fe(e));return}let s=[...a.args,"-m","paperforge","--vault",n,"probe",e,"--json"];(0,G.execFile)(a.path,s,{cwd:n,timeout:15e3},(h,u,m)=>{if(this._probing.delete(e),h){console.warn(`[PaperForge] Probe ${e} failed:`,h.message),this._updateCapabilityEnvelope(e,Fe(e));return}try{let f=JSON.parse(u);ct(f,e)?this._updateCapabilityEnvelope(e,f):(console.warn(`[PaperForge] Probe ${e}: invalid envelope schema`,u==null?void 0:u.slice(0,200)),this._updateCapabilityEnvelope(e,Fe(e)))}catch(f){console.warn(`[PaperForge] Probe ${e}: unparseable JSON`,u==null?void 0:u.slice(0,200)),this._updateCapabilityEnvelope(e,Fe(e))}})}_updateCapabilityEnvelope(e,t){this._capabilityState||(this._capabilityState={});let r=this._capabilityState[t.module];this._capabilityState[t.module]=t,this._persistCapabilityState(),(r==null?void 0:r.activity_state)==="running"&&t.activity_state!=="running"&&new F.Notice(o("cc_notice_refreshed"),3e3),this.display()}_ccBadgeKey(e,t){return e.severity==="ok"?"cc_badge_ok":e.severity==="error"&&t==="installation"?"cc_badge_setup":e.severity==="warning"||e.severity==="error"?"cc_badge_attention":"cc_badge_pending"}_sevClass(e){return e==="error"?"error":e==="warning"?"warn":e==="unknown"?"unknown":"ok"}_localizeReason(e,t){let r="cc_reason_"+e.replace(/\./g,"_"),n=o(r);if(n!==r)return n.replace("{module}",t);let s="cc_reason_"+e.replace(/^[a-z]+\./,""),i=o(s);return i===s?null:i.replace("{module}",t)}_renderCard(e,t,r){let n=r,a=this._sevClass(n.severity),s=ke._REAL_PROBE.has(t),i=ke._NAVIGABLE.has(t),l=e.createEl("div",{cls:"pf-cc-card",attr:{role:"listitem",tabindex:"0","data-module":t,"aria-label":`${o("cc_module_"+t)} \u2014 ${o(this._ccBadgeKey(n,t))}`}}),c=l.createEl("div",{cls:"pf-cc-card-header"}),_=c.createEl("div",{cls:"pf-cc-card-name-area"});if(i){let v=t==="installation"?o("module_detail_open_installation"):t==="help"?o("module_detail_open_help"):t==="maintenance"?o("module_detail_open_maintenance"):o("md_select_installation"),S=_.createEl("button",{cls:"pf-open-module-btn",text:o("cc_module_"+t),attr:{"data-module":t,"aria-label":v}});S.addEventListener("click",()=>this._handleCardNavigation(t)),S.addEventListener("keydown",C=>{(C.key==="Enter"||C.key===" ")&&(C.preventDefault(),this._handleCardNavigation(t))})}else _.createEl("div",{cls:"pf-cc-card-name",text:o("cc_module_"+t)});c.createEl("div",{cls:`pf-cc-card-badge pf-cc-card-badge--${a}`,text:o(this._ccBadgeKey(n,t))});let h;if(!s)h=o("cc_reason_placeholder").replace("{module}",o("cc_module_"+t));else{let v=this._localizeReason(n.reason.code,t);h=v!=null?v:n.reason.text}if(l.createEl("div",{cls:"pf-cc-card-reason",text:h}),n.activity_state==="running"&&n.activity_label){let v=l.createEl("div",{cls:"pf-cc-card-activity",attr:{"aria-live":"polite"}});if(v.createEl("span",{text:n.activity_label}),n.activity_progress&&n.activity_progress.total>0){let S=Math.round(n.activity_progress.current/n.activity_progress.total*100),P=v.createEl("div",{cls:"pf-cc-card-progress",attr:{role:"progressbar","aria-valuenow":String(n.activity_progress.current),"aria-valuemin":"0","aria-valuemax":String(n.activity_progress.total)}}).createEl("div",{cls:"pf-cc-card-progress-fill"});P.style.width=S+"%"}}let u=l.createEl("div",{cls:"pf-cc-card-footer"});if(s&&n.action.primary&&!qe(n)){let v=Ze(n),C=v.kind==="setup"?"pf-cc-card-action pf-cc-card-action--primary":"pf-cc-card-action";u.createEl("button",{cls:C,text:v.label,attr:{"aria-label":v.label}}).addEventListener("click",()=>{v.kind==="setup"?new De(this.app,this.plugin,()=>{this._probeModule("installation"),this._probeModule("help")}).open():this._probeModule(t)})}let m=l.createEl("details",{cls:"pf-cc-card-diagnostic"});m.createEl("summary",{text:o("cc_diagnostic_toggle")});let f=m.createEl("div",{cls:"pf-cc-card-diagnostic-body"}),x=o("cc_state_"+n.capability_state)||n.capability_state,g=o("cc_severity_"+n.severity)||n.severity,b=o("cc_activity_"+n.activity_state)||n.activity_state,E;try{E=new Date(n.updated_at).toLocaleString()}catch(v){E=n.updated_at}f.createEl("div",{text:`${o("cc_diag_module")}: ${n.module}`}),f.createEl("div",{text:`${o("cc_diag_state")}: ${x}`}),f.createEl("div",{text:`${o("cc_diag_severity")}: ${g}`}),f.createEl("div",{text:`${o("cc_diag_activity")}: ${b}`});let k=f.createEl("div");k.appendText(o("cc_diag_reason")+": "+h+" ");let y=k.createEl("code",{text:n.reason.code});f.createEl("div",{text:`${o("cc_diag_ttl")}: ${String(n.ttl_seconds)}s`}),f.createEl("div",{text:`${o("cc_diag_updated")}: ${E}`})}_handleCardNavigation(e){e==="installation"?(this.activeTab="module-detail",this._selectedDetailModule="installation",this._focusTargetId="#pf-installation-detail-heading"):e==="help"?(this.activeTab="help",this._selectedDetailModule="",this._focusTargetId="button.pf-open-module-btn[data-module=help]"):e==="maintenance"&&(this.activeTab="maintenance",this._selectedDetailModule="",this._focusTargetId=null),this.display()}_renderControlCenter(e){var m,f,x;let t=e.createEl("div",{cls:"pf-control-center"}),r=Ce,n=(m=this._capabilityState)!=null?m:{},a=0,s=0,i=0;for(let g of r){let b=(f=n[g])!=null?f:Re(g);b.severity==="ok"&&b.capability_state==="ready"&&b.action.primary===null?a++:ke._REAL_PROBE.has(g)?(b.severity==="error"||b.severity==="warning"||b.severity==="unknown")&&s++:i++}let l=t.createEl("div",{cls:"pf-cc-summary"});l.createEl("div",{cls:"pf-cc-summary-eyebrow",text:o("cc_title")});let c,_;s>0?(c=o("cc_summary_attention"),_=o("cc_summary_attention_body")):a===r.length?(c=o("cc_summary_ok"),_=o("cc_summary_ok_body")):a>0&&i>0&&s===0?(c=o("cc_summary_core_ok").replace("{n}",String(i)),_=o("cc_summary_core_ok_body")):(c=o("cc_summary_core_ok").replace("{n}",String(r.length-a)),_=o("cc_desc")),l.createEl("div",{cls:"pf-cc-summary-title",text:c}),l.createEl("div",{cls:"pf-cc-summary-body",text:_});let h=l.createEl("div",{cls:"pf-cc-summary-counts"});h.createEl("div",{cls:"pf-cc-summary-count",text:o("cc_n_ready").replace("{n}",String(a))}),i>0&&h.createEl("div",{cls:"pf-cc-summary-count",text:o("cc_n_pending").replace("{n}",String(i))});let u=t.createEl("div",{cls:"pf-cc-grid",attr:{role:"list","aria-label":o("cc_zone_modules")}});for(let g of r){let b=(x=n[g])!=null?x:Re(g);this._renderCard(u,g,b)}}_applyStaleTolerance(){if(!this._capabilityState)return;let e=!1;for(let t of Ce){let r=this._capabilityState[t];r&&dt(r)&&(this._capabilityState[t]=pt(t),e=!0)}e&&this._persistCapabilityState()}};ke._REAL_PROBE=new Set(["installation","help"]),ke._NAVIGABLE=new Set(["installation","maintenance","help"]);var at=ke;var V=require("obsidian"),ve=K(require("fs")),Ue=K(require("path")),be=require("child_process");var ze=K(require("path"));function ar(d){if(!d)return null;let p=ze.dirname(d);for(;;){let e=ze.basename(p);if(!e||e===".")break;let t=e.match(/^([A-Z0-9]{8})(?:\s*-\s*.*)?$/i);if(t)return t[1];let r=ze.dirname(p);if(r===p)break;p=r}return null}var J=K(require("fs")),we=K(require("path"));function Ke(d){return ce(d).ocrDir}function Tr(d,p){let e=we.join(Ke(d),p,"versions","manifest.json");try{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)return r}return null}catch(t){return null}}function Ar(d){let p=Ke(d);try{return J.existsSync(p)?J.readdirSync(p,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name):[]}catch(e){return[]}}function Rt(d){let p=Ar(d),e=[];for(let t of p){let r=Tr(d,t);if(!r)continue;let n=r.versions.map(s=>s.label),a=0;for(let s of n){let i=we.join(Ke(d),t,"versions",s,"fulltext.md");try{J.existsSync(i)&&(a+=J.statSync(i).size)}catch(l){}}e.push({key:t,title:t.replace(/_/g," "),versions:r.versions,currentLabel:r.current.label,totalSize:a})}return e.sort((t,r)=>t.title.localeCompare(r.title)),e}function or(d,p,e){let t=Ke(d),r=we.join(t,p,"versions",e,"fulltext.md"),n=we.join(t,p,"render"),a=we.join(n,"fulltext.md");try{return J.existsSync(r)?(J.existsSync(n)||J.mkdirSync(n,{recursive:!0}),J.copyFileSync(r,a),!0):!1}catch(s){return!1}}function lr(d,p,e,t){var u;let r=Ke(d),n=we.join(r,p,"versions",e,"fulltext.md"),a=we.join(r,p,"versions",t,"fulltext.md"),s="",i="";try{J.existsSync(n)&&(s=J.readFileSync(n,"utf-8"))}catch(m){}try{J.existsSync(a)&&(i=J.readFileSync(a,"utf-8"))}catch(m){}let l=ir(s),c=ir(i),_=Math.max(l.length,c.length),h=[];for(let m=0;m<_;m++){let f=m{t.createEl("button",{cls:"paperforge-settings-tab"+(a.id===this.activeTab?" paperforge-settings-tab--active":""),text:a.label}).addEventListener("click",()=>{this.activeTab=a.id,this.display()})}),r.forEach(a=>{n[a.id]=e.createDiv({cls:"paperforge-tab-content"+(a.id===this.activeTab?" paperforge-tab-content--active":"")})}),this.activeTab==="overview"?this._renderOverviewTab(n.overview):this.activeTab==="module-detail"?this._renderModuleDetailTab(n["module-detail"]):this.activeTab==="maintenance"?this._renderMaintenanceTab(n.maintenance):this.activeTab==="help"&&this._renderHelpTab(n.help),this._focusTargetId&&this.activeTab!=="help"){let a=e.querySelector(this._focusTargetId);if(a){try{a.focus()}catch(s){}this._focusTargetId=null}}}_renderOverviewTab(e){var r;let t=this._getVaultBasePath();this.plugin.settings.vault_path||(this.plugin.settings.vault_path=t,this._debouncedSave()),e.createEl("h2",{text:i("header_title")||"PaperForge"}),e.createEl("p",{text:i("desc"),cls:"paperforge-settings-desc"}),this._renderControlCenter(e),this._renderAdvancedSection(e);for(let n of Fe){let a=(r=this._capabilityState)==null?void 0:r[n];a&&a.capability_state==="unknown"&&a.updated_at===new Date(0).toISOString()&&!this._attemptedProbes.has(n)&&(this._attemptedProbes.add(n),n!=="maintenance"&&this._probeModule(n))}}_renderAdvancedSection(e){this._advCollapsed===void 0&&(this._advCollapsed=!0);let t=e.createEl("div",{cls:"paperforge-collapsible-header"}),r=t.createEl("span",{text:"\u25B6",cls:"paperforge-collapsible-arrow"});r.style.transform=this._advCollapsed?"rotate(0deg)":"rotate(90deg)";let n=t.createEl("span",{cls:"paperforge-collapsible-title",text:"Advanced"}),a=t.createEl("span",{cls:"paperforge-collapsible-sub",text:"Memory + Vector DB + Embedding"}),s=e.createEl("div",{cls:"paperforge-collapsible-content"});s.style.display=this._advCollapsed?"none":"",t.addEventListener("click",()=>{this._advCollapsed=!this._advCollapsed,s.style.display=this._advCollapsed?"none":"",r.style.transform=this._advCollapsed?"rotate(0deg)":"rotate(90deg)"}),s.createEl("h4",{text:"Memory Layer"}),s.createEl("div",{cls:"paperforge-desc-box"}).setText(i("feat_memory_desc"));let l=s.createEl("div",{cls:"paperforge-memory-status"}),c=this.app.vault.adapter.basePath;this.plugin._lastSyncTime&&!this._lastSyncTime&&(this._lastSyncTime=this.plugin._lastSyncTime),this._memoryStatusText===null&&(this._memoryStatusText=Xe(c)),this._renderMemoryStatusText(l,this._memoryStatusText,this._lastSyncTime),this._renderVectorSection(s)}_getVaultBasePath(){let e=this.app.vault.adapter;if(e&&typeof e=="object"&&"basePath"in e){let t=e.basePath;return typeof t=="string"?t:""}return""}_ensureManagedRuntime(){return this._managedRuntime?this._managedRuntime:(this._managedRuntime=new st({version:this.plugin.manifest.version}),this._managedRuntime)}_resolveRuntimeCommand(e){let t=tr(this._ensureManagedRuntime().current());if(t)return{path:t.command,args:[...t.args]};console.warn("[PaperForge] Release N: Managed runtime not ready (cold/stale), falling back to legacy resolver");let r=ye(e,this.plugin.settings);return r.path?{path:r.path,args:r.extraArgs}:null}_renderInstallationDetail(e){var S,R;e.createEl("button",{cls:"pf-back-btn",text:i("btn_back_to_overview")}).addEventListener("click",()=>{this.activeTab="overview",this._selectedDetailModule="",this._focusTargetId="button.pf-open-module-btn[data-module=installation]",this.display()});let r=e.createEl("h2",{cls:"pf-installation-detail-heading",text:i("installation_detail_heading")||"Installation Details",attr:{id:"pf-installation-detail-heading",tabindex:"-1"}}),n=[{id:"installation",labelKey:"md_select_installation",disabled:!1}],a=e.createEl("div",{cls:"pf-module-detail-selector"});for(let P of n){let F=a.createEl("button",{cls:"pf-module-detail-btn"+(P.id==="installation"?" pf-module-detail-btn--active":"")+(P.disabled?" pf-module-detail-btn--disabled":""),text:i(P.labelKey)});P.disabled&&(F.disabled=!0)}let s=(S=this._capabilityState)!=null?S:{},o="installation",l=(R=s[o])!=null?R:Se(o),c=this._sevClass(l.severity),u=je(l),h=e.createEl("div",{cls:"pf-cc-card",attr:{style:"margin-bottom: 12px;"}}),_=h.createEl("div",{cls:"pf-cc-card-header"});_.createEl("span",{cls:"pf-cc-card-name",text:i("cc_module_installation")}),_.createEl("span",{cls:`pf-cc-card-badge pf-cc-card-badge--${c}`,text:i(this._ccBadgeKey(l,o))});let f=this._localizeReason(l.reason.code,"installation");if(h.createEl("div",{cls:"pf-cc-card-reason",text:f!=null?f:l.reason.text}),l.action.primary&&!u){let P=He(l),M=P.kind==="setup"?"pf-cc-card-action pf-cc-card-action--primary":"pf-cc-card-action";h.createEl("button",{cls:M,text:P.label}).addEventListener("click",()=>{P.kind==="setup"?new Re(this.app,this.plugin,()=>{this._probeModule("installation"),this._probeModule("help")}).open():this._probeModule(o)})}e.createEl("h3",{text:i("managed_runtime_status")});let g=e.createEl("div",{cls:"pf-runtime-status-card"}),E=(P,F,M)=>{let I=g.createEl("div",{cls:"pf-runtime-actions"});for(let D of P){let T=I.createEl("button",{cls:"pf-runtime-action-btn",text:D.label});M&&D.verb!=="stop"&&(T.disabled=!0),T.addEventListener("click",async()=>{var Z;if(D.verb==="stop"){let j=this._runtimeAbortController;if(!j||j.signal.aborted)return;j.abort(),new C.Notice(i("managed_runtime_action_cancelled")),this.display(),this._probeModule("installation"),this._probeModule("help");return}let w=this._ensureManagedRuntime(),L=new AbortController;this._runtimeAbortController=L,this._runtimeBusy=!0,new C.Notice(i("managed_runtime_running"));try{D.verb==="install"||D.verb==="repair"||D.verb==="update"?await w.ensure({signal:L.signal,force:D.verb==="update"||D.verb==="repair"}):D.verb==="rollback"?await w.ensure({signal:L.signal,version:(Z=F.previousVersion)!=null?Z:void 0}):await w.status(),L.signal.aborted||new C.Notice(i("managed_runtime_action_complete"))}catch(j){if((j==null?void 0:j.name)!=="AbortError"){let N=j instanceof Error?j.message:String(j);new C.Notice(i("managed_runtime_action_failed").replace("{error}",N),8e3)}}finally{this._runtimeAbortController=null,this._runtimeBusy=!1,this._probeModule("installation"),this._probeModule("help"),this.display()}})}},m=()=>{var w;g.empty();let F=this._ensureManagedRuntime().current(),M=g.createEl("div",{cls:"pf-runtime-status-header"});M.createEl("div",{cls:"pf-runtime-status-label",text:i("managed_runtime_status")});let I,D;switch(F.state){case"ready":I="ok",D=i("managed_runtime_ok_state");break;case"not_installed":I="warn",D=i("managed_runtime_not_installed");break;case"needs_repair":I="warn",D=i("managed_runtime_needs_repair");break;case"unavailable":I="error",D=i("managed_runtime_unavailable");break;default:I="unknown",D=i("managed_runtime_unknown_state")}if(M.createEl("span",{cls:`pf-runtime-status-state pf-runtime-status-state--${I}`,text:D}),F.version&&g.createEl("div",{cls:"pf-meta",text:`Python ${F.version}`}),F.pythonPath&&g.createEl("div",{cls:"pf-meta",text:F.pythonPath,attr:{style:"word-break: break-all;"}}),F.lastVerifiedAt&&g.createEl("div",{cls:"pf-meta",text:i("managed_runtime_last_verified").replace("{time}",new Date(F.lastVerifiedAt).toLocaleString())}),F.error&&g.createEl("div",{cls:"pf-runtime-error",text:`${F.error.code}: ${F.error.message}`}),F.warnings&&F.warnings.length>0)for(let L of F.warnings){let Z=g.createEl("div",{cls:"pf-runtime-warning",text:`\u26A0 ${L.message}`});L.platformAction&&Z.createEl("div",{cls:"pf-runtime-warning-action",text:L.platformAction})}(w=F.error)!=null&&w.platformAction&&g.createEl("div",{cls:"pf-runtime-error-action",text:F.error.platformAction});let T=er(F,this.plugin.manifest.version,this._runtimeBusy);E(T,F,this._runtimeBusy)};m();let b=this._ensureManagedRuntime().status();b&&b.then(()=>{e.isConnected&&m()}).catch(()=>{}),e.createEl("h3",{text:i("section_config")||"Current Configuration"});let x=this._getVaultBasePath(),k=z(x,this.plugin.settings,void 0,void 0),y=this._getPythonDesc(k.path,k.source);new C.Setting(e).setName(i("field_python_interp")||"Python Interpreter").setDesc(y).addExtraButton(P=>{P.setIcon("reset").setTooltip("Re-detect").onClick(()=>{this._pythonInterpDescEl=null,this._managedRuntime=null,this.display()})}).addButton(P=>{P.setButtonText(i("runtime_health_sync")||"Sync Runtime").onClick(()=>{this._syncRuntime(P)})});let v=e.createEl("div",{cls:"setting-item-description"});this._customPathDescEl=v,new C.Setting(e).setName(i("field_python_custom")||"Custom Python Path").setDesc(i("optional_later")).addText(P=>{P.setPlaceholder("e.g. C:\\Python311\\python.exe").setValue(this.plugin.settings.python_path||"").onChange(F=>{this.plugin.settings.python_path=F.trim(),this._debouncedSave(),this._managedRuntime=null})}).addButton(P=>{P.setButtonText(i("feat_verify")||"Validate").onClick(()=>{this._validatePythonOverride()})}),new C.Setting(e).setName(i("field_zotero_data")||"Zotero Data Dir").setDesc(i("field_zotero_placeholder")).addText(P=>{P.setPlaceholder("C:\\Users\\...\\Zotero").setValue(this.plugin.settings.zotero_data_dir||"").onChange(F=>{this.plugin.settings.zotero_data_dir=F.trim(),this._debouncedSave()})}),e.createEl("h3",{text:i("agent_integration_section")||"Agent Integration"}),this._renderSkillsList(e);try{r.focus()}catch(P){}}_renderSkillsList(e){let t={opencode:"OpenCode",claude:"Claude Code",codex:"Codex",cursor:"Cursor",windsurf:"Windsurf",github_copilot:"GitHub Copilot",gemini:"Gemini CLI"},r={opencode:".opencode/skills",claude:".claude/skills",codex:".codex/skills",cursor:".cursor/skills",windsurf:".windsurf/skills",github_copilot:".github/skills",gemini:".gemini/skills"},n=this._getVaultBasePath(),a=this.plugin.settings.agent_platform||"opencode";new C.Setting(e).setName(i("label_agent")||"Agent Platform").setDesc(i("feat_agent_platform_desc")).addDropdown(_=>{Object.entries(t).forEach(([f,g])=>_.addOption(f,g)),_.setValue(a).onChange(f=>{this.plugin.settings.agent_platform=f,this.plugin.saveSettings(),this.display()})}).addExtraButton(_=>{_.setIcon("folder").setTooltip("Open skills folder").onClick(()=>{let f=r[a]||".opencode/skills",g=ee.join(n,f);W.existsSync(g)?(0,Y.exec)(`start "" "${g}"`):new C.Notice(`Skills folder not found: ${f}`)})}),e.createEl("h3",{text:"Skills"});let s=e.createEl("div",{cls:"paperforge-desc-box"});s.setText(i("feat_skills_desc")),s.createEl("br"),s.createEl("span",{text:i("feat_skills_system")});let o=ee.join(n,r[a]),l=[],c=[];W.existsSync(o)&&W.readdirSync(o,{withFileTypes:!0}).forEach(_=>{if(!_.isDirectory())return;let f=ee.join(o,_.name,"SKILL.md");if(!W.existsSync(f))return;let g=W.readFileSync(f,"utf-8"),E=g.match(/^name:\s*(.+)$/m),m=g.split(` +`),b=m.findIndex(R=>/^description:/.test(R)),x="";if(b>=0){let R=m[b].match(/^description:\s*(.+)$/);if(R&&R[1]&&R[1]!==">"&&R[1]!=="|-"&&R[1]!=="|")x=R[1].trim();else{for(let P=b+1;P{if(f.length===0)return;let E=u.createEl("div",{cls:"paperforge-skills-group"}),m=E.createEl("div",{cls:"paperforge-skills-collapse-header"}),b=E.createEl("div",{cls:"paperforge-skills-collapse-content"}),x=m.createEl("span",{text:"\u25BC",cls:"paperforge-skills-arrow"});m.createEl("h4",{text:`${_} (${f.length})`,cls:"paperforge-skills-subheader"}),f.forEach(v=>{let S=v.name+(v.version?" v"+v.version:""),R=g?" [system]":" [user]",P=v.desc||"",F=new C.Setting(b).setName(S+R).setDesc(P);F.settingEl.style.opacity=v.disabled?"0.4":"1",F.addToggle(M=>{M.setValue(!v.disabled).onChange(I=>{let D=!I,w=v.content.match(/^disable-model-invocation:\s*(.+)$/m)?v.content.replace(/^disable-model-invocation:\s*.+$/m,`disable-model-invocation: ${D}`):v.content.replace(/^(---\r?\n)/,`$1disable-model-invocation: ${D} +`);W.writeFileSync(v.path,w,"utf-8"),v.disabled=D,v.content=w,F.settingEl.style.opacity=v.disabled?"0.4":"1"})})});let k=g?"system":"user";(this._skillsCollapsed[k]||!1)&&(b.style.display="none",x.style.transform="rotate(-90deg)"),m.addEventListener("click",()=>{b.style.display!=="none"?(b.style.display="none",x.style.transform="rotate(-90deg)"):(b.style.display="",x.style.transform="rotate(0deg)"),this._skillsCollapsed[k]=b.style.display==="none"})};h("System Skills",l,!0),h("User Skills",c,!1),l.length===0&&c.length===0&&u.createEl("p",{text:`No skills found in ${r[a]}. Run setup to deploy skills.`,cls:"setting-item-description"})}_renderModuleDetailTab(e){this._selectedDetailModule||(this._selectedDetailModule="installation"),this._selectedDetailModule==="installation"?this._renderInstallationDetail(e):this._selectedDetailModule==="library"?this._renderLibraryDetail(e):this._selectedDetailModule==="ocr"?this._renderOcrDetail(e):this._selectedDetailModule==="memory"?this._renderMemoryDetail(e):(this._selectedDetailModule="installation",this._renderInstallationDetail(e))}_renderLibraryDetail(e){this._renderModuleDetailShell(e,"library")}_renderOcrDetail(e){if(this._renderModuleDetailShell(e,"ocr"),this.plugin._ocrProcess!=null){let r=e.createEl("div",{cls:"pf-detail-controls"});r.createEl("button",{cls:"mod-warning",text:i("ocr_stop_batch")||"Stop OCR batch"}).addEventListener("click",()=>{var o;let s=this.plugin._ocrProcess;(o=s==null?void 0:s.stdin)!=null&&o.write?(s.stdin.write(`PAPERFORGE_STOP +`),this.plugin._ocrWasStopped=!0):s!=null&&s.kill&&s.kill("SIGINT")});let a=this.plugin._ocrProgress;a&&a.total>0&&r.createEl("span",{cls:"pf-detail-progress",text:`${a.current}/${a.total} papers`})}}_renderMemoryDetail(e){this._renderModuleDetailShell(e,"memory")}_dispatchModuleAction(e,t){var s,o,l;let r=(s=t.action)==null?void 0:s.primary;if(!r){this._probeModule(e);return}let n=r.verb,a=(o=r.command)!=null?o:"";if(r.destructive&&r.confirmation_required){let c=(l=r.confirmation_prompt)!=null?l:"Proceed?";if(!confirm(c))return}if((n==="setup"||n==="set_config")&&a==="paperforge setup"&&(e==="installation"||e==="library"||e==="ocr")){let c=[e];e==="installation"&&c.push("help"),new Re(this.app,this.plugin,()=>{for(let u of c)this._probeModule(u)}).open();return}if(n==="probe"&&a==="probe "+e){this._probeModule(e);return}if(e!=="installation"){if(e==="library"){if(n==="sync"&&a==="paperforge sync"){this._runManualSync();return}}else if(e==="ocr"){if(n==="run"&&a==="paperforge ocr run"){this._dispatchOcrAction("run");return}if(n==="rebuild_derived"&&a==="paperforge ocr rebuild --all"){this._dispatchOcrAction("rebuild");return}if(n==="redo"&&a==="paperforge ocr redo"){this._dispatchOcrAction("redo");return}if(n==="investigate"){if(a==="paperforge ocr doctor"){this._callPython(["ocr","doctor"],{timeout:3e4,onClose:c=>{this._probeModule("ocr"),this.display()}});return}if(a==="paperforge ocr list --json"){this._callPython(["ocr","list","--json"],{timeout:3e4,onClose:c=>{this._probeModule("ocr"),this.display()}});return}}}else if(e==="memory"){if((n==="run"||n==="rebuild_index")&&a==="paperforge memory build"){this._dispatchMemoryBuild("build");return}if(n==="rebuild_index"&&a==="paperforge embed build --force"){this._dispatchMemoryBuild("embed");return}if(n==="restore_backup"&&a==="paperforge memory restore-backup"){this._callPython(["memory","restore-backup"],{timeout:3e4,onClose:c=>{this._probeModule("memory"),this.display()}});return}}}new C.Notice((i("action_unknown_pair")||"Unknown action: {verb}").replace("{verb}",n),5e3),this._probeModule(e)}_dispatchOcrAction(e){var l;let t=this.app.vault.adapter.basePath;if(!this._resolveRuntimeCommand(t)){new C.Notice(i("runtime_not_available")||"No Python runtime available");return}let n=e==="run"?["ocr","run"]:e==="rebuild"?["ocr","rebuild","--all"]:["ocr","redo"],a={run:"Running OCR\u2026",rebuild:"Rebuilding OCR derived artifacts\u2026",redo:"Running OCR redo\u2026"},s=(l=this._capabilityState)!=null?l:{};s.ocr&&(s.ocr.activity_state="running",s.ocr.activity_label=a[e]||"Running\u2026",s.ocr.activity_progress={current:0,total:1}),this.plugin._ocrBuffer="",this.plugin._ocrProgress={current:0,total:1,key:""},this.plugin._ocrWasStopped=!1,this.display();let o=this._callPython(n,{stream:!0,onData:c=>{var f;let u=typeof c=="string"?c:Buffer.isBuffer(c)?c.toString("utf-8"):String(c),{events:h,buffer:_}=Ue(u,(f=this.plugin._ocrBuffer)!=null?f:"");this.plugin._ocrBuffer=_;for(let g of h)g.event==="START"?(this.plugin._ocrProgress&&(this.plugin._ocrProgress.total=g.total||1),s.ocr&&(s.ocr.activity_progress={current:0,total:g.total||1})):g.event==="PROGRESS"&&(this.plugin._ocrProgress={current:g.current||0,total:g.total||1,key:g.key||""},s.ocr&&(s.ocr.activity_progress={current:g.current||0,total:g.total||1}));this.display()},onError:c=>{this.plugin._ocrProcess=null,s.ocr&&(s.ocr.activity_state="idle",s.ocr.activity_label=null,s.ocr.activity_progress=null),new C.Notice("OCR error: "+(c.message||c),8e3),this._probeModule("ocr"),this.display()},onClose:c=>{this.plugin._ocrProcess=null,s.ocr&&(s.ocr.activity_state="idle",s.ocr.activity_label=null,s.ocr.activity_progress=null),c===0?new C.Notice(e==="run"?"OCR run complete.":e==="rebuild"?"OCR rebuild complete.":"OCR redo complete."):c===130||this.plugin._ocrWasStopped?(this.plugin._ocrWasStopped=!1,new C.Notice("OCR batch stopped by user.")):new C.Notice("OCR operation failed with exit code "+(c!=null?c:"?"),8e3),this._probeModule("ocr"),this.display()}});this.plugin._ocrProcess=o}_dispatchMemoryBuild(e){var s;let t=this.app.vault.adapter.basePath,r=(s=this._capabilityState)!=null?s:{};r.memory&&(r.memory.activity_state="running",r.memory.activity_label=e==="embed"?"Building vector index\u2026":"Building memory\u2026"),this.display();let n=e==="embed"?["embed","build","--force"]:["memory","build"],a=e==="embed"?"Vector index":"Memory";if(e==="embed"){this.plugin._embedBuffer="",this.plugin._embedProgress={current:0,total:0,key:""};let o=this._callPython(n,{stream:!0,onData:l=>{var _;let c=typeof l=="string"?l:Buffer.isBuffer(l)?l.toString("utf-8"):String(l),{events:u,buffer:h}=Ue(c,(_=this.plugin._embedBuffer)!=null?_:"");this.plugin._embedBuffer=h;for(let f of u)f.event==="PROGRESS"&&(this.plugin._embedProgress={current:f.current||0,total:f.total||0,key:f.key||""},r.memory&&(r.memory.activity_progress={current:f.current||0,total:f.total||1}));this.display()},onError:l=>{this.plugin._embedProcess=null,r.memory&&(r.memory.activity_state="idle",r.memory.activity_label=null,r.memory.activity_progress=null),new C.Notice(a+" build error: "+(l.message||l),8e3),this._probeModule("memory"),this.display()},onClose:l=>{this.plugin._embedProcess=null,r.memory&&(r.memory.activity_state="idle",r.memory.activity_label=null,r.memory.activity_progress=null),l===0?new C.Notice(a+" build complete."):new C.Notice(a+" build failed with exit code "+(l!=null?l:"?"),8e3),this._probeModule("memory"),this.display()}});this.plugin._embedProcess=o}else this._callPython(n,{timeout:12e4,onClose:(o,l,c)=>{r.memory&&(r.memory.activity_state="idle",r.memory.activity_label=null),o===0?new C.Notice(a+" rebuild complete"):new C.Notice(a+" build failed"+(c?": "+c.slice(0,120):""),8e3),this._probeModule("memory"),this.display()}})}_renderModuleDetailShell(e,t){var F,M,I,D;let r=t+"_detail_heading",n="pf-"+t+"-detail-heading";e.createEl("button",{cls:"pf-back-btn",text:i("btn_back_to_overview")}).addEventListener("click",()=>{this.activeTab="overview",this._selectedDetailModule="",this._focusTargetId="button.pf-open-module-btn[data-module="+t+"]",this.display()});let s=e.createEl("h2",{cls:"pf-module-detail-heading",text:i(r)||i("cc_module_"+t),attr:{id:n,tabindex:"-1"}}),o=[{id:"installation",labelKey:"md_select_installation"},{id:"library",labelKey:"md_select_library"},{id:"ocr",labelKey:"md_select_ocr"},{id:"memory",labelKey:"md_select_memory"}],l=e.createEl("div",{cls:"pf-module-detail-selector"});for(let T of o)l.createEl("button",{cls:"pf-module-detail-btn"+(T.id===t?" pf-module-detail-btn--active":""),text:i(T.labelKey)}).addEventListener("click",()=>{this._selectedDetailModule=T.id,this._focusTargetId=T.id==="installation"?"#pf-installation-detail-heading":"#pf-"+T.id+"-detail-heading",this.display()});let u=(M=((F=this._capabilityState)!=null?F:{})[t])!=null?M:Se(t),h=this._sevClass(u.severity),_=je(u),f=e.createEl("div",{cls:"pf-cc-card pf-module-detail-card"}),g=f.createEl("div",{cls:"pf-cc-card-header"});if(g.createEl("span",{cls:"pf-cc-card-name",text:i("cc_module_"+t)}),g.createEl("span",{cls:"pf-cc-card-badge pf-cc-card-badge--"+h,text:i(this._ccBadgeKey(u,t))}),u.activity_state==="running"&&u.activity_label){let T=f.createEl("div",{cls:"pf-cc-card-activity",attr:{"aria-live":"polite"}});if(T.createEl("span",{text:u.activity_label}),u.activity_progress&&u.activity_progress.total>0){let w=Math.round(u.activity_progress.current/u.activity_progress.total*100),Z=T.createEl("div",{cls:"pf-cc-card-progress",attr:{role:"progressbar","aria-valuenow":String(u.activity_progress.current),"aria-valuemin":"0","aria-valuemax":String(u.activity_progress.total)}}).createEl("div",{cls:"pf-cc-card-progress-fill"});Z.style.width=w+"%"}}let E=this._localizeReason(u.reason.code,t);f.createEl("div",{cls:"pf-cc-card-reason",text:E!=null?E:u.reason.text});let m=(I=u.action)==null?void 0:I.primary;if(m&&!_){m.destructive&&m.confirmation_required&&f.createEl("div",{cls:"pf-destructive-notice"}).createEl("span",{text:(D=m.destructive_effect)!=null?D:""});let T=u.activity_state==="running",w=He(u),L=f.createEl("button",{cls:"pf-cc-card-action pf-cc-card-action--primary",text:w.label});T&&L.setAttr("disabled","disabled"),L.addEventListener("click",()=>{T||this._dispatchModuleAction(t,u)})}let b=f.createEl("div",{cls:"pf-meta"}),x;try{x=new Date(u.updated_at).toLocaleString()}catch(T){x=u.updated_at}if(b.createEl("span",{text:i("cc_diag_updated")+": "+x+" | TTL: "+String(u.ttl_seconds)+"s"}),u.notices&&u.notices.length>0)for(let T of u.notices)e.createEl("div",{cls:"pf-notice pf-notice--"+(T.level||"info"),text:T.message});let k=f.createEl("details",{cls:"pf-cc-card-diagnostic"});k.createEl("summary",{text:i("cc_diagnostic_toggle")});let y=k.createEl("div",{cls:"pf-cc-card-diagnostic-body"}),v=i("cc_state_"+u.capability_state)||u.capability_state,S=i("cc_severity_"+u.severity)||u.severity,R=i("cc_activity_"+u.activity_state)||u.activity_state;y.createEl("div",{text:i("cc_diag_module")+": "+u.module}),y.createEl("div",{text:i("cc_diag_state")+": "+v}),y.createEl("div",{text:i("cc_diag_severity")+": "+S}),y.createEl("div",{text:i("cc_diag_activity")+": "+R});let P=y.createEl("div");P.appendText(i("cc_diag_reason")+": "+(E!=null?E:u.reason.text)+" "),P.createEl("code",{text:u.reason.code});try{s.focus()}catch(T){}}_renderHelpTab(e){var x,k;let t=(x=this._capabilityState)!=null?x:{},r="help",n=(k=t[r])!=null?k:Se(r),a=this._sevClass(n.severity),s=ke._REAL_PROBE.has(r);e.createEl("h2",{text:i("cc_module_help")||"Help & Docs"});let o=e.createEl("div",{cls:"pf-cc-card",attr:{style:"margin-bottom: 12px;"}}),l=o.createEl("div",{cls:"pf-cc-card-header"});l.createEl("span",{cls:"pf-cc-card-name",text:i("cc_module_help")}),l.createEl("span",{cls:`pf-cc-card-badge pf-cc-card-badge--${a}`,text:i(this._ccBadgeKey(n,r))});let c;if(!s)c=i("cc_reason_placeholder").replace("{module}",i("cc_module_"+r));else{let y=this._localizeReason(n.reason.code,r);c=y!=null?y:n.reason.text}if(o.createEl("div",{cls:"pf-cc-card-reason",text:c}),n.action.primary&&!je(n)){let y=He(n),S=y.kind==="setup"?"pf-cc-card-action pf-cc-card-action--primary":"pf-cc-card-action";o.createEl("button",{cls:S,text:y.label,attr:{"aria-label":y.label}}).addEventListener("click",()=>{y.kind==="setup"?new Re(this.app,this.plugin,()=>{this._probeModule("installation"),this._probeModule("help")}).open():this._probeModule(r)})}let u=o.createEl("details",{cls:"pf-cc-card-diagnostic"});u.createEl("summary",{text:i("cc_diagnostic_toggle")});let h=u.createEl("div",{cls:"pf-cc-card-diagnostic-body"}),_=i("cc_state_"+n.capability_state)||n.capability_state,f=i("cc_severity_"+n.severity)||n.severity,g=i("cc_activity_"+n.activity_state)||n.activity_state,E;try{E=new Date(n.updated_at).toLocaleString()}catch(y){E=n.updated_at}h.createEl("div",{text:`${i("cc_diag_module")}: ${n.module}`}),h.createEl("div",{text:`${i("cc_diag_state")}: ${_}`}),h.createEl("div",{text:`${i("cc_diag_severity")}: ${f}`}),h.createEl("div",{text:`${i("cc_diag_activity")}: ${g}`});let m=h.createEl("div");m.appendText(i("cc_diag_reason")+": "+c+" ");let b=m.createEl("code",{text:n.reason.code});h.createEl("div",{text:`${i("cc_diag_ttl")}: ${String(n.ttl_seconds)}s`}),h.createEl("div",{text:`${i("cc_diag_updated")}: ${E}`}),this._renderReleaseNotesTab(e)}_execMemoryStatus(e,t,r){(0,Y.exec)(`"${e}" -m paperforge --vault "${t}" memory status --json`,{encoding:"utf-8",timeout:15e3},(n,a)=>{if(n){r("Status unavailable");return}try{let s=JSON.parse(a);if(s.ok){let o=s.data,l=o.fresh?"fresh":"stale";r(`Papers: ${o.paper_count_db} | ${l}${o.needs_rebuild?" - needs rebuild":""}`)}else r("DB not found. Run paperforge memory build.")}catch(s){r("Could not parse status.")}})}_execEmbedStatus(e,t,r){(0,Y.exec)(`"${e}" -m paperforge --vault "${t}" embed status --json`,{encoding:"utf-8",timeout:15e3},(n,a)=>{if(n){r("Status unavailable");return}try{let s=JSON.parse(a);s.ok?r(`Chunks: ${s.data.chunk_count} | ${s.data.model} | ${s.data.mode}`):r("Could not parse status.")}catch(s){r("Could not parse status.")}})}_callPython(e,t){let r=this.app.vault.adapter.basePath,n=this._resolveRuntimeCommand(r);if(!n)return t&&t.onClose&&t.onClose(1,"","No python runtime available"),null;let a=[...n.args,"-m","paperforge","--vault",r,...e];if(t&&t.stream){let s=(0,Y.spawn)(n.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}return(0,Y.execFile)(n.path,a,{cwd:r,timeout:t&&t.timeout||6e4},(s,o,l)=>{t&&t.onClose&&t.onClose(s?1:0,o,l)}),null}_renderMemoryStatusText(e,t,r){e.innerHTML="",e.createEl("span",{text:t,cls:"paperforge-memory-text"}),r==="syncing"?e.createEl("span",{text:"Syncing...",cls:"paperforge-sync-status"}):r&&e.createEl("span",{text:r,cls:"paperforge-sync-status"});let n=e.createEl("button",{cls:"paperforge-rebuild-btn",text:i("feat_memory_rebuild_btn")});n.title="Rebuild memory database",n.onclick=()=>{let s=this.app.vault.adapter.basePath,o=ye(s,this.plugin.settings);if(!o.path){new C.Notice(i("feat_no_python"));return}console.log("[PaperForge] Rebuilding memory:",o.path),n.setText(i("feat_memory_rebuilding")),n.setAttr("disabled",""),this._callPython(["memory","build"],{timeout:6e4,onClose:(l,c,u)=>{console.log("[PaperForge] memory build exit:",l?"FAIL:"+l:"OK",(c||"").slice(0,200),(u||"").slice(0,200)),n.setText(i("feat_memory_rebuild_btn")),n.removeAttribute("disabled"),l===0?new C.Notice(i("feat_memory_rebuild_done")):new C.Notice(i("feat_memory_rebuild_failed")+(u?" "+u.slice(0,80):"")),this._memoryStatusText=Xe(s),this._refreshSnapshots(s)}})};let a=e.createEl("button",{cls:"paperforge-refresh-btn",text:"\u21BB"});a.title="Sync now",a.onclick=()=>{this._memoryStatusText=null,this._runManualSync()}}_getBuildCommand(e){let t=this.app.vault.adapter.basePath,r=z(t,e,void 0,void 0);return r.path?`"${r.path}" -m paperforge --vault "${t}" sync`:null}_runManualSync(){var a;let e=this.app.vault.adapter.basePath;if(!ye(e,this.plugin.settings).path)return;let r=(a=this._capabilityState)!=null?a:{};r.library&&(r.library.activity_state="running",r.library.activity_label="Syncing library\u2026");let n=document.querySelector(".paperforge-memory-status");n&&this._renderMemoryStatusText(n,"Checking...","syncing"),this.plugin._autoSyncRunning=!0,this._libraryRunning=!0,this.display(),this._callPython(["sync"],{timeout:12e4,onClose:s=>{this.plugin._autoSyncRunning=!1,this._libraryRunning=!1,this._memoryStatusText=null,r.library&&(r.library.activity_state="idle",r.library.activity_label=null),s===0&&(this._lastSyncTime=new Date().toLocaleTimeString(),this.plugin._lastSyncTime=this._lastSyncTime),this._probeModule("library",s!=null?s:1),this.display(),this._refreshSnapshots(e),Qe(this.app,this.plugin,e)}})}_refreshSnapshots(e){let t=ye(e,this.plugin.settings),r=[...t.extraArgs,"-m","paperforge","--vault",e,"runtime-health","--json"];this._refreshPending=!0,(0,Y.execFile)(t.path,r,{cwd:e,timeout:3e4,windowsHide:!0},(n,a,s)=>{this._refreshPending=!1,this._memoryStatusText=Xe(e),this._embedStatusText=De(e),this.display()})}_renderVectorSection(e){var l;if(e.createEl("h4",{text:"Vector Database"}),this.plugin.settings.features||(this.plugin.settings.features={memory_layer:!0,vector_db:!1}),e.createEl("div",{cls:"paperforge-desc-box"}).setText(i("feat_vector_desc")),new C.Setting(e).setName(i("feat_vector_enable")).setDesc(i("feat_vector_enable_desc")).addToggle(c=>{c.setValue(!!this.plugin.settings.features.vector_db).onChange(u=>{this.plugin.settings.features.vector_db=u,this.plugin.saveSettings(),this._vectorDepsOk=null,this._embedStatusText=null,this.display()})}),!this.plugin.settings.features.vector_db)return;let r=this.app.vault.adapter.basePath,n=e.createEl("div",{cls:"paperforge-vec-header"}),a=n.createEl("span",{text:"\u25BC",cls:"paperforge-skills-arrow"});n.createEl("span",{cls:"paperforge-vec-header-label",text:i("feat_vector_config_label")});let s=e.createEl("div",{cls:"paperforge-vector-config"}),o=c=>{s.style.display=c?"none":"",a.style.transform=c?"rotate(-90deg)":"rotate(0deg)"};if(o(Ct(this._featurePanelsCollapsed,"vectorConfig",!1)),n.addEventListener("click",()=>{let c=rr(this._featurePanelsCollapsed,"vectorConfig",!1);o(c)}),this._vectorDepsOk===!0){this._renderVectorReady(s,r);return}if(this._vectorDepsOk===!1){this._renderVectorNoDeps(s);return}if(this._vectorDepsOk===null){let c=Ye(r);this._vectorDepsOk=c&&(l=c.deps_installed)!=null?l:!1,this._vectorDepsOk&&(this._embedStatusText=De(r)),this.display()}}_renderApiConfig(e){new C.Setting(e).setName(i("feat_openai_key")).setDesc(i("feat_openai_key_desc")).addText(t=>{t.setPlaceholder("sk-...").setValue(this.plugin.settings.vector_db_api_key||"").onChange(r=>{this.plugin.settings.vector_db_api_key=r,this.plugin.saveSettings()})}),new C.Setting(e).setName(i("feat_api_base_url")).setDesc(i("feat_api_base_url_desc")).addText(t=>{t.setPlaceholder("https://api.openai.com/v1").setValue(this.plugin.settings.vector_db_api_base||"").onChange(r=>{this.plugin.settings.vector_db_api_base=r,this.plugin.saveSettings()})}),new C.Setting(e).setName(i("feat_api_model")).setDesc(i("feat_api_model_desc")).addText(t=>{t.setPlaceholder("text-embedding-3-small").setValue(this.plugin.settings.vector_db_api_model||"text-embedding-3-small").onChange(r=>{this.plugin.settings.vector_db_api_model=r,this.plugin.saveSettings()})})}_renderVectorNoDeps(e){e.createEl("div",{cls:"paperforge-desc-box"}).setText(i("feat_deps_missing")),new C.Setting(e).setName(i("feat_install_deps")).setDesc(i("feat_install_deps_desc")).addButton(r=>{r.setButtonText(i("feat_install_btn")).setCta().onClick(async()=>{let n=this.app.vault.adapter.basePath,a=ye(n,this.plugin.settings);if(!a.path){new C.Notice(i("feat_no_python"));return}r.setButtonText(i("feat_installing")),r.setDisabled(!0);let s="chromadb openai",o=new C.Notice(i("feat_installing_pkgs").replace("{pkgs}",s),0);try{let l=Object.assign({},process.env,{PYTHONIOENCODING:"utf-8",PYTHONUTF8:"1"}),c=s.split(" ");await new Promise((u,h)=>{(0,Y.execFile)(a.path,[...a.extraArgs,"-m","pip","install",...c],{cwd:n,timeout:3e5,env:l,windowsHide:!0},_=>{_?h(_):u()})}),o.hide(),new C.Notice(i("feat_install_done")),this._vectorDepsOk=!0,this._embedStatusText=De(n),this.display()}catch(l){o.hide(),new C.Notice(i("feat_install_failed")+(l.stderr||l.message||l)),r.setButtonText(i("feat_retry_btn")),r.setDisabled(!1)}})})}_renderVectorReady(e,t){e.createEl("div",{cls:"paperforge-desc-box"}).setText(De(t)),this._renderApiConfig(e);let n=e.createEl("div",{cls:"paperforge-embed-section"});n.createEl("div",{cls:"paperforge-embed-header"}).createEl("span",{text:i("retrieval_rebuild_vectors"),cls:"setting-item-name"});let s=n.createEl("div",{cls:"paperforge-embed-controls"}),o=n.createEl("div",{cls:"paperforge-embed-status-text",attr:{"aria-live":"polite"}});(()=>{s.empty(),o.empty();let c=Ye(t),u=c==null?void 0:c.build_state,h=u&&typeof u=="object"&&!Array.isArray(u)?u:{};this.plugin._embedProgress=this.plugin._embedProgress||{current:0,total:0,key:""},!this.plugin._embedProcess&&h.status==="running"&&(this.plugin._embedProgress={current:typeof h.current=="number"?h.current:0,total:typeof h.total=="number"?h.total:1,key:typeof h.paper_id=="string"?h.paper_id:""});let{current:_,total:f,key:g}=this.plugin._embedProgress,E=typeof(c==null?void 0:c.body_chunk_count)=="number"?c.body_chunk_count:0,m=typeof(c==null?void 0:c.object_chunk_count)=="number"?c.object_chunk_count:0,x=(typeof(c==null?void 0:c.chunk_count)=="number"?c.chunk_count:0)+E+m,k=x>0,y=c!==null&&typeof c.corrupted=="boolean"&&c.corrupted,v=!!this.plugin._embedProcess,S=!this.plugin._embedProcess&&h.status==="running",R=(c==null?void 0:c.deps_installed)!==void 0?!!c.deps_installed:!0,P=typeof h.status=="string"?h.status:"",F=typeof h.message=="string"?h.message:"",M=w=>{var j;if(w==="--resume"&&k&&!y){let N=i("retrieval_rebuild_warning").replace("{n}",String(x));if(!confirm(N))return}if(w==="--force"&&k&&!y){let N="Force rebuild will replace "+x+" existing chunk(s). Continue?";if(!confirm(N))return}if(!ye(t,this.plugin.settings).path){new C.Notice(i("retrieval_no_python"));return}let Z=Object.assign({},process.env,{PYTHONIOENCODING:"utf-8",PYTHONUTF8:"1",VECTOR_DB_API_KEY:this.plugin.settings.vector_db_api_key||"",VECTOR_DB_API_BASE:this.plugin.settings.vector_db_api_base||"",VECTOR_DB_API_MODEL:this.plugin.settings.vector_db_api_model||""});this.plugin._embedStderr="",this.plugin._embedProgress={current:0,total:0,key:""},this.plugin._embedProcess=this._callPython(["embed","build",w],{stream:!0,env:Z,onData:N=>{var ae;let oe=typeof N=="string"?N:Buffer.isBuffer(N)?N.toString("utf-8"):String(N),{events:se,buffer:de}=Ue(oe,(ae=this.plugin._embedBuffer)!=null?ae:"");this.plugin._embedBuffer=de;for(let J of se)J.event==="START"?this.plugin._embedProgress.total=J.total||0:J.event==="PROGRESS"?(this.plugin._embedProgress.current=J.current||0,this.plugin._embedProgress.key=J.key||""):J.event==="DONE"&&(this.plugin._embedProcess=null,this.plugin._embedProgress.current=this.plugin._embedProgress.total);this.display()},onStderr:N=>{this.plugin._embedStderr||(this.plugin._embedStderr=""),this.plugin._embedStderr+=String(N)},onError:N=>{this.plugin._embedProcess=null,new C.Notice(i("feat_build_failed")+": "+(N.message||N)),this.display()},onClose:N=>{var oe;if(clearInterval((oe=this.plugin._embedPollInterval)!=null?oe:void 0),this.plugin._embedPollInterval=null,this.plugin._embedProcess=null,N===0)this.plugin._embedProgress.current=this.plugin._embedProgress.total,this.plugin.saveSettings(),this._embedStatusText=De(t),new C.Notice(i("feat_build_complete"));else{this._embedStatusText=null;let se=(this.plugin._embedStderr||"").slice(0,200);new C.Notice(i("feat_build_failed")+(se?": "+se:""),8e3)}this.plugin._embedStderr="",this.display(),this._refreshSnapshots(t)}}),clearInterval((j=this.plugin._embedPollInterval)!=null?j:void 0),this.plugin._embedPollInterval=setInterval(()=>{this.plugin._embedPolling||(this.plugin._embedPolling=!0,this._callPython(["embed","status","--json"],{timeout:5e3,onClose:(N,oe)=>{var se;if(this.plugin._embedPolling=!1,N===0&&oe)try{let ae=JSON.parse(oe).data;if(ae&&ae.build_state){let J=ae.build_state;(J.status==="stopping"||J.status==="idle")&&this.plugin._embedProcess&&(this.plugin._embedProcess=null,clearInterval((se=this.plugin._embedPollInterval)!=null?se:void 0),this.plugin._embedPollInterval=null,this.display()),J.current!==void 0&&J.total!==void 0&&(this.plugin._embedProgress.current=J.current,this.plugin._embedProgress.total=J.total||1,this.plugin._embedProgress.key=J.paper_id||"")}}catch(de){}}}))},2e3),this.display()},I=ze(t),D=!1;I&&typeof I.summary=="object"&&I.summary!==null&&"status"in I.summary&&(D=I.summary.status==="version_mismatch");let T;switch(R?D?T="runtime-mismatch":P==="stopping"?T="stopping":v&&P==="running"?T="building":P==="failed"?T="failed":P==="stopped"?T="stopped":S?T="stale":y?T="corrupted":k?T="ready":T="idle":T="deps-missing",T){case"building":{let w=s.createEl("div",{cls:"paperforge-progress-track"});w.style.cssText="flex:1;";let L=f>0?(_/f*100).toFixed(1):"0",Z=w.createEl("div",{cls:"paperforge-progress-seg done"});if(Z.style.cssText=`width:${L}%; min-width:${_>0?"2px":"0"};`,_{this._callPython(["embed","stop","--json"],{timeout:8e3}),this.display()});break}case"stopping":{let w=s.createEl("div",{cls:"paperforge-progress-track"});w.style.cssText="flex:1; opacity:0.5;";let L=f>0?(_/f*100).toFixed(1):"0",Z=w.createEl("div",{cls:"paperforge-progress-seg done"});if(Z.style.cssText=`width:${L}%; min-width:${_>0?"2px":"0"};`,_M("--resume"));let L=s.createEl("button");L.setText(i("retrieval_force_rebuild")),L.style.marginLeft="6px",L.addEventListener("click",()=>M("--force"));break}case"stopped":{o.setText(i("retrieval_build_stopped"));let w=s.createEl("button");w.setText(i("retrieval_retry")),w.className="mod-cta",w.addEventListener("click",()=>M("--resume"));break}case"corrupted":{o.createEl("div",{cls:"paperforge-desc-box",text:i("feat_vector_corrupted"),attr:{style:"background:var(--background-modifier-warning);"}});let w=s.createEl("button");w.setText(i("retrieval_force_rebuild")),w.className="mod-cta",w.addEventListener("click",()=>M("--force"));break}case"stale":{o.createEl("div",{cls:"paperforge-desc-box",text:i("retrieval_build_stale"),attr:{style:"color:var(--text-warning);"}});let w=s.createEl("button");w.setText(i("retrieval_rebuild_vectors")),w.className="mod-cta",w.addEventListener("click",()=>M("--resume"));break}case"ready":{s.createEl("span",{text:x+" chunks embedded",cls:"setting-item-description"});let w=s.createEl("button");w.setText(i("retrieval_rebuild_vectors")),w.className="mod-cta",w.addEventListener("click",()=>M("--resume"));let L=s.createEl("button");L.setText(i("retrieval_force_rebuild")),L.style.marginLeft="6px",L.addEventListener("click",()=>M("--force"));break}case"deps-missing":{o.setText(i("retrieval_build_deps_missing"));let w=s.createEl("a");w.setText(i("feat_install_deps")),w.style.cssText="cursor:pointer; text-decoration:underline;",w.addEventListener("click",()=>{this.display()});break}case"runtime-mismatch":{o.createEl("div",{cls:"paperforge-desc-box",text:i("retrieval_build_runtime_mismatch"),attr:{style:"color:var(--text-warning);"}});let w=s.createEl("a");w.setText(i("runtime_health_sync")),w.style.cssText="cursor:pointer; text-decoration:underline;",w.addEventListener("click",()=>{this.display()});break}case"idle":default:{o.setText(i("retrieval_build_idle"));let w=s.createEl("button");w.setText(i("feat_build_btn")),w.className="mod-cta",w.addEventListener("click",()=>M("--resume"));break}}})()}_getCurrentModelKey(){return this.plugin.settings.vector_db_api_model||"text-embedding-3-small"}_parseEmbedStatus(e){let t={};return e&&(e.split(` +`).forEach(r=>{let n=r.match(/^\s*([^:]+):\s*(.*)/);n&&(t[n[1].trim()]=n[2].trim())}),t.db_exists!==void 0&&(t.db_exists=t.db_exists==="True"),t.chunk_count!==void 0&&(t.chunk_count=parseInt(t.chunk_count,10)||0)),t}_getPythonDesc(e,t){return t==="stale"?`[!!] ${e} (stale \u2014 path no longer exists, update or clear the override below)`:t==="manual"?`${e} (manual)`:`${e} (auto-detected)`}_refreshPythonInterpDesc(e,t){let r=this._pythonInterpDescEl;r&&(t==="stale"?r.textContent=`[!!] ${e} (stale \u2014 path no longer exists, update or clear the override below)`:t==="manual"?r.textContent=`${e} (manual)`:r.textContent=`${e} (auto-detected)`)}_validatePythonOverride(){let e=this.plugin.settings.python_path?this.plugin.settings.python_path.trim():"",t=this._customPathDescEl;if(!e){let r="\u8BF7\u8F93\u5165\u8DEF\u5F84 / Enter a path first";t&&(t.innerHTML=`\u2717 ${r}`),new C.Notice(r);return}if(!W.existsSync(e)){let r="\u8DEF\u5F84\u4E0D\u5B58\u5728 / Path does not exist";t&&(t.innerHTML=`\u2717 ${r}`),new C.Notice(r,4e3);return}try{W.accessSync(e,W.constants.X_OK)}catch(r){let n="\u4E0D\u53EF\u6267\u884C / Not executable";t&&(t.innerHTML=`\u2717 ${n}`),new C.Notice(n,4e3);return}(0,Y.execFile)(e,["--version"],{timeout:8e3},(r,n)=>{if(r||!n){let l="\u65E0\u6CD5\u8FD0\u884C / Cannot run";t&&(t.innerHTML=`\u2717 ${l}`),new C.Notice(l,4e3);return}let a=n.match(/Python (\d+)\.(\d+)/);if(!a){let l="\u65E0\u6CD5\u89E3\u6790\u7248\u672C / Cannot parse version";t&&(t.innerHTML=`\u2717 ${l}`),new C.Notice(l,4e3);return}let s=parseInt(a[1],10),o=parseInt(a[2],10);if(s<3||s===3&&o<10){let l="Python \u7248\u672C\u8FC7\u4F4E\uFF0C\u9700\u8981 3.10+ / Python version too low, need 3.10+";t&&(t.innerHTML=`\u2717 ${l}`),new C.Notice(l,4e3);return}(0,Y.execFile)(e,["-m","pip","--version"],{timeout:8e3},l=>{if(l){let c=`\u2713 Python ${s}.${o} \u6709\u6548\uFF0C\u4F46\u672A\u68C0\u6D4B\u5230 pip / Valid, but pip not found`;t&&(t.innerHTML=`\u26A0 ${c}`),new C.Notice(c,4e3)}else{let c=`\u2713 Python ${s}.${o} \u6709\u6548 / Valid`;t&&(t.innerHTML=`${c}`),new C.Notice(c,4e3)}})})}_syncRuntime(e){let t=this.app.vault.adapter.basePath,{path:r,extraArgs:n=[]}=z(t,this.plugin.settings,void 0,void 0),a=this.plugin.manifest.version,s=Nt(r,a,n);e.setDisabled(!0),e.setButtonText(i("runtime_health_syncing"));let o=(c,u)=>(console.log(`[PaperForge] Sync Runtime: trying ${u}`),Vt(s.cmd,c,t,s.timeout,void 0,Ce())),l=()=>{let c="opencode";try{let f=W.readFileSync(ee.join(t,"paperforge.json"),"utf-8"),g=JSON.parse(f);g.agent_key&&(c=g.agent_key)}catch(f){}let u=[...n,"-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+'", overwrite=True); print("skills deployed" if r["skill_deployed"] else "skills skipped", flush=True)'],h=(0,Y.spawn)(r,u,{cwd:t,timeout:3e4,windowsHide:!0}),_="";h.stdout.on("data",f=>{_+=f.toString("utf-8")}),h.on("close",f=>{console.log(`[PaperForge] Skill deploy: ${_.trim()} (exit ${f})`)})};o(s.pypiArgs,"PyPI").then(c=>{if(c.exitCode===0){console.log("[PaperForge] Sync Runtime: installed via PyPI"),l(),new C.Notice(i("runtime_health_sync_done").replace("{0}",a),5e3),this.display();return}console.warn("[PaperForge] Sync Runtime: PyPI failed, falling back to git..."),o(s.gitArgs,"git").then(u=>{u.exitCode===0?(console.log("[PaperForge] Sync Runtime: installed via git"),l(),new C.Notice(i("runtime_health_sync_done").replace("{0}",a),5e3),this.display()):(e.setDisabled(!1),e.setButtonText(i("runtime_health_sync")),console.error("[PaperForge] git fallback stderr:",u.stderr),new C.Notice(i("runtime_health_sync_fail").replace("{0}","pip exit code "+u.exitCode),8e3))})})}_debouncedSave(){clearTimeout(this._saveTimeout),this._saveTimeout=setTimeout(()=>this.plugin.saveSettings(),500)}_preCheck(e){var a;let t=this.app.vault.adapter.basePath,{path:r,extraArgs:n=[]}=z(t,(a=this.plugin)==null?void 0:a.settings,void 0,void 0);(0,Y.execFile)(r,[...n,"--version"],{timeout:8e3},(s,o)=>{let l=[];l.push({label:"Python",ok:!s,detail:s?i("check_python_fail"):o.trim()});let c=!1,u=process.env.HOME||process.env.USERPROFILE||nr.homedir()||"";if(process.platform==="darwin")c=["/Applications/Zotero.app",ee.join(u,"Applications","Zotero.app")].some(b=>{try{return W.existsSync(b)}catch(x){return!1}});else if(process.platform==="win32"){let m=process.env.ProgramFiles||"",b=process.env.LOCALAPPDATA||"";c=[ee.join(m,"Zotero"),ee.join(m,"(x86)","Zotero"),ee.join(b,"Programs","Zotero"),ee.join(b,"Zotero"),ee.join(u,"AppData","Local","Programs","Zotero")].filter(Boolean).some(k=>{try{return W.existsSync(k)}catch(y){return!1}})}else c=[ee.join(u,".local","share","zotero","zotero"),"/usr/bin/zotero","/usr/local/bin/zotero"].some(b=>{try{return W.existsSync(b)}catch(x){return!1}});let h=this.plugin.settings.zotero_data_dir;if(!c&&h)try{c=W.existsSync(h)}catch(m){}l.push({label:"Zotero",ok:c,detail:c?i("check_zotero_ok"):i("check_zotero_fail")});let _=!1,f=process.env.APPDATA||"";process.platform==="win32"&&f&&(_=Ge(ee.join(f,"Zotero","Zotero","Profiles"))),!_&&process.platform==="darwin"&&u&&(_=Ge(ee.join(u,"Library","Application Support","Zotero","Profiles"))),!_&&process.platform!=="win32"&&process.platform!=="darwin"&&u&&(_=Ge(ee.join(u,".zotero","zotero","Profiles"))),!_&&h&&String(h).trim()&&(_=vt(h.trim())),!_&&u&&(_=vt(ee.join(u,"Zotero"))),l.push({label:"Better BibTeX",ok:_,detail:_?i("check_bbt_ok"):i("check_bbt_fail")});let g={true:"\u2713",false:"\u2717"};if(this._checkEl){this._checkEl.setText(l.map(b=>`${g[String(b.ok)]} ${b.label}: ${b.detail}`).join(` +`));let m=l.some(b=>!b.ok);this._checkEl.className=`paperforge-message msg-${m?"error":"ok"}`}let E=l.filter(m=>!m.ok);E.length>0&&new C.Notice(`[!!] \u672A\u901A\u8FC7: ${E.map(m=>m.label).join(", ")}`,6e3),e()})}_renderMaintenanceTab(e){var u;e.createEl("h2",{text:i("tab_maintenance")||"\u7EF4\u62A4"});let r=(u=this.app.vault.adapter.basePath)!=null?u:"",n=e.createEl("div"),a={active:"all"},s=null;try{s=tt(r)}catch(h){}let o=z(r,this.plugin.settings,W,Y.execFileSync);if(!o.path){n.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 l=()=>!!this.plugin._ocrProcess,c=h=>{n.empty();let _=h,f=n.createEl("div",{cls:"pf-maint-filters"}),g=f.createEl("button",{cls:"pf-maint-filter"+(a.active==="all"?" active":""),text:i("maintenance_filter_all")||"All"});g.addEventListener("click",()=>{a.active="all",c(h)});let E=f.createEl("button",{cls:"pf-maint-filter"+(a.active==="recommended"?" active":""),text:i("maintenance_filter_recommended")||"Recommended"});E.addEventListener("click",()=>{a.active="recommended",c(h)});let m=a.active==="recommended"?_.filter(b=>b.needs_derived_rebuild===!0):_;if(m.length===0)n.createEl("p",{text:"\u5F53\u524D\u7B5B\u9009\u6761\u4EF6\u4E0B\u65E0\u6570\u636E",cls:"setting-item-description"});else{let b=o.path,x=o.extraArgs||[],k=n.createEl("div",{cls:"pf-maint-progress"});k.style.display="none";let y=k.createEl("div",{cls:"paperforge-progress-track"});y.style.cssText="flex:1;";let v=y.createEl("div",{cls:"paperforge-progress-seg done"}),S=y.createEl("div",{cls:"paperforge-progress-seg pending"}),R=k.createEl("span",{cls:"pf-maint-progress-text"}),P=k.createEl("span",{cls:"pf-maint-progress-key"}),F=k.createEl("button",{text:i("maintenance_stop")||"Stop"});F.className="mod-warning",F.addEventListener("click",()=>{let A=this.plugin._ocrProcess;A&&(A.stdin&&typeof A.stdin.write=="function"?A.stdin.write(`PAPERFORGE_STOP +`):typeof A.kill=="function"&&A.kill("SIGINT")),this.plugin._ocrWasStopped=!0,F.disabled=!0,F.textContent=(i("maintenance_stop")||"Stop")+"\u2026"});let M=()=>{let A=this.plugin._ocrProgress;if(!A||A.total===0||!this.plugin._ocrProcess){k.style.display="none";return}k.style.display="flex";let Q=A.total>0?(A.current/A.total*100).toFixed(1):"0";v.style.width=`${Q}%`,v.style.minWidth=A.current>0?"2px":"0",A.current{let Q=document.createElement("th");Q.textContent=A,Z.appendChild(Q)});let j=l();for(let A of m){let Q=L.insertRow(),O=Q.insertCell();O.style.cssText="padding:3px 4px;text-align:center;width:24px;";let V=document.createElement("input");V.type="checkbox",V.className="pf-maint-sel",V.checked=I.get(A.key)||!1,V.addEventListener("change",()=>{I.set(A.key,V.checked),se()}),O.appendChild(V);let te=Q.insertCell();te.style.cssText="padding:3px 4px;";let B=te.createEl("div",{cls:"pf-maint-paper-info"});B.createEl("div",{cls:"pf-maint-paper-title",text:A.title||A.key}),B.createEl("div",{cls:"pf-maint-paper-key",text:A.key});let re=Q.insertCell();re.style.cssText="padding:3px 4px;",re.createEl("div",{cls:"pf-maint-reason",text:A.display_reason||""});let ue=Q.insertCell();ue.style.cssText="padding:3px 4px;white-space:nowrap;";let Ee=ue.createEl("div",{cls:"pf-maint-actions"}),Le=kt(A);if(Le==="rebuild"){let le=Ee.createEl("button",{cls:"pf-maint-action-btn rebuild",text:i("maintenance_btn_rebuild")||"Rebuild"});j&&(le.disabled=!0),le.addEventListener("click",()=>{(0,Y.execFile)(b,[...x,"-m","paperforge","ocr","rebuild",A.key],{cwd:r,timeout:12e4,windowsHide:!0},()=>{new C.Notice((i("maintenance_btn_rebuild")||"Rebuild")+" \u2014 "+A.key)})})}else if(Le==="redo"){let le=Ee.createEl("button",{cls:"pf-maint-action-btn redo",text:i("ocr_maint_redo_btn")||"Redo"});j&&(le.disabled=!0),le.addEventListener("click",()=>{wt("redo")&&!confirm((i("ocr_maint_redo_confirm")||"Rerun OCR for {n} paper(s)? Existing derived OCR artifacts will be replaced.").replace("{n}","1"))||(0,Y.execFile)(b,[...x,"-m","paperforge","ocr","redo",A.key],{cwd:r,timeout:3e5,windowsHide:!0},()=>{new C.Notice((i("ocr_maint_redo_btn")||"Redo OCR")+" \u2014 "+A.key)})})}}let N=n.createEl("div",{cls:"pf-maint-batch-bar"}),oe=N.createEl("span",{cls:"pf-maint-batch-label",text:"0 selected"}),se=()=>{let A=m.filter(Q=>I.get(Q.key)).length;oe.textContent=A+" selected"},de=N.createEl("button",{cls:"mod-cta",text:i("maintenance_batch_rebuild")||"\u25B6 Rebuild selected"});de.disabled=j;let ae=N.createEl("button",{cls:"mod-cta",text:i("maintenance_batch_redo")||"\u25B6 Full OCR redo selected"});ae.disabled=j;let J=A=>{let Q=m.filter(B=>I.get(B.key)&&kt(B)===A);if(Q.length===0){let B=A==="rebuild"?i("maintenance_btn_rebuild")||"Rebuild":i("ocr_maint_redo_btn")||"Redo";new C.Notice("Selected papers are not eligible for "+B+". Uncheck ineligible rows and try again.",6e3);return}if(wt(A)&&!confirm((i("ocr_maint_redo_confirm")||"Rerun OCR for {n} paper(s)? Existing derived OCR artifacts will be replaced.").replace("{n}",String(Q.length))))return;let O=Q.map(B=>B.key);this.plugin._ocrProgress={current:0,total:O.length,key:""},this.plugin._ocrBuffer="",this.plugin._ocrWasStopped=!1;let V=A==="rebuild"?"OCR_REBUILD":"OCR_REDO";de.disabled=!0,ae.disabled=!0,Array.from(D.querySelectorAll(".pf-maint-action-btn")).forEach(B=>{B.disabled=!0}),Array.from(D.querySelectorAll(".pf-maint-sel")).forEach(B=>{B.disabled=!0}),g.disabled=!0,E.disabled=!0,F.disabled=!1,F.textContent=i("maintenance_stop")||"Stop";let te=this._callPython(["ocr",A,...O],{stream:!0,onData:B=>{var Le;let re=typeof B=="string"?B:Buffer.isBuffer(B)?B.toString("utf-8"):String(B),{events:ue,buffer:Ee}=Ue(re,(Le=this.plugin._ocrBuffer)!=null?Le:"");this.plugin._ocrBuffer=Ee;for(let le of ue)le.event==="START"?this.plugin._ocrProgress&&(this.plugin._ocrProgress.total=le.total||O.length):le.event==="PROGRESS"&&(this.plugin._ocrProgress={current:le.current||0,total:le.total||O.length,key:le.key||""});M()},onError:B=>{this.plugin._ocrProcess=null,new C.Notice("Batch error: "+(B.message||B)),c(h)},onClose:B=>{this.plugin._ocrWasStopped||B===130?(this.plugin._ocrWasStopped=!1,this.plugin._ocrProcess=null,M(),new C.Notice("OCR batch stopped by user.")):B===0?(this.plugin._ocrProgress&&(this.plugin._ocrProgress.current=this.plugin._ocrProgress.total),this.plugin._ocrProcess=null,M(),new C.Notice((i("maintenance_batch_complete")||"Batch operation complete \u2014 {n} papers processed.").replace("{n}",String(O.length)))):(this.plugin._ocrProcess=null,M(),new C.Notice("Batch operation finished with exit code "+B+".",8e3)),Pt(r,b,x,s).then(re=>{s=tt(r),c(re.data)}).catch(()=>{c(_)})}});this.plugin._ocrProcess=te,M()};de.addEventListener("click",()=>J("rebuild")),ae.addEventListener("click",()=>J("redo")),se()}};if(s){let h=Object.values(s.papers);c(h)}else n.createEl("p",{text:"\u6B63\u5728\u52A0\u8F7D OCR \u7EF4\u62A4\u6570\u636E\u2026"});Pt(r,o.path,o.extraArgs||[],s||null).then(h=>{s=tt(r),(h.changed||!s)&&c(h.data)}).catch(()=>{s||(n.empty(),n.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"}))})}_renderReleaseNotesTab(e){e.createEl("h2",{text:"\u66F4\u65B0\u4E0E\u624B\u518C"}),e.createEl("h3",{text:"\u7248\u672C\u66F4\u65B0\u8BB0\u5F55"});let t=sr.default.versions||[];for(let a of t){let s=e.createEl("div",{cls:"paperforge-release-card"}),o=s.createEl("div",{cls:"paperforge-release-header"});if(o.createEl("strong",{text:`v${a.version} \u2014 ${a.title}`}),o.createEl("span",{cls:"paperforge-release-date",text:` (${a.date})`}),a.breaking_or_migration&&a.breaking_or_migration.length>0){let l=s.createEl("div",{cls:"paperforge-release-section"});l.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",{cls:"paperforge-release-item",text:`\u2022 ${c}`})}if(a.new_features&&a.new_features.length>0){let l=s.createEl("div",{cls:"paperforge-release-section"});l.createEl("div",{cls:"paperforge-release-label",text:"\u65B0\u529F\u80FD"});for(let c of a.new_features)l.createEl("div",{cls:"paperforge-release-item",text:`\u2022 ${c}`})}if(a.fixes&&a.fixes.length>0){let l=s.createEl("div",{cls:"paperforge-release-section"});l.createEl("div",{cls:"paperforge-release-label",text:"\u4FEE\u590D"});for(let c of a.fixes)l.createEl("div",{cls:"paperforge-release-item",text:`\u2022 ${c}`})}if(a.recommended_actions&&a.recommended_actions.length>0){let l=s.createEl("div",{cls:"paperforge-release-section paperforge-release-recommended"});l.createEl("div",{cls:"paperforge-release-label",text:"\u5EFA\u8BAE\u64CD\u4F5C"});for(let c of a.recommended_actions)l.createEl("div",{cls:"paperforge-release-item paperforge-release-item-bold",text:`\u2022 ${c}`})}}e.createEl("h3",{text:"\u4F7F\u7528\u624B\u518C"}),e.createEl("div",{cls:"paperforge-manual-links"}).createEl("a",{text:"\u2192 \u67E5\u770B\u5B8C\u6574\u4F7F\u7528\u624B\u518C\uFF08GitHub\uFF09",href:"https://github.com/LLLin000/PaperForge/blob/master/docs/user-manual.md"}).setAttr("target","_blank")}_initCapabilityState(){let e=this.plugin.settings.capabilityState;this._capabilityState=Ot(e!=null?e:{},Fe),this._persistCapabilityState()}_persistCapabilityState(){this._capabilityState&&(this.plugin.settings.capabilityState=this._capabilityState,this.plugin.saveSettings())}_probeModule(e,t){var l,c,u,h;if(this._probing.has(e))return;this._probing.add(e);let r=(l=this._capabilityState)==null?void 0:l[e],n={schema_version:1,module:e,capability_state:(c=r==null?void 0:r.capability_state)!=null?c:"unknown",activity_state:"running",activity_label:"Probing...",activity_progress:null,severity:"unknown",reason:{code:`${e}.probing`,text:`Checking ${e} status...`},action:{primary:$e(e)},notices:(u=r==null?void 0:r.notices)!=null?u:[],updated_at:new Date().toISOString(),ttl_seconds:(h=r==null?void 0:r.ttl_seconds)!=null?h:0};this._updateCapabilityEnvelope(e,n);let a=this.app.vault.adapter.basePath,s=this._resolveRuntimeCommand(a);if(!s){if(this._probing.delete(e),e==="installation"){let _={schema_version:1,module:"installation",capability_state:"unknown",activity_state:"idle",activity_label:null,activity_progress:null,severity:"error",reason:{code:"installation.no_python",text:"No Python found. Run the Setup Wizard to install the managed runtime."},action:{primary:Dt()},notices:[],updated_at:new Date().toISOString(),ttl_seconds:60};this._updateCapabilityEnvelope(e,_)}else this._updateCapabilityEnvelope(e,Te(e));return}let o=[...s.args,"-m","paperforge","--vault",a,"probe",e,"--json"];e==="library"&&t!=null&&t!==0&&o.push("--last-operation-exit-code",String(t)),(0,Y.execFile)(s.path,o,{cwd:a,timeout:15e3},(_,f,g)=>{if(this._probing.delete(e),_){console.warn(`[PaperForge] Probe ${e} failed:`,_.message),this._updateCapabilityEnvelope(e,Te(e));return}try{let E=JSON.parse(f);pt(E,e)?this._updateCapabilityEnvelope(e,E):(console.warn(`[PaperForge] Probe ${e}: invalid envelope schema`,f==null?void 0:f.slice(0,200)),this._updateCapabilityEnvelope(e,Te(e)))}catch(E){console.warn(`[PaperForge] Probe ${e}: unparseable JSON`,f==null?void 0:f.slice(0,200)),this._updateCapabilityEnvelope(e,Te(e))}})}_updateCapabilityEnvelope(e,t){this._capabilityState||(this._capabilityState={});let r=this._capabilityState[t.module];this._capabilityState[t.module]=t,this._persistCapabilityState(),(r==null?void 0:r.activity_state)==="running"&&t.activity_state!=="running"&&new C.Notice(i("cc_notice_refreshed"),3e3),this.display()}_ccBadgeKey(e,t){return e.severity==="ok"?"cc_badge_ok":e.severity==="error"&&t==="installation"?"cc_badge_setup":e.severity==="warning"||e.severity==="error"?"cc_badge_attention":"cc_badge_pending"}_sevClass(e){return e==="error"?"error":e==="warning"?"warn":e==="unknown"?"unknown":"ok"}_localizeReason(e,t){let r="cc_reason_"+e.replace(/\./g,"_"),n=i(r);if(n!==r)return n.replace("{module}",t);let s="cc_reason_"+e.replace(/^[a-z]+\./,""),o=i(s);return o===s?null:o.replace("{module}",t)}_renderCard(e,t,r){let n=r,a=this._sevClass(n.severity),s=ke._REAL_PROBE.has(t),o=ke._NAVIGABLE.has(t),l=e.createEl("div",{cls:"pf-cc-card",attr:{role:"listitem",tabindex:"0","data-module":t,"aria-label":`${i("cc_module_"+t)} \u2014 ${i(this._ccBadgeKey(n,t))}`}}),c=l.createEl("div",{cls:"pf-cc-card-header"}),u=c.createEl("div",{cls:"pf-cc-card-name-area"});if(o){let v=t==="installation"?i("module_detail_open_installation"):t==="library"?i("module_detail_open_library"):t==="ocr"?i("module_detail_open_ocr"):t==="memory"?i("module_detail_open_memory"):t==="help"?i("module_detail_open_help"):t==="maintenance"?i("module_detail_open_maintenance"):i("md_select_installation"),S=u.createEl("button",{cls:"pf-open-module-btn",text:i("cc_module_"+t),attr:{"data-module":t,"aria-label":v}});S.addEventListener("click",()=>this._handleCardNavigation(t)),S.addEventListener("keydown",R=>{(R.key==="Enter"||R.key===" ")&&(R.preventDefault(),this._handleCardNavigation(t))})}else u.createEl("div",{cls:"pf-cc-card-name",text:i("cc_module_"+t)});c.createEl("div",{cls:`pf-cc-card-badge pf-cc-card-badge--${a}`,text:i(this._ccBadgeKey(n,t))});let h;if(!s)h=i("cc_reason_placeholder").replace("{module}",i("cc_module_"+t));else{let v=this._localizeReason(n.reason.code,t);h=v!=null?v:n.reason.text}if(l.createEl("div",{cls:"pf-cc-card-reason",text:h}),n.activity_state==="running"&&n.activity_label){let v=l.createEl("div",{cls:"pf-cc-card-activity",attr:{"aria-live":"polite"}});if(v.createEl("span",{text:n.activity_label}),n.activity_progress&&n.activity_progress.total>0){let S=Math.round(n.activity_progress.current/n.activity_progress.total*100),P=v.createEl("div",{cls:"pf-cc-card-progress",attr:{role:"progressbar","aria-valuenow":String(n.activity_progress.current),"aria-valuemin":"0","aria-valuemax":String(n.activity_progress.total)}}).createEl("div",{cls:"pf-cc-card-progress-fill"});P.style.width=S+"%"}}let _=l.createEl("div",{cls:"pf-cc-card-footer"});if(s&&n.action.primary&&!je(n)){let v=He(n),R=v.kind==="setup"?"pf-cc-card-action pf-cc-card-action--primary":"pf-cc-card-action";_.createEl("button",{cls:R,text:v.label,attr:{"aria-label":v.label}}).addEventListener("click",()=>{v.kind==="setup"?new Re(this.app,this.plugin,()=>{this._probeModule("installation"),this._probeModule("help")}).open():this._dispatchModuleAction(t,n)})}let f=l.createEl("details",{cls:"pf-cc-card-diagnostic"});f.createEl("summary",{text:i("cc_diagnostic_toggle")});let g=f.createEl("div",{cls:"pf-cc-card-diagnostic-body"}),E=i("cc_state_"+n.capability_state)||n.capability_state,m=i("cc_severity_"+n.severity)||n.severity,b=i("cc_activity_"+n.activity_state)||n.activity_state,x;try{x=new Date(n.updated_at).toLocaleString()}catch(v){x=n.updated_at}g.createEl("div",{text:`${i("cc_diag_module")}: ${n.module}`}),g.createEl("div",{text:`${i("cc_diag_state")}: ${E}`}),g.createEl("div",{text:`${i("cc_diag_severity")}: ${m}`}),g.createEl("div",{text:`${i("cc_diag_activity")}: ${b}`});let k=g.createEl("div");k.appendText(i("cc_diag_reason")+": "+h+" ");let y=k.createEl("code",{text:n.reason.code});g.createEl("div",{text:`${i("cc_diag_ttl")}: ${String(n.ttl_seconds)}s`}),g.createEl("div",{text:`${i("cc_diag_updated")}: ${x}`})}_handleCardNavigation(e){e==="installation"?(this.activeTab="module-detail",this._selectedDetailModule="installation",this._focusTargetId="#pf-installation-detail-heading"):e==="library"?(this.activeTab="module-detail",this._selectedDetailModule="library",this._focusTargetId="#pf-library-detail-heading"):e==="ocr"?(this.activeTab="module-detail",this._selectedDetailModule="ocr",this._focusTargetId="#pf-ocr-detail-heading"):e==="memory"?(this.activeTab="module-detail",this._selectedDetailModule="memory",this._focusTargetId="#pf-memory-detail-heading"):e==="help"?(this.activeTab="help",this._selectedDetailModule="",this._focusTargetId="button.pf-open-module-btn[data-module=help]"):e==="maintenance"&&(this.activeTab="maintenance",this._selectedDetailModule="",this._focusTargetId=null),this.display()}_renderControlCenter(e){var f,g,E;let t=e.createEl("div",{cls:"pf-control-center"}),r=Fe,n=(f=this._capabilityState)!=null?f:{},a=0,s=0,o=0;for(let m of r){let b=(g=n[m])!=null?g:Se(m);b.severity==="ok"&&b.capability_state==="ready"&&b.action.primary===null?a++:ke._REAL_PROBE.has(m)?(b.severity==="error"||b.severity==="warning"||b.severity==="unknown")&&s++:o++}let l=t.createEl("div",{cls:"pf-cc-summary"});l.createEl("div",{cls:"pf-cc-summary-eyebrow",text:i("cc_title")});let c,u;s>0?(c=i("cc_summary_attention"),u=i("cc_summary_attention_body")):a===r.length?(c=i("cc_summary_ok"),u=i("cc_summary_ok_body")):a>0&&o>0&&s===0?(c=i("cc_summary_core_ok").replace("{n}",String(o)),u=i("cc_summary_core_ok_body")):(c=i("cc_summary_core_ok").replace("{n}",String(r.length-a)),u=i("cc_desc")),l.createEl("div",{cls:"pf-cc-summary-title",text:c}),l.createEl("div",{cls:"pf-cc-summary-body",text:u});let h=l.createEl("div",{cls:"pf-cc-summary-counts"});h.createEl("div",{cls:"pf-cc-summary-count",text:i("cc_n_ready").replace("{n}",String(a))}),o>0&&h.createEl("div",{cls:"pf-cc-summary-count",text:i("cc_n_pending").replace("{n}",String(o))});let _=t.createEl("div",{cls:"pf-cc-grid",attr:{role:"list","aria-label":i("cc_zone_modules")}});for(let m of r){let b=(E=n[m])!=null?E:Se(m);this._renderCard(_,m,b)}}_applyStaleTolerance(){if(!this._capabilityState)return;let e=!1;for(let t of Fe){let r=this._capabilityState[t];r&&ut(r)&&(this._capabilityState[t]=dt(t),e=!0)}e&&this._persistCapabilityState()}};ke._REAL_PROBE=new Set(["installation","library","ocr","memory","help"]),ke._NAVIGABLE=new Set(["installation","library","ocr","memory","maintenance","help"]);var it=ke;var $=require("obsidian"),ve=K(require("fs")),Ze=K(require("path")),be=require("child_process");var We=K(require("path"));function ar(d){if(!d)return null;let p=We.dirname(d);for(;;){let e=We.basename(p);if(!e||e===".")break;let t=e.match(/^([A-Z0-9]{8})(?:\s*-\s*.*)?$/i);if(t)return t[1];let r=We.dirname(p);if(r===p)break;p=r}return null}var G=K(require("fs")),we=K(require("path"));function qe(d){return ce(d).ocrDir}function Tr(d,p){let e=we.join(qe(d),p,"versions","manifest.json");try{if(!G.existsSync(e))return null;let t=G.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)return r}return null}catch(t){return null}}function Ar(d){let p=qe(d);try{return G.existsSync(p)?G.readdirSync(p,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name):[]}catch(e){return[]}}function Rt(d){let p=Ar(d),e=[];for(let t of p){let r=Tr(d,t);if(!r)continue;let n=r.versions.map(s=>s.label),a=0;for(let s of n){let o=we.join(qe(d),t,"versions",s,"fulltext.md");try{G.existsSync(o)&&(a+=G.statSync(o).size)}catch(l){}}e.push({key:t,title:t.replace(/_/g," "),versions:r.versions,currentLabel:r.current.label,totalSize:a})}return e.sort((t,r)=>t.title.localeCompare(r.title)),e}function or(d,p,e){let t=qe(d),r=we.join(t,p,"versions",e,"fulltext.md"),n=we.join(t,p,"render"),a=we.join(n,"fulltext.md");try{return G.existsSync(r)?(G.existsSync(n)||G.mkdirSync(n,{recursive:!0}),G.copyFileSync(r,a),!0):!1}catch(s){return!1}}function lr(d,p,e,t){var _;let r=qe(d),n=we.join(r,p,"versions",e,"fulltext.md"),a=we.join(r,p,"versions",t,"fulltext.md"),s="",o="";try{G.existsSync(n)&&(s=G.readFileSync(n,"utf-8"))}catch(f){}try{G.existsSync(a)&&(o=G.readFileSync(a,"utf-8"))}catch(f){}let l=ir(s),c=ir(o),u=Math.max(l.length,c.length),h=[];for(let f=0;f0)e.push(t.join(` `).trim()),t=[r];else if(r.trim()===""&&t.length>0){let n=t.join(` `).trim();n&&(e.push(n),t=[])}else t.push(r);if(t.length>0){let r=t.join(` -`).trim();r&&e.push(r)}return e}var Ie=class extends V.ItemView{constructor(e){super(e);this._currentMode=null;this._currentDomain=null;this._currentPaperKey=null;this._currentPaperEntry=null;this._currentFilePath=null;this._cachedItems=null;this._modeSubscribers=[];this._leafChangeTimer=null;this._ocrPrivacyShown=!1;this._cachedStats=null;this._techDetailsExpanded=!1;this._paperforgeVersion="";this._dashboardPermissions={};this._headerTitle=null;this._versionBadge=null;this._messageEl=null;this._metricsEl=null;this._ocrSection=null;this._ocrEmpty=null;this._ocrBadge=null;this._ocrTrack=null;this._ocrCounts=null;this._driftBannerEl=null;this._versionPapers=null;this._versionFilter="";this._searchContainer=null;this._searchInput=null;this._searchResultsEl=null;this._searchTimer=void 0;this._searchState="idle";this._searchMode="M";this._searchResults=null;this._searchActiveIndex=-1;this._onKeyDown=null;this._currentMode=null,this._currentDomain=null,this._currentPaperKey=null,this._currentPaperEntry=null,this._currentFilePath=null,this._cachedItems=null,this._modeSubscribers=[],this._leafChangeTimer=null,this._ocrPrivacyShown=!1}getViewType(){return Pe}getDisplayText(){return"PaperForge"}getIcon(){return Ne}async onOpen(){this._buildPanel(),this._modeSubscribers=[],this._leafChangeTimer=null,this._setupEventSubscriptions(),this._fetchVersion(),this._detectAndSwitch(),this._onKeyDown=e=>{var t,r,n;if(e.key==="/"&&!e.ctrlKey&&!e.metaKey&&!e.altKey){let a=(r=(t=e.target)==null?void 0:t.tagName)==null?void 0:r.toLowerCase();a!=="input"&&a!=="textarea"&&(e.preventDefault(),(n=this._searchInput)==null||n.focus())}},document.addEventListener("keydown",this._onKeyDown)}async onClose(){if(this._modeSubscribers&&this._modeSubscribers.length>0){for(let e of this._modeSubscribers)e.event==="active-leaf-change"?this.app.workspace.off("active-leaf-change",e.ref):e.event==="modify"&&this.app.vault.off("modify",e.ref);this._modeSubscribers=[]}this._leafChangeTimer&&(clearTimeout(this._leafChangeTimer),this._leafChangeTimer=null),this._onKeyDown&&(document.removeEventListener("keydown",this._onKeyDown),this._onKeyDown=null),this._searchState="idle",this._searchResults=null,this._searchActiveIndex=-1,this._searchTimer=void 0,this._cachedItems=null,this._cachedStats=null}_buildPanel(){let e=this.containerEl;e.empty(),e.addClass("paperforge-status-panel");let t=e.createEl("div",{cls:"paperforge-header"}),r=t.createEl("div",{cls:"paperforge-header-left"});r.createEl("div",{cls:"paperforge-header-logo",text:"P"}),this._modeContextEl=r.createEl("div",{cls:"paperforge-mode-context"}),this._headerTitle=r.createEl("h3",{cls:"paperforge-header-title",text:"PaperForge"}),this._versionBadge=r.createEl("span",{cls:"paperforge-header-badge",text:"v\u2014"});let n=t.createEl("button",{cls:"paperforge-header-refresh",attr:{"aria-label":"Refresh"}});n.innerHTML="\u21BB",n.addEventListener("click",()=>{this._invalidateIndex(),this._detectAndSwitch()}),this._messageEl=e.createEl("div",{cls:"paperforge-message",attr:{"aria-live":"polite"}}),this._contentEl=e.createEl("div",{cls:"paperforge-content-area"})}_fetchVersion(){var s,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)||"?",{path:n,extraArgs:a=[]}=z(e,(i=t==null?void 0:t.settings)!=null?i:null,void 0,void 0);Lt(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,this._versionBadge&&this._versionBadge.setText(this._paperforgeVersion),this._driftBannerEl&&r&&this._paperforgeVersion!=="v"+r.replace(/^v/,"")?(this._driftBannerEl.style.display="block",this._driftBannerEl.setText(o("dashboard_drift_warning").replace("{0}",this._paperforgeVersion).replace("{1}","v"+r.replace(/^v/,"")))):this._driftBannerEl&&(this._driftBannerEl.style.display="none")})}_fetchStats(e){var s;if(!this._metricsEl)return;if(!e&&!this._cachedStats)this._metricsEl.empty(),this._metricsEl.createEl("div",{cls:"paperforge-status-loading",text:"Loading..."});else if(e&&!this._cachedStats)return;let t=this.app.vault.adapter.basePath,r=this.app.plugins.plugins.paperforge,{path:n,extraArgs:a=[]}=z(t,(s=r==null?void 0:r.settings)!=null?s:null,void 0,void 0);(0,be.execFile)(n,[...a,"-m","paperforge","dashboard","--json"],{cwd:t,timeout:3e4},(i,l)=>{if(!i)try{let c=JSON.parse(l);if(c.ok&&c.data){let _=this._normalizeDashboardData(c.data);this._cachedStats=_,this._metricsEl.empty(),this._renderStats(_),this._renderOcr(_),this._dashboardPermissions=c.data.permissions||{};return}}catch(c){}this._fallbackFetchStats(e,t,r)})}_normalizeDashboardData(e){let t=e.stats||{},r=t.ocr_health||{},n=t.pdf_health||{},a=e.ocr_version_state||{},s=(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,pending:r.pending||0,processing:0,done:r.done||0,failed:r.failed||0},path_errors:(n.broken||0)+(n.missing||0),ocr_version_state:{total_papers:a.total_papers||0,derived_stale_count:a.derived_stale_count||0,raw_upgradable_count:a.raw_upgradable_count||0}}}_fallbackFetchStats(e,t,r){var s,i,l;let n=((s=r==null?void 0:r.settings)==null?void 0:s.system_dir)||"System",a=Ue.join(t,n,"PaperForge","indexes","formal-library.json");try{let c=ve.readFileSync(a,"utf-8"),_=JSON.parse(c),h=_.items||[],u={},m={pdf_health:{healthy:0,unhealthy:0},ocr_health:{healthy:0,unhealthy:0},note_health:{healthy:0,unhealthy:0},asset_health:{healthy:0,unhealthy:0}},f=0,x=0,g=0,b=0,E=0,k=0;for(let y of h){y.note_path&&k++;let v=y.lifecycle||"pdf_ready";u[v]=(u[v]||0)+1;let S=y.health||{};for(let P of["pdf_health","ocr_health","note_health","asset_health"])(S[P]||"healthy")==="healthy"?m[P].healthy++:m[P].unhealthy++;let C=y.ocr_status||"";f++,C==="done"?x++:C==="pending"?g++:C==="processing"||C==="queued"||C==="running"?b++:E++}this._cachedStats={version:_.paperforge_version||((i=this._cachedStats)==null?void 0:i.version)||"\u2014",total_papers:h.length,formal_notes:k,exports:0,bases:0,ocr:{total:f,pending:g,processing:b,done:x,failed:E},path_errors:0,lifecycle_level_counts:u,health_aggregate:m},this._metricsEl.empty(),this._renderStats(this._cachedStats),this._renderOcr(this._cachedStats)}catch(c){!e&&!this._cachedStats&&this._metricsEl.createEl("div",{cls:"paperforge-status-loading",text:"No index \u2014 trying CLI..."});let{path:_,extraArgs:h=[]}=z(t,(l=r==null?void 0:r.settings)!=null?l:null,void 0,void 0);(0,be.execFile)(_,[...h,"-m","paperforge","status","--json"],{cwd:t,timeout:3e4},(u,m)=>{if(u){if(this._cachedStats)return;this._metricsEl.createEl("div",{cls:"paperforge-status-error",text:`Cannot reach PaperForge CLI. -Make sure paperforge is installed and in your PATH.`});return}try{let f=JSON.parse(m);this._cachedStats=f,this._metricsEl.empty(),this._renderStats(f),this._renderOcr(f)}catch(f){this._cachedStats||this._metricsEl.createEl("div",{cls:"paperforge-status-error",text:"Invalid response from paperforge status."})}})}}_renderSkeleton(e){e.addClass("paperforge-loading")}_renderEmptyState(e,t){e.createEl("div",{cls:"paperforge-empty-state",text:t||"No data"})}_buildMetricBar(e,t,r){if(r<=0)return;let n=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)}%`}})}_loadIndex(){var a;let e=this.app.vault.adapter.basePath,t=this.app.plugins.plugins.paperforge,r=((a=t==null?void 0:t.settings)==null?void 0:a.system_dir)||"System",n=Ue.join(e,r,"PaperForge","indexes","formal-library.json");try{let s=ve.readFileSync(n,"utf-8");return JSON.parse(s)}catch(s){return null}}_getCachedIndex(){if(!this._cachedItems){let e=this._loadIndex();this._cachedItems=e?e.items||[]:[]}return this._cachedItems}_findEntry(e){if(!e)return null;let t=this._getCachedIndex().find(r=>r.zotero_key===e)||null;return At(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]=ot(this._cachedItems[r],t))}_filterByDomain(e){return e?this._getCachedIndex().filter(t=>t.domain===e):[]}_renderStats(e){var s;if(this._versionBadge&&this._versionBadge.setText(this._paperforgeVersion||(e.version?"v"+e.version:"v\u2014")),!e||typeof e.total_papers=="undefined"){this._metricsEl&&this._renderSkeleton(this._metricsEl);return}if(!this._metricsEl)return;this._metricsEl.removeClass("paperforge-loading");let t=e.total_papers||0,r=e.formal_notes||0,n=[{value:t,label:"Papers",color:"var(--color-cyan)",barMax:0},{value:r,label:"Formal Notes",color:"var(--color-blue)",barMax:t},{value:e.exports||0,label:"Exports",color:"var(--color-purple)",barMax:0}];for(let i of n){let l=this._metricsEl.createEl("div",{cls:"paperforge-metric-card"});l.style.setProperty("--metric-color",i.color),l.createEl("div",{cls:"paperforge-metric-value",text:((s=i.value)==null?void 0:s.toString())||"\u2014"}),l.createEl("div",{cls:"paperforge-metric-label",text:i.label}),i.barMax>0&&this._buildMetricBar(l,i.value,i.barMax)}let a=e.ocr_version_state||{};if(a.total_papers>0&&(a.derived_stale_count>0||a.raw_upgradable_count>0)){let i=[];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",{cls:"paperforge-metric-card"});l.style.setProperty("--metric-color","var(--color-yellow)"),l.createEl("div",{cls:"paperforge-metric-value",text:i.join(", ")}),l.createEl("div",{cls:"paperforge-metric-label",text:"OCR Version"})}}_renderOcr(e){if(!this._ocrSection)return;let t=e.ocr||{},r=t.total||0;if(r===0){this._ocrSection.style.display="none";return}this._ocrSection.style.display="block",this._ocrEmpty&&(this._ocrEmpty.style.display="none");let n=t.done||0,a=t.pending||0,s=t.processing||0,i=t.failed||0;if(this._ocrBadge&&(this._ocrBadge.removeClass("active","idle"),s>0?(this._ocrBadge.addClass("active"),this._ocrBadge.setText("Processing")):a>0?(this._ocrBadge.addClass("idle"),this._ocrBadge.setText("Pending")):(this._ocrBadge.addClass("idle"),this._ocrBadge.setText("Idle"))),this._ocrTrack){this._ocrTrack.empty(),s>0?this._ocrTrack.addClass("paperforge-processing"):this._ocrTrack.removeClass("paperforge-processing");let l=[{cls:"pending",count:a},{cls:"active",count:s},{cls:"done",count:n},{cls:"failed",count:i}];for(let c of l)if(c.count>0){let _=(c.count/r*100).toFixed(1);this._ocrTrack.createEl("div",{cls:`paperforge-progress-seg ${c.cls}`,attr:{style:`width:${_}%`}})}}if(this._ocrCounts){this._ocrCounts.empty();let l=[{cls:"pending",value:a,label:"Pending"},{cls:"active",value:s,label:"Processing"},{cls:"done",value:n,label:"Done"},{cls:"failed",value:i,label:"Failed"}];for(let c of l){let _=this._ocrCounts.createEl("div",{cls:"paperforge-ocr-count"});_.createEl("div",{cls:"paperforge-ocr-count-value",text:c.value.toString()}),_.createEl("div",{cls:"paperforge-ocr-count-label",text:c.label})}}}_renderLifecycleStepper(e,t,r){if(!t||!r){this._renderSkeleton(e);return}let n=[{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}),i.key===r?(l.addClass("current"),s=!0):s?l.addClass("pending"):l.addClass("completed")}}_renderHealthMatrix(e,t){if(!t){this._renderSkeleton(e);return}let r=[{key:"pdf_health",label:"PDF Health",iconOk:"\u2713",iconWarn:"\u26A0",iconFail:"\u2717"},{key:"ocr_health",label:"OCR Health",iconOk:"\u2713",iconWarn:"\u26A0",iconFail:"\u2717"},{key:"note_health",label:"Note Health",iconOk:"\u2713",iconWarn:"\u26A0",iconFail:"\u2717"},{key:"asset_health",label:"Asset Health",iconOk:"\u2713",iconWarn:"\u26A0",iconFail:"\u2717"}],n=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,c,_;s==="healthy"||s==="ok"?(l=a.iconOk,c="ok",_=`${a.label}: OK`):s==="warn"||s==="warning"||s==="degraded"?(l=a.iconWarn,c="warn",_=`${a.label}: Needs Attention`):(l=a.iconFail,c="fail",_=`${a.label}: Failed`),i.addClass(c),i.setAttribute("title",_),i.createEl("div",{cls:"paperforge-health-cell-icon",text:l}),i.createEl("div",{cls:"paperforge-health-cell-label",text:a.label})}}_renderMaturityGauge(e,t,r){if(t==null||t===void 0){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}`))}if(n.createEl("div",{cls:"gauge-level",text:`Level ${i} / ${s}`}),i0){let c=n.createEl("ul",{cls:"gauge-blockers"});for(let _ of l)c.createEl("li",{text:_})}}}_renderBarChart(e,t){if(!t||Object.keys(t).length===0){this._renderEmptyState(e,"No lifecycle data");return}let r=[{key:"indexed",label:"Indexed",cls:"stage-indexed"},{key:"pdf_ready",label:"PDF Ready",cls:"stage-pdf-ready"},{key:"fulltext_ready",label:"Fulltext Ready",cls:"stage-fulltext-ready"},{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.createEl("div",{cls:"bar-track"}).createEl("div",{cls:`bar-fill ${s.cls}`,attr:{style:`width:${l.toFixed(1)}%`}}),c.createEl("div",{cls:"bar-count",text:i.toString()})}}_invalidateIndex(){this._cachedItems=null}_extractZoteroKeyFromPath(e){return ar(e)}_resolveModeForFile(e){if(!e)return{mode:"global",filePath:null,key:null,domain:null};let t=e.extension,r=e.path;if(t==="base")return{mode:"collection",filePath:r,key:null,domain:e.basename.trim()};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}}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)return{mode:"paper",filePath:r,key:s.zotero_key,domain:null}}}let n=this._extractZoteroKeyFromPath(r);return n?{mode:"paper",filePath:r,key:n,domain:null}:{mode:"global",filePath:r,key:null,domain:null}}_detectAndSwitch(){let e=this._resolveModeForFile(this.app.workspace.getActiveFile());this._currentDomain=e.domain||null,this._currentPaperKey=e.key||null,this._currentPaperEntry=e.key?this._findEntry(e.key):null,this._switchMode(e.mode,e.filePath)}_switchMode(e,t){if(this._currentMode===e&&this._currentFilePath===t){this._refreshCurrentMode();return}if(this._currentMode=e,this._currentFilePath=t,this._techDetailsExpanded=!1,!!this._contentEl)switch(this._contentEl.empty(),this._contentEl.removeClass("switching"),this._renderModeHeader(e),e){case"global":this._renderGlobalMode();break;case"paper":this._renderPaperMode();break;case"collection":this._renderCollectionMode();break;case"versions":this._renderVersionMode();break}}_renderGlobalMode(){var oe,se,de,ae,Z,T,X;if(!this._contentEl)return;let e=this._contentEl.createEl("div",{cls:"paperforge-global-view"});this._driftBannerEl=e.createEl("div",{cls:"paperforge-drift-banner"}),this._driftBannerEl.style.display="none";let t=this._getCachedIndex(),r=t.length,n=0,a=0,s=0;for(let A of t)A.has_pdf&&n++,A.ocr_status==="done"&&a++,A.deep_reading_status==="done"&&s++;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=[{value:r,label:"papers"},{value:n,label:"PDFs ready"},{value:a,label:"OCR done"},{value:s,label:"deep-read done"}];for(let A of c){let N=l.createEl("div",{cls:"paperforge-snapshot-pill"});N.createEl("span",{cls:"paperforge-snapshot-value",text:String(A.value)}),N.createEl("span",{cls:"paperforge-snapshot-label",text:" "+A.label})}let _=e.createEl("div",{cls:"paperforge-system-status"});_.createEl("div",{cls:"paperforge-section-label",text:"System Status"});let h=_.createEl("div",{cls:"paperforge-status-grid"}),u=this.app.plugins.plugins.paperforge,m=((oe=u==null?void 0:u.manifest)==null?void 0:oe.version)||"?",f=this._paperforgeVersion;if(!f)try{let A=this.app.vault.adapter.basePath,{path:N,extraArgs:te=[]}=z(A,(se=u==null?void 0:u.settings)!=null?se:null,void 0,void 0),D=(0,be.execFileSync)(N,[...te,"-c","import paperforge; print(paperforge.__version__)"],{cwd:A,timeout:5e3,encoding:"utf-8",windowsHide:!0}).trim();D&&(f=D.startsWith("v")?D:"v"+D,this._paperforgeVersion=f)}catch(A){}f=f||"\u2014";let x=f==="v"+m;this._renderSystemStatusRow(h,"Runtime",x?"healthy":"mismatch",x?"v"+m:"plugin v"+m+" \u2260 CLI "+f);let g=this._loadIndex(),b=g&&g.items&&g.items.length>0;this._renderSystemStatusRow(h,"Index",b?"healthy":"missing",b?g.items.length+" entries":"formal-library.json not found");let E=((de=u==null?void 0:u.settings)==null?void 0:de.system_dir)||"System",k=this.app.vault.adapter.basePath,y=!1,v="No exports found";try{let A=Ue.join(k,E,"PaperForge","exports");if(ve.existsSync(A)){let N=ve.readdirSync(A).filter(te=>te.endsWith(".json"));y=N.length>0,v=y?N.length+" export(s)":"No JSON exports"}}catch(A){}this._renderSystemStatusRow(h,"Zotero Export",y?"healthy":"missing",v);let S=!!((ae=u==null?void 0:u.settings)!=null&&ae.paddleocr_api_key);if(!S)try{let A=((Z=u==null?void 0:u.settings)==null?void 0:Z.system_dir)||"System",N=Ue.join(k,A,"PaperForge",".env");if(ve.existsSync(N)){let D=ve.readFileSync(N,"utf-8").match(/^PADDLEOCR_API_TOKEN\s*=\s*(.+)$/m);S=!!(D&&D[1]&&D[1].trim())}}catch(A){}S||(S=!!(process.env.PADDLEOCR_API_TOKEN||process.env.PADDLEOCR_API_KEY||process.env.OCR_TOKEN)),this._renderSystemStatusRow(h,"OCR Token",S?"configured":"missing",S?"Configured":"Not set");let C=!1,P="",R=this.app.vault.adapter.basePath,I=je(R);C=jt(R),P=I&&((T=I.summary)==null?void 0:T.reason)||I&&((X=I.summary)==null?void 0:X.status)||"Unknown",this._renderSystemStatusRow(h,"Memory Layer",C?"healthy":"fail",P);let M=!x&&f!=="\u2014";if(M||!b||!y||!S){let A=e.createEl("div",{cls:"paperforge-issue-summary"});A.createEl("div",{cls:"paperforge-section-label",text:"\u9700\u8981\u5904\u7406"});let N=A.createEl("div",{cls:"paperforge-issue-list"});M&&N.createEl("div",{cls:"paperforge-issue-item",text:"Runtime version mismatch"}),b||N.createEl("div",{cls:"paperforge-issue-item",text:"Index missing or corrupted"}),y||N.createEl("div",{cls:"paperforge-issue-item",text:"No Zotero export found"}),S||N.createEl("div",{cls:"paperforge-issue-item",text:"PaddleOCR API key not configured"});let te=A.createEl("div",{cls:"paperforge-issue-actions"}),D=te.createEl("button",{cls:"paperforge-contextual-btn"});D.createEl("span",{text:"Run Doctor"}),D.addEventListener("click",()=>{let ue=ie.find(Ee=>Ee.id==="paperforge-doctor");ue&&this._runAction(ue,D)});let re=te.createEl("button",{cls:"paperforge-contextual-btn"});re.createEl("span",{text:"Repair Issues"}),re.addEventListener("click",()=>{let ue=ie.find(Ee=>Ee.id==="paperforge-repair");ue&&this._runAction(ue,re)})}let B=e.createEl("div",{cls:"paperforge-global-actions"});B.createEl("div",{cls:"paperforge-section-label",text:"Start Working"});let w=B.createEl("div",{cls:"paperforge-global-actions-row"}),j=w.createEl("button",{cls:"paperforge-contextual-btn primary"});j.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u{1F4C1}"}),j.createEl("span",{text:"Open Literature Hub"}),j.addEventListener("click",()=>{var te;let A=((te=u==null?void 0:u.settings)==null?void 0:te.base_dir)||"Bases",N=this.app.vault.getAbstractFileByPath(A);if(N){let D=null;if(N.children&&(D=N.children.find(re=>re.extension==="base")),D){let re=this.app.workspace.getLeaf(!1);re&&re.openFile(D)}else new V.Notice("[!!] No .base file found in "+A,6e3)}else new V.Notice("[!!] Base directory not found: "+A,6e3)});let Q=w.createEl("button",{cls:"paperforge-contextual-btn"});Q.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u21BB"}),Q.createEl("span",{text:"Sync Library"}),Q.addEventListener("click",()=>{let A=ie.find(N=>N.id==="paperforge-sync");A&&this._runAction(A,Q)});let $=w.createEl("button",{cls:"paperforge-contextual-btn"});$.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u229E"}),$.createEl("span",{text:"Run OCR"}),$.addEventListener("click",()=>{let A=ie.find(N=>N.id==="paperforge-ocr");A&&this._runAction(A,$)});let L=w.createEl("button",{cls:"paperforge-contextual-btn warn"});L.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u21BA"}),L.createEl("span",{text:"Redo OCR"}),L.addEventListener("click",()=>{let A=ie.find(N=>N.id==="paperforge-ocr-redo");A&&this._runAction(A,L)})}_renderSystemStatusRow(e,t,r,n){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||""})}_renderPaperMode(){let e=this._currentPaperEntry,t=this._currentPaperKey;if(!this._contentEl)return;if(!t){this._renderEmptyState(this._contentEl,"No paper data available.");return}if(!e){this._contentEl.createEl("div",{cls:"paperforge-content-placeholder",text:'Paper "'+t+'" not found in canonical index. Sync first.'});return}let r=this._contentEl.createEl("div",{cls:"paperforge-paper-view"}),n=r.createEl("div",{cls:"paperforge-paper-header"});n.createEl("div",{cls:"paperforge-paper-title pf-copy",text:e.title||"Untitled"}).addEventListener("click",()=>{navigator.clipboard.writeText(e.title||""),new V.Notice("Title copied")});let s=n.createEl("div",{cls:"paperforge-paper-meta"});e.authors&&e.authors.length>0&&s.createEl("span",{cls:"paperforge-paper-authors",text:e.authors.join(", ")}),e.year&&s.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"}),_=[{key:"pdf",label:"PDF",ok:e.has_pdf===!0},{key:"ocr",label:"OCR",ok:e.ocr_status==="done",pending:["pending","queued","processing"].includes(e.ocr_status||""),fail:["failed","blocked","done_incomplete","nopdf"].includes(e.ocr_status||"")},{key:"deep",label:"\u7CBE\u8BFB",ok:e.deep_reading_status==="done"}];for(let u of _){let m=l.createEl("span",{cls:"paperforge-status-pill"}),f="pending";u.ok?f="ok":u.fail?f="fail":u.pending&&(f="pending"),m.addClass(f);let x=u.ok?"\u2713":u.fail?"\u2717":"\u25CB";m.createEl("span",{cls:"paperforge-status-pill-icon",text:x}),m.createEl("span",{text:" "+u.label})}if(e.pdf_path){let u=c.createEl("button",{cls:"paperforge-contextual-btn"});u.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u{1F4C4}"}),u.createEl("span",{text:"\u6253\u5F00 PDF"}),u.addEventListener("click",()=>{let m=e.pdf_path.match(/\[\[([^\]]+)\]\]/),f=m?m[1]:e.pdf_path;this.app.vault.getAbstractFileByPath(f)?this.app.workspace.openLinkText(f,""):new V.Notice("[!!] PDF not found: "+f,6e3)})}if(e.fulltext_path){let u=c.createEl("button",{cls:"paperforge-contextual-btn"});u.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u{1F4DD}"}),u.createEl("span",{text:"\u6253\u5F00\u5168\u6587"}),u.addEventListener("click",()=>this._openFulltext(e.fulltext_path))}let h=c.createEl("button",{cls:"paperforge-contextual-btn"});if(h.createEl("span",{text:o("version_panel_title")}),h.addEventListener("click",()=>{this._switchToVersionMode(t)}),this._renderPaperOverviewCard(r,e),e.next_step==="ready"&&e.deep_reading_status==="done"){let u=r.createEl("div",{cls:"paperforge-complete-row"});u.createEl("span",{text:"\u2713"}),u.createEl("span",{text:"\u5DF2\u5B8C\u6210\uFF0C\u53EF\u76F4\u63A5\u4F7F\u7528"})}else this._renderNextStepCard(r,e,t);this._renderRecentDiscussionCard(r,e),this._renderPaperTechnicalDetails(r,e)}_renderPaperOverviewCard(e,t){let r=e.createEl("div",{cls:"paperforge-paper-overview"});r.createEl("div",{cls:"paperforge-paper-overview-header"}).createEl("span",{cls:"paperforge-paper-overview-title",text:"\u6587\u7AE0\u6982\u89C8"});let a=r.createEl("div",{cls:"paperforge-paper-overview-body"}),s=a.createEl("div",{cls:"paperforge-paper-overview-excerpt",text:"\u52A0\u8F7D\u4E2D..."});if(t.note_path){let i=this.app.vault.getAbstractFileByPath(t.note_path);i?this.app.vault.read(i).then(l=>{let c=this._extractOverviewFromNote(l);if(c){let _=c.length>200?c.slice(0,200)+"...":c;if(s.setText(_),c.length>200){let h=a.createEl("div",{cls:"paperforge-expand-container"}),u=h.createEl("button",{cls:"paperforge-expand-icon",title:"\u5C55\u5F00/\u6536\u8D77"});u.innerHTML='';let m=!1;h.addEventListener("click",()=>{s.setText(m?_:c),u.innerHTML=m?'':'',m=!m})}}else s.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")}):s.setText("\u7B14\u8BB0\u6587\u4EF6\u4E0D\u5B58\u5728")}else s.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=["**\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),_=["**5 Cs","**Figure","**\u8BC1\u636E","### Pass 2","## "],h=c.length;for(let f of _){let x=c.indexOf(f);x!==-1&&x{var t,r,n;if(e.key==="/"&&!e.ctrlKey&&!e.metaKey&&!e.altKey){let a=(r=(t=e.target)==null?void 0:t.tagName)==null?void 0:r.toLowerCase();a!=="input"&&a!=="textarea"&&(e.preventDefault(),(n=this._searchInput)==null||n.focus())}},document.addEventListener("keydown",this._onKeyDown)}async onClose(){if(this._modeSubscribers&&this._modeSubscribers.length>0){for(let e of this._modeSubscribers)e.event==="active-leaf-change"?this.app.workspace.off("active-leaf-change",e.ref):e.event==="modify"&&this.app.vault.off("modify",e.ref);this._modeSubscribers=[]}this._leafChangeTimer&&(clearTimeout(this._leafChangeTimer),this._leafChangeTimer=null),this._onKeyDown&&(document.removeEventListener("keydown",this._onKeyDown),this._onKeyDown=null),this._searchState="idle",this._searchResults=null,this._searchActiveIndex=-1,this._searchTimer=void 0,this._cachedItems=null,this._cachedStats=null}_buildPanel(){let e=this.containerEl;e.empty(),e.addClass("paperforge-status-panel");let t=e.createEl("div",{cls:"paperforge-header"}),r=t.createEl("div",{cls:"paperforge-header-left"});r.createEl("div",{cls:"paperforge-header-logo",text:"P"}),this._modeContextEl=r.createEl("div",{cls:"paperforge-mode-context"}),this._headerTitle=r.createEl("h3",{cls:"paperforge-header-title",text:"PaperForge"}),this._versionBadge=r.createEl("span",{cls:"paperforge-header-badge",text:"v\u2014"});let n=t.createEl("button",{cls:"paperforge-header-refresh",attr:{"aria-label":"Refresh"}});n.innerHTML="\u21BB",n.addEventListener("click",()=>{this._invalidateIndex(),this._detectAndSwitch()}),this._messageEl=e.createEl("div",{cls:"paperforge-message",attr:{"aria-live":"polite"}}),this._contentEl=e.createEl("div",{cls:"paperforge-content-area"})}_fetchVersion(){var s,o;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)||"?",{path:n,extraArgs:a=[]}=z(e,(o=t==null?void 0:t.settings)!=null?o:null,void 0,void 0);Lt(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,this._versionBadge&&this._versionBadge.setText(this._paperforgeVersion),this._driftBannerEl&&r&&this._paperforgeVersion!=="v"+r.replace(/^v/,"")?(this._driftBannerEl.style.display="block",this._driftBannerEl.setText(i("dashboard_drift_warning").replace("{0}",this._paperforgeVersion).replace("{1}","v"+r.replace(/^v/,"")))):this._driftBannerEl&&(this._driftBannerEl.style.display="none")})}_fetchStats(e){var s;if(!this._metricsEl)return;if(!e&&!this._cachedStats)this._metricsEl.empty(),this._metricsEl.createEl("div",{cls:"paperforge-status-loading",text:"Loading..."});else if(e&&!this._cachedStats)return;let t=this.app.vault.adapter.basePath,r=this.app.plugins.plugins.paperforge,{path:n,extraArgs:a=[]}=z(t,(s=r==null?void 0:r.settings)!=null?s:null,void 0,void 0);(0,be.execFile)(n,[...a,"-m","paperforge","dashboard","--json"],{cwd:t,timeout:3e4},(o,l)=>{if(!o)try{let c=JSON.parse(l);if(c.ok&&c.data){let u=this._normalizeDashboardData(c.data);this._cachedStats=u,this._metricsEl.empty(),this._renderStats(u),this._renderOcr(u),this._dashboardPermissions=c.data.permissions||{};return}}catch(c){}this._fallbackFetchStats(e,t,r)})}_normalizeDashboardData(e){let t=e.stats||{},r=t.ocr_health||{},n=t.pdf_health||{},a=e.ocr_version_state||{},s=(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,pending:r.pending||0,processing:0,done:r.done||0,failed:r.failed||0},path_errors:(n.broken||0)+(n.missing||0),ocr_version_state:{total_papers:a.total_papers||0,derived_stale_count:a.derived_stale_count||0,raw_upgradable_count:a.raw_upgradable_count||0}}}_fallbackFetchStats(e,t,r){var s,o,l;let n=((s=r==null?void 0:r.settings)==null?void 0:s.system_dir)||"System",a=Ze.join(t,n,"PaperForge","indexes","formal-library.json");try{let c=ve.readFileSync(a,"utf-8"),u=JSON.parse(c),h=u.items||[],_={},f={pdf_health:{healthy:0,unhealthy:0},ocr_health:{healthy:0,unhealthy:0},note_health:{healthy:0,unhealthy:0},asset_health:{healthy:0,unhealthy:0}},g=0,E=0,m=0,b=0,x=0,k=0;for(let y of h){y.note_path&&k++;let v=y.lifecycle||"pdf_ready";_[v]=(_[v]||0)+1;let S=y.health||{};for(let P of["pdf_health","ocr_health","note_health","asset_health"])(S[P]||"healthy")==="healthy"?f[P].healthy++:f[P].unhealthy++;let R=y.ocr_status||"";g++,R==="done"?E++:R==="pending"?m++:R==="processing"||R==="queued"||R==="running"?b++:x++}this._cachedStats={version:u.paperforge_version||((o=this._cachedStats)==null?void 0:o.version)||"\u2014",total_papers:h.length,formal_notes:k,exports:0,bases:0,ocr:{total:g,pending:m,processing:b,done:E,failed:x},path_errors:0,lifecycle_level_counts:_,health_aggregate:f},this._metricsEl.empty(),this._renderStats(this._cachedStats),this._renderOcr(this._cachedStats)}catch(c){!e&&!this._cachedStats&&this._metricsEl.createEl("div",{cls:"paperforge-status-loading",text:"No index \u2014 trying CLI..."});let{path:u,extraArgs:h=[]}=z(t,(l=r==null?void 0:r.settings)!=null?l:null,void 0,void 0);(0,be.execFile)(u,[...h,"-m","paperforge","status","--json"],{cwd:t,timeout:3e4},(_,f)=>{if(_){if(this._cachedStats)return;this._metricsEl.createEl("div",{cls:"paperforge-status-error",text:`Cannot reach PaperForge CLI. +Make sure paperforge is installed and in your PATH.`});return}try{let g=JSON.parse(f);this._cachedStats=g,this._metricsEl.empty(),this._renderStats(g),this._renderOcr(g)}catch(g){this._cachedStats||this._metricsEl.createEl("div",{cls:"paperforge-status-error",text:"Invalid response from paperforge status."})}})}}_renderSkeleton(e){e.addClass("paperforge-loading")}_renderEmptyState(e,t){e.createEl("div",{cls:"paperforge-empty-state",text:t||"No data"})}_buildMetricBar(e,t,r){if(r<=0)return;let n=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)}%`}})}_loadIndex(){var a;let e=this.app.vault.adapter.basePath,t=this.app.plugins.plugins.paperforge,r=((a=t==null?void 0:t.settings)==null?void 0:a.system_dir)||"System",n=Ze.join(e,r,"PaperForge","indexes","formal-library.json");try{let s=ve.readFileSync(n,"utf-8");return JSON.parse(s)}catch(s){return null}}_getCachedIndex(){if(!this._cachedItems){let e=this._loadIndex();this._cachedItems=e?e.items||[]:[]}return this._cachedItems}_findEntry(e){if(!e)return null;let t=this._getCachedIndex().find(r=>r.zotero_key===e)||null;return At(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]=lt(this._cachedItems[r],t))}_filterByDomain(e){return e?this._getCachedIndex().filter(t=>t.domain===e):[]}_renderStats(e){var s;if(this._versionBadge&&this._versionBadge.setText(this._paperforgeVersion||(e.version?"v"+e.version:"v\u2014")),!e||typeof e.total_papers=="undefined"){this._metricsEl&&this._renderSkeleton(this._metricsEl);return}if(!this._metricsEl)return;this._metricsEl.removeClass("paperforge-loading");let t=e.total_papers||0,r=e.formal_notes||0,n=[{value:t,label:"Papers",color:"var(--color-cyan)",barMax:0},{value:r,label:"Formal Notes",color:"var(--color-blue)",barMax:t},{value:e.exports||0,label:"Exports",color:"var(--color-purple)",barMax:0}];for(let o of n){let l=this._metricsEl.createEl("div",{cls:"paperforge-metric-card"});l.style.setProperty("--metric-color",o.color),l.createEl("div",{cls:"paperforge-metric-value",text:((s=o.value)==null?void 0:s.toString())||"\u2014"}),l.createEl("div",{cls:"paperforge-metric-label",text:o.label}),o.barMax>0&&this._buildMetricBar(l,o.value,o.barMax)}let a=e.ocr_version_state||{};if(a.total_papers>0&&(a.derived_stale_count>0||a.raw_upgradable_count>0)){let o=[];a.derived_stale_count>0&&o.push(`${a.derived_stale_count} stale`),a.raw_upgradable_count>0&&o.push(`${a.raw_upgradable_count} upgradable`);let l=this._metricsEl.createEl("div",{cls:"paperforge-metric-card"});l.style.setProperty("--metric-color","var(--color-yellow)"),l.createEl("div",{cls:"paperforge-metric-value",text:o.join(", ")}),l.createEl("div",{cls:"paperforge-metric-label",text:"OCR Version"})}}_renderOcr(e){if(!this._ocrSection)return;let t=e.ocr||{},r=t.total||0;if(r===0){this._ocrSection.style.display="none";return}this._ocrSection.style.display="block",this._ocrEmpty&&(this._ocrEmpty.style.display="none");let n=t.done||0,a=t.pending||0,s=t.processing||0,o=t.failed||0;if(this._ocrBadge&&(this._ocrBadge.removeClass("active","idle"),s>0?(this._ocrBadge.addClass("active"),this._ocrBadge.setText("Processing")):a>0?(this._ocrBadge.addClass("idle"),this._ocrBadge.setText("Pending")):(this._ocrBadge.addClass("idle"),this._ocrBadge.setText("Idle"))),this._ocrTrack){this._ocrTrack.empty(),s>0?this._ocrTrack.addClass("paperforge-processing"):this._ocrTrack.removeClass("paperforge-processing");let l=[{cls:"pending",count:a},{cls:"active",count:s},{cls:"done",count:n},{cls:"failed",count:o}];for(let c of l)if(c.count>0){let u=(c.count/r*100).toFixed(1);this._ocrTrack.createEl("div",{cls:`paperforge-progress-seg ${c.cls}`,attr:{style:`width:${u}%`}})}}if(this._ocrCounts){this._ocrCounts.empty();let l=[{cls:"pending",value:a,label:"Pending"},{cls:"active",value:s,label:"Processing"},{cls:"done",value:n,label:"Done"},{cls:"failed",value:o,label:"Failed"}];for(let c of l){let u=this._ocrCounts.createEl("div",{cls:"paperforge-ocr-count"});u.createEl("div",{cls:"paperforge-ocr-count-value",text:c.value.toString()}),u.createEl("div",{cls:"paperforge-ocr-count-label",text:c.label})}}}_renderLifecycleStepper(e,t,r){if(!t||!r){this._renderSkeleton(e);return}let n=[{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 o of n){let l=a.createEl("div",{cls:"step"});l.createEl("div",{cls:"step-indicator"}),l.createEl("div",{cls:"step-label",text:o.label}),o.key===r?(l.addClass("current"),s=!0):s?l.addClass("pending"):l.addClass("completed")}}_renderHealthMatrix(e,t){if(!t){this._renderSkeleton(e);return}let r=[{key:"pdf_health",label:"PDF Health",iconOk:"\u2713",iconWarn:"\u26A0",iconFail:"\u2717"},{key:"ocr_health",label:"OCR Health",iconOk:"\u2713",iconWarn:"\u26A0",iconFail:"\u2717"},{key:"note_health",label:"Note Health",iconOk:"\u2713",iconWarn:"\u26A0",iconFail:"\u2717"},{key:"asset_health",label:"Asset Health",iconOk:"\u2713",iconWarn:"\u26A0",iconFail:"\u2717"}],n=e.createEl("div",{cls:"paperforge-health-matrix"});for(let a of r){let s=t[a.key]||"healthy",o=n.createEl("div",{cls:"paperforge-health-cell"}),l,c,u;s==="healthy"||s==="ok"?(l=a.iconOk,c="ok",u=`${a.label}: OK`):s==="warn"||s==="warning"||s==="degraded"?(l=a.iconWarn,c="warn",u=`${a.label}: Needs Attention`):(l=a.iconFail,c="fail",u=`${a.label}: Failed`),o.addClass(c),o.setAttribute("title",u),o.createEl("div",{cls:"paperforge-health-cell-icon",text:l}),o.createEl("div",{cls:"paperforge-health-cell-label",text:a.label})}}_renderMaturityGauge(e,t,r){if(t==null||t===void 0){this._renderSkeleton(e);return}let n=e.createEl("div",{cls:"paperforge-maturity-gauge"}),a=n.createEl("div",{cls:"gauge-track"}),s=4,o=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<=o&&(c.addClass("filled"),c.addClass(`level-${l}`))}if(n.createEl("div",{cls:"gauge-level",text:`Level ${o} / ${s}`}),o0){let c=n.createEl("ul",{cls:"gauge-blockers"});for(let u of l)c.createEl("li",{text:u})}}}_renderBarChart(e,t){if(!t||Object.keys(t).length===0){this._renderEmptyState(e,"No lifecycle data");return}let r=[{key:"indexed",label:"Indexed",cls:"stage-indexed"},{key:"pdf_ready",label:"PDF Ready",cls:"stage-pdf-ready"},{key:"fulltext_ready",label:"Fulltext Ready",cls:"stage-fulltext-ready"},{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 o=t[s.key]||0,l=o/a*100,c=n.createEl("div",{cls:"bar-row"});c.createEl("div",{cls:"bar-label",text:s.label}),c.createEl("div",{cls:"bar-track"}).createEl("div",{cls:`bar-fill ${s.cls}`,attr:{style:`width:${l.toFixed(1)}%`}}),c.createEl("div",{cls:"bar-count",text:o.toString()})}}_invalidateIndex(){this._cachedItems=null}_extractZoteroKeyFromPath(e){return ar(e)}_resolveModeForFile(e){if(!e)return{mode:"global",filePath:null,key:null,domain:null};let t=e.extension,r=e.path;if(t==="base")return{mode:"collection",filePath:r,key:null,domain:e.basename.trim()};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}}if(t==="pdf"){let a=this._getCachedIndex();for(let s of a){let o=(s.pdf_path||"").match(/\[\[([^\]]+)\]\]/);if((o?o[1]:s.pdf_path)===r)return{mode:"paper",filePath:r,key:s.zotero_key,domain:null}}}let n=this._extractZoteroKeyFromPath(r);return n?{mode:"paper",filePath:r,key:n,domain:null}:{mode:"global",filePath:r,key:null,domain:null}}_detectAndSwitch(){let e=this._resolveModeForFile(this.app.workspace.getActiveFile());this._currentDomain=e.domain||null,this._currentPaperKey=e.key||null,this._currentPaperEntry=e.key?this._findEntry(e.key):null,this._switchMode(e.mode,e.filePath)}_switchMode(e,t){if(this._currentMode===e&&this._currentFilePath===t){this._refreshCurrentMode();return}if(this._currentMode=e,this._currentFilePath=t,this._techDetailsExpanded=!1,!!this._contentEl)switch(this._contentEl.empty(),this._contentEl.removeClass("switching"),this._renderModeHeader(e),e){case"global":this._renderGlobalMode();break;case"paper":this._renderPaperMode();break;case"collection":this._renderCollectionMode();break;case"versions":this._renderVersionMode();break}}_renderGlobalMode(){var oe,se,de,ae,J,A,Q;if(!this._contentEl)return;let e=this._contentEl.createEl("div",{cls:"paperforge-global-view"});this._driftBannerEl=e.createEl("div",{cls:"paperforge-drift-banner"}),this._driftBannerEl.style.display="none";let t=this._getCachedIndex(),r=t.length,n=0,a=0,s=0;for(let O of t)O.has_pdf&&n++,O.ocr_status==="done"&&a++,O.deep_reading_status==="done"&&s++;let o=e.createEl("div",{cls:"paperforge-library-snapshot"});o.createEl("div",{cls:"paperforge-section-label",text:"Library Snapshot"});let l=o.createEl("div",{cls:"paperforge-snapshot-pills"}),c=[{value:r,label:"papers"},{value:n,label:"PDFs ready"},{value:a,label:"OCR done"},{value:s,label:"deep-read done"}];for(let O of c){let V=l.createEl("div",{cls:"paperforge-snapshot-pill"});V.createEl("span",{cls:"paperforge-snapshot-value",text:String(O.value)}),V.createEl("span",{cls:"paperforge-snapshot-label",text:" "+O.label})}let u=e.createEl("div",{cls:"paperforge-system-status"});u.createEl("div",{cls:"paperforge-section-label",text:"System Status"});let h=u.createEl("div",{cls:"paperforge-status-grid"}),_=this.app.plugins.plugins.paperforge,f=((oe=_==null?void 0:_.manifest)==null?void 0:oe.version)||"?",g=this._paperforgeVersion;if(!g)try{let O=this.app.vault.adapter.basePath,{path:V,extraArgs:te=[]}=z(O,(se=_==null?void 0:_.settings)!=null?se:null,void 0,void 0),B=(0,be.execFileSync)(V,[...te,"-c","import paperforge; print(paperforge.__version__)"],{cwd:O,timeout:5e3,encoding:"utf-8",windowsHide:!0}).trim();B&&(g=B.startsWith("v")?B:"v"+B,this._paperforgeVersion=g)}catch(O){}g=g||"\u2014";let E=g==="v"+f;this._renderSystemStatusRow(h,"Runtime",E?"healthy":"mismatch",E?"v"+f:"plugin v"+f+" \u2260 CLI "+g);let m=this._loadIndex(),b=m&&m.items&&m.items.length>0;this._renderSystemStatusRow(h,"Index",b?"healthy":"missing",b?m.items.length+" entries":"formal-library.json not found");let x=((de=_==null?void 0:_.settings)==null?void 0:de.system_dir)||"System",k=this.app.vault.adapter.basePath,y=!1,v="No exports found";try{let O=Ze.join(k,x,"PaperForge","exports");if(ve.existsSync(O)){let V=ve.readdirSync(O).filter(te=>te.endsWith(".json"));y=V.length>0,v=y?V.length+" export(s)":"No JSON exports"}}catch(O){}this._renderSystemStatusRow(h,"Zotero Export",y?"healthy":"missing",v);let S=!!((ae=_==null?void 0:_.settings)!=null&&ae.paddleocr_api_key);if(!S)try{let O=((J=_==null?void 0:_.settings)==null?void 0:J.system_dir)||"System",V=Ze.join(k,O,"PaperForge",".env");if(ve.existsSync(V)){let B=ve.readFileSync(V,"utf-8").match(/^PADDLEOCR_API_TOKEN\s*=\s*(.+)$/m);S=!!(B&&B[1]&&B[1].trim())}}catch(O){}S||(S=!!(process.env.PADDLEOCR_API_TOKEN||process.env.PADDLEOCR_API_KEY||process.env.OCR_TOKEN)),this._renderSystemStatusRow(h,"OCR Token",S?"configured":"missing",S?"Configured":"Not set");let R=!1,P="",F=this.app.vault.adapter.basePath,M=ze(F);R=jt(F),P=M&&((A=M.summary)==null?void 0:A.reason)||M&&((Q=M.summary)==null?void 0:Q.status)||"Unknown",this._renderSystemStatusRow(h,"Memory Layer",R?"healthy":"fail",P);let I=!E&&g!=="\u2014";if(I||!b||!y||!S){let O=e.createEl("div",{cls:"paperforge-issue-summary"});O.createEl("div",{cls:"paperforge-section-label",text:"\u9700\u8981\u5904\u7406"});let V=O.createEl("div",{cls:"paperforge-issue-list"});I&&V.createEl("div",{cls:"paperforge-issue-item",text:"Runtime version mismatch"}),b||V.createEl("div",{cls:"paperforge-issue-item",text:"Index missing or corrupted"}),y||V.createEl("div",{cls:"paperforge-issue-item",text:"No Zotero export found"}),S||V.createEl("div",{cls:"paperforge-issue-item",text:"PaddleOCR API key not configured"});let te=O.createEl("div",{cls:"paperforge-issue-actions"}),B=te.createEl("button",{cls:"paperforge-contextual-btn"});B.createEl("span",{text:"Run Doctor"}),B.addEventListener("click",()=>{let ue=ie.find(Ee=>Ee.id==="paperforge-doctor");ue&&this._runAction(ue,B)});let re=te.createEl("button",{cls:"paperforge-contextual-btn"});re.createEl("span",{text:"Repair Issues"}),re.addEventListener("click",()=>{let ue=ie.find(Ee=>Ee.id==="paperforge-repair");ue&&this._runAction(ue,re)})}let T=e.createEl("div",{cls:"paperforge-global-actions"});T.createEl("div",{cls:"paperforge-section-label",text:"Start Working"});let w=T.createEl("div",{cls:"paperforge-global-actions-row"}),L=w.createEl("button",{cls:"paperforge-contextual-btn primary"});L.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u{1F4C1}"}),L.createEl("span",{text:"Open Literature Hub"}),L.addEventListener("click",()=>{var te;let O=((te=_==null?void 0:_.settings)==null?void 0:te.base_dir)||"Bases",V=this.app.vault.getAbstractFileByPath(O);if(V){let B=null;if(V.children&&(B=V.children.find(re=>re.extension==="base")),B){let re=this.app.workspace.getLeaf(!1);re&&re.openFile(B)}else new $.Notice("[!!] No .base file found in "+O,6e3)}else new $.Notice("[!!] Base directory not found: "+O,6e3)});let Z=w.createEl("button",{cls:"paperforge-contextual-btn"});Z.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u21BB"}),Z.createEl("span",{text:"Sync Library"}),Z.addEventListener("click",()=>{let O=ie.find(V=>V.id==="paperforge-sync");O&&this._runAction(O,Z)});let j=w.createEl("button",{cls:"paperforge-contextual-btn"});j.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u229E"}),j.createEl("span",{text:"Run OCR"}),j.addEventListener("click",()=>{let O=ie.find(V=>V.id==="paperforge-ocr");O&&this._runAction(O,j)});let N=w.createEl("button",{cls:"paperforge-contextual-btn warn"});N.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u21BA"}),N.createEl("span",{text:"Redo OCR"}),N.addEventListener("click",()=>{let O=ie.find(V=>V.id==="paperforge-ocr-redo");O&&this._runAction(O,N)})}_renderSystemStatusRow(e,t,r,n){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||""})}_renderPaperMode(){let e=this._currentPaperEntry,t=this._currentPaperKey;if(!this._contentEl)return;if(!t){this._renderEmptyState(this._contentEl,"No paper data available.");return}if(!e){this._contentEl.createEl("div",{cls:"paperforge-content-placeholder",text:'Paper "'+t+'" not found in canonical index. Sync first.'});return}let r=this._contentEl.createEl("div",{cls:"paperforge-paper-view"}),n=r.createEl("div",{cls:"paperforge-paper-header"});n.createEl("div",{cls:"paperforge-paper-title pf-copy",text:e.title||"Untitled"}).addEventListener("click",()=>{navigator.clipboard.writeText(e.title||""),new $.Notice("Title copied")});let s=n.createEl("div",{cls:"paperforge-paper-meta"});e.authors&&e.authors.length>0&&s.createEl("span",{cls:"paperforge-paper-authors",text:e.authors.join(", ")}),e.year&&s.createEl("span",{cls:"paperforge-paper-year",text:String(e.year)});let o=r.createEl("div",{cls:"paperforge-status-strip"}),l=o.createEl("div",{cls:"paperforge-status-strip-left"}),c=o.createEl("div",{cls:"paperforge-status-strip-right"}),u=[{key:"pdf",label:"PDF",ok:e.has_pdf===!0},{key:"ocr",label:"OCR",ok:e.ocr_status==="done",pending:["pending","queued","processing"].includes(e.ocr_status||""),fail:["failed","blocked","done_incomplete","nopdf"].includes(e.ocr_status||"")},{key:"deep",label:"\u7CBE\u8BFB",ok:e.deep_reading_status==="done"}];for(let _ of u){let f=l.createEl("span",{cls:"paperforge-status-pill"}),g="pending";_.ok?g="ok":_.fail?g="fail":_.pending&&(g="pending"),f.addClass(g);let E=_.ok?"\u2713":_.fail?"\u2717":"\u25CB";f.createEl("span",{cls:"paperforge-status-pill-icon",text:E}),f.createEl("span",{text:" "+_.label})}if(e.pdf_path){let _=c.createEl("button",{cls:"paperforge-contextual-btn"});_.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u{1F4C4}"}),_.createEl("span",{text:"\u6253\u5F00 PDF"}),_.addEventListener("click",()=>{let f=e.pdf_path.match(/\[\[([^\]]+)\]\]/),g=f?f[1]:e.pdf_path;this.app.vault.getAbstractFileByPath(g)?this.app.workspace.openLinkText(g,""):new $.Notice("[!!] PDF not found: "+g,6e3)})}if(e.fulltext_path){let _=c.createEl("button",{cls:"paperforge-contextual-btn"});_.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u{1F4DD}"}),_.createEl("span",{text:"\u6253\u5F00\u5168\u6587"}),_.addEventListener("click",()=>this._openFulltext(e.fulltext_path))}let h=c.createEl("button",{cls:"paperforge-contextual-btn"});if(h.createEl("span",{text:i("version_panel_title")}),h.addEventListener("click",()=>{this._switchToVersionMode(t)}),this._renderPaperOverviewCard(r,e),e.next_step==="ready"&&e.deep_reading_status==="done"){let _=r.createEl("div",{cls:"paperforge-complete-row"});_.createEl("span",{text:"\u2713"}),_.createEl("span",{text:"\u5DF2\u5B8C\u6210\uFF0C\u53EF\u76F4\u63A5\u4F7F\u7528"})}else this._renderNextStepCard(r,e,t);this._renderRecentDiscussionCard(r,e),this._renderPaperTechnicalDetails(r,e)}_renderPaperOverviewCard(e,t){let r=e.createEl("div",{cls:"paperforge-paper-overview"});r.createEl("div",{cls:"paperforge-paper-overview-header"}).createEl("span",{cls:"paperforge-paper-overview-title",text:"\u6587\u7AE0\u6982\u89C8"});let a=r.createEl("div",{cls:"paperforge-paper-overview-body"}),s=a.createEl("div",{cls:"paperforge-paper-overview-excerpt",text:"\u52A0\u8F7D\u4E2D..."});if(t.note_path){let o=this.app.vault.getAbstractFileByPath(t.note_path);o?this.app.vault.read(o).then(l=>{let c=this._extractOverviewFromNote(l);if(c){let u=c.length>200?c.slice(0,200)+"...":c;if(s.setText(u),c.length>200){let h=a.createEl("div",{cls:"paperforge-expand-container"}),_=h.createEl("button",{cls:"paperforge-expand-icon",title:"\u5C55\u5F00/\u6536\u8D77"});_.innerHTML='';let f=!1;h.addEventListener("click",()=>{s.setText(f?u:c),_.innerHTML=f?'':'',f=!f})}}else s.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")}):s.setText("\u7B14\u8BB0\u6587\u4EF6\u4E0D\u5B58\u5728")}else s.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=["**\u4E00\u53E5\u8BDD\u603B\u89C8:**","**\u4E00\u53E5\u8BDD\u603B\u89C8**","**\u6587\u7AE0\u6458\u8981:**","**\u6587\u7AE0\u6458\u8981**"];for(let o of n){let l=r.indexOf(o);if(l!==-1){let c=r.slice(l+o.length),u=["**5 Cs","**Figure","**\u8BC1\u636E","### Pass 2","## "],h=c.length;for(let g of u){let E=c.indexOf(g);E!==-1&&E300?s.slice(0,300)+"...":s}_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";this.app.vault.adapter.exists(s).then(i=>{if(i)return this.app.vault.adapter.read(s)}).then(async i=>{if(!i)return;let l=this._parseDiscussionMD(i);if(!l||l.length===0)return;r.style.display="block",r.createEl("div",{cls:"paperforge-discussion-header"}).createEl("span",{cls:"paperforge-discussion-title",text:"\u6700\u8FD1\u8BA8\u8BBA"});for(let h of l){let u=r.createEl("div",{cls:"paperforge-discussion-item"}),m=u.createEl("div",{cls:"paperforge-discussion-q"});m.createEl("span",{cls:"paperforge-discussion-q-label",text:"\u63D0\u95EE\uFF1A"}),m.createEl("span",{cls:"paperforge-discussion-q-text",text:h.question});let f=u.createEl("div",{cls:"paperforge-discussion-a"}),x=!1;if(h.answer&&h.answer.length>500&&(x=!0,f.classList.add("paperforge-discussion-a-collapsed")),await V.MarkdownRenderer.render(this.app,h.answer||"",f,s,this),x){let g=!1;u.style.cursor="pointer",u.addEventListener("click",()=>{g=!g,f.classList.toggle("paperforge-discussion-a-collapsed",!g),f.classList.toggle("paperforge-discussion-a-expanded",g)})}}r.createEl("a",{cls:"paperforge-discussion-viewall",text:"\u67E5\u770B\u5168\u90E8\u8BA8\u8BBA \u2192"}).addEventListener("click",h=>{h.preventDefault(),this.app.vault.getAbstractFileByPath(s)?this.app.workspace.openLinkText(s,""):new V.Notice("\u8BA8\u8BBA\u6587\u4EF6\u5C1A\u672A\u751F\u6210")})}).catch(i=>{console.error("PaperForge: discussion.md read error",s,i.message)})}_parseDiscussionMD(e){let t=e.split(/\n## /).slice(1);if(t.length===0)return null;let r=t[t.length-1],n=[],a=r.split(/\*\*\u95EE\u9898:\*\*/).slice(1);for(let s of a){let i=s.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})}return n.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",this._techDetailsExpanded?(s.style.display="block",a.setText("\u6280\u672F\u8BE6\u60C5 \u25BE")):a.setText("\u6280\u672F\u8BE6\u60C5 \u25B8"),a.addEventListener("click",()=>{let u=s.style.display!=="none";s.style.display=u?"none":"block",a.setText(u?"\u6280\u672F\u8BE6\u60C5 \u25B8":"\u6280\u672F\u8BE6\u60C5 \u25BE"),this._techDetailsExpanded=!u});let i=s.createEl("div",{cls:"paperforge-workflow-toggles"}),l=[{key:"do_ocr",label:"OCR",hint:"\u52A0\u5165 OCR"},{key:"analyze",label:"\u7CBE\u8BFB",hint:"\u6807\u8BB0\u7CBE\u8BFB"}];for(let u of l){let m=i.createEl("label",{cls:"paperforge-workflow-toggle"}),f=m.createEl("input",{type:"checkbox",cls:"paperforge-workflow-checkbox"});f.checked=t[u.key]===!0,m.createEl("span",{cls:"paperforge-workflow-toggle-label",text:u.label}),m.createEl("span",{cls:"paperforge-workflow-toggle-hint",text:u.hint}),f.addEventListener("change",async()=>{let x=t.note_path?this.app.vault.getAbstractFileByPath(t.note_path):null;if(!x){new V.Notice("[!!] Note file not found",6e3);return}let g=f.checked;await this.app.fileManager.processFrontMatter(x,b=>{b[u.key]=g}),this._patchCachedEntry(r,{[u.key]:g}),this._currentPaperEntry=ot(this._currentPaperEntry,{[u.key]:g})})}let c=t.health||{},_=[["PDF Health",c.pdf_health||"\u2014"],["OCR Status",t.ocr_status||"\u2014"],["Asset Health",c.asset_health||"\u2014"],["Note Path",t.note_path||"\u2014"],["Fulltext Path",t.fulltext_path||"\u2014"]],h=new Set(["Note Path","Fulltext Path","Key"]);for(let[u,m]of _){let f=s.createEl("div",{cls:"paperforge-technical-row"});f.createEl("span",{cls:"paperforge-technical-label",text:u});let x=f.createEl("span",{cls:"paperforge-technical-value",text:String(m)});h.has(u)&&m&&m!=="\u2014"&&(x.addClass("pf-copy"),x.addEventListener("click",()=>{navigator.clipboard.writeText(m),new V.Notice(u+" copied")}))}}_renderNextStepCard(e,t,r){var l,c;let n=t.next_step||"ready",a={sync:{label:"Sync Needed",text:"This paper needs to be synced from Zotero. Click to run sync.",cmd:"sync",icon:"\u21BB"},ocr:{label:"OCR Needed",text:"Fulltext is missing but PDF is present. Click to run OCR.",cmd:"ocr",icon:"\u229E"},repair:{label:"Repair Needed",text:"State divergence or path errors detected. Click to repair.",cmd:"repair",icon:"\u21BA"},"rebuild index":{label:"Rebuild Needed",text:"Index may be stale. Click to run sync to rebuild.",cmd:"sync",icon:"\u21BB"},"/pf-deep":{label:"Ready for Deep Reading",text:"Fulltext is ready. Copy /pf-deep command and run in your agent.",cmd:null,icon:"\u{1F50D}"},ready:{label:"All Set",text:"This paper is fully processed and ready for use.",cmd:"ready",icon:"\u2713"}},s=a[n]||a.ready,i=e.createEl("div",{cls:"paperforge-next-step-card"});if(n==="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"){let _=i.createEl("button",{cls:"paperforge-next-step-trigger"});_.createEl("span",{text:s.icon+" "+s.label}),_.addEventListener("click",()=>{let h=ie.find(u=>u.cmd===s.cmd);h&&this._runAction(h,_)})}else if(n==="/pf-deep"){let _=i.createEl("button",{cls:"paperforge-next-step-trigger"});_.createEl("span",{text:"\u{1F4CB} "+o("copy_pf_deep_cmd")}),_.addEventListener("click",()=>{let x="/pf-deep "+r;navigator.clipboard.writeText(x).then(()=>{_.setText("\u2713 "+o("copied")),new V.Notice(x+" copied")}).catch(()=>{new V.Notice("[!!] Clipboard write failed",6e3)})});let h=((c=(l=this.app.plugins.plugins.paperforge)==null?void 0:l.settings)==null?void 0:c.agent_platform)||"opencode",m={opencode:"OpenCode",claude:"Claude Code",cursor:"Cursor",github_copilot:"GitHub Copilot",windsurf:"Windsurf",codex:"Codex",gemini:"Gemini CLI",cline:"Cline"}[h]||h;i.createEl("div",{cls:"paperforge-agent-platform-label"}).setText(o("run_in_agent").replace("{0}",m))}else n==="ready"&&i.createEl("button",{cls:"paperforge-next-step-trigger"}).createEl("span",{text:"\u2713 "+s.label})}_openFulltext(e){if(!e){new V.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 V.Notice("[!!] Fulltext file not found: "+e,6e3)}_renderCollectionMode(){let e=this._currentDomain||"Unknown",t=this._filterByDomain(e);if(t.length===0){this._renderGlobalMode();return}if(!this._contentEl)return;let r=this._contentEl.createEl("div",{cls:"paperforge-collection-view"}),n=t.length,a=0,s=0,i=0,l=0,c=0,_=0,h=0;for(let y of t){y.has_pdf&&a++,y.ocr_status==="done"&&s++,y.ocr_status==="done"&&y.analyze===!0&&i++,y.deep_reading_status==="done"&&l++;let v=y.ocr_status||"";v==="pending"||v==="queued"?c++:v==="processing"?_++:(v==="failed"||v==="blocked"||v==="done_incomplete"||v==="nopdf")&&h++}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",{cls:"paperforge-section-label",text:"Workflow Overview"});let f=m.createEl("div",{cls:"paperforge-workflow-funnel"}),x=[{value:n,label:"Total"},{value:a,label:"PDF Ready"},{value:s,label:"OCR Done"},{value:l,label:"Deep Read"}];for(let y=0;y0){let y=r.createEl("div",{cls:"paperforge-ocr-section"}),v=y.createEl("div",{cls:"paperforge-collection-ocr-header"});v.createEl("h4",{cls:"paperforge-ocr-title",text:"OCR Pipeline"});let S=v.createEl("span",{cls:"paperforge-ocr-badge idle"});_>0?(S.addClass("active"),S.setText("Processing")):c>0?S.setText("Pending"):(S.addClass("idle"),S.setText("Idle"));let C=y.createEl("div",{cls:"paperforge-progress-track"});_>0&&C.addClass("paperforge-processing");let P=c+_+s+h,R=[{cls:"pending",count:c},{cls:"active",count:_},{cls:"done",count:s},{cls:"failed",count:h}];for(let O of R)if(O.count>0){let B=(O.count/P*100).toFixed(1);C.createEl("div",{cls:`paperforge-progress-seg ${O.cls}`,attr:{style:`width:${B}%`}})}let I=y.createEl("div",{cls:"paperforge-ocr-counts"}),M=[{cls:"pending",value:c,label:"Pending"},{cls:"active",value:_,label:"Processing"},{cls:"done",value:s,label:"Done"},{cls:"failed",value:h,label:"Attention"}];for(let O of M){let B=I.createEl("div",{cls:"paperforge-ocr-count"});B.createEl("div",{cls:"paperforge-ocr-count-value",text:O.value.toString()}),B.createEl("div",{cls:"paperforge-ocr-count-label",text:O.label})}}let g=r.createEl("div",{cls:"paperforge-collection-actions"}),b=g.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 y=ie.find(v=>v.id==="paperforge-ocr");y&&this._runAction(y,b)});let E=g.createEl("button",{cls:"paperforge-contextual-btn"});E.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u21BB"}),E.createEl("span",{text:"Sync Library"}),E.addEventListener("click",()=>{let y=ie.find(v=>v.id==="paperforge-sync");y&&this._runAction(y,E)});let k=g.createEl("button",{cls:"paperforge-contextual-btn warn"});k.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u21BA"}),k.createEl("span",{text:"Redo OCR"}),k.addEventListener("click",()=>{let y=ie.find(v=>v.id==="paperforge-ocr-redo");y&&this._runAction(y,k)}),this.renderSearchSection(r)}_refreshCurrentMode(){if(!(!this._currentMode||!this._contentEl)){this._contentEl.empty(),this._contentEl.addClass("switching"),this._invalidateIndex(),this._currentPaperEntry=this._currentPaperKey?this._findEntry(this._currentPaperKey):null,this._renderModeHeader(this._currentMode);try{switch(this._currentMode){case"global":this._renderGlobalMode();break;case"paper":this._renderPaperMode();break;case"collection":this._renderCollectionMode();break;case"versions":this._renderVersionMode();break}}finally{setTimeout(()=>{this._contentEl&&this._contentEl.removeClass("switching")},50)}}}_switchToVersionMode(e){let r=this.app.vault.adapter.basePath,n=typeof r=="string"?r:"";if(!n){new V.Notice("Cannot determine vault path");return}this._versionPapers=Rt(n),this._versionFilter="",this._currentMode="versions",this._currentFilePath=null,this._techDetailsExpanded=!1,this._contentEl&&(this._contentEl.empty(),this._contentEl.removeClass("switching"),this._renderModeHeader("versions"),this._renderVersionMode())}_renderVersionMode(){if(!this._contentEl)return;let e=this._contentEl.createEl("div",{cls:"paperforge-version-panel"}),r=this.app.vault.adapter.basePath,n=typeof r=="string"?r:"";if(!n){e.createEl("div",{cls:"paperforge-status-error",text:"Could not determine vault path"});return}(!this._versionPapers||this._versionPapers.length===0)&&(this._versionPapers=Rt(n));let a=e.createEl("div",{cls:"paperforge-version-left"}),s=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 b=this._versionFilter.toLowerCase(),E=this._versionPapers?this._versionPapers.filter(y=>!b||y.key.toLowerCase().includes(b)||y.title.toLowerCase().includes(b)):[];if(E.length===0){l.createEl("div",{cls:"paperforge-meta",text:o("version_no_backups")});return}let k=l.createEl("div",{cls:"paperforge-meta",text:o("version_papers_count").replace("{n}",String(E.length))});for(let y of E){let v=l.createEl("div",{cls:"paperforge-version-paper-item"}),S=v.createEl("span",{cls:"paperforge-version-paper-title",text:y.title}),C=v.createEl("span",{cls:"paperforge-version-paper-versions",text:y.versions.map(P=>P.label).join(" ")});v.addEventListener("click",()=>{l.querySelectorAll(".paperforge-version-paper-item.selected").forEach(P=>P.removeClass("selected")),v.addClass("selected"),h(y)})}};i.addEventListener("input",()=>{this._versionFilter=i.value,c()});let _=s.createEl("div",{cls:"paperforge-version-timeline-area"}),h=b=>{if(_.empty(),_.createEl("div",{cls:"paperforge-version-timeline-header"}).createEl("span",{cls:"pf-title",text:b.title}),b.versions.length===0){_.createEl("div",{cls:"paperforge-meta",text:o("version_no_backups")});return}let k=_.createEl("div",{cls:"paperforge-version-timeline"});for(let y of b.versions){let v=y.label===b.currentLabel,S=k.createEl("div",{cls:"paperforge-version-entry"+(v?" paperforge-version-current":"")}),C=S.createEl("div",{cls:"paperforge-version-dot"}),P=S.createEl("div",{cls:"paperforge-version-content"}),R=P.createEl("div",{cls:"paperforge-version-label-row"});R.createEl("span",{cls:"paperforge-version-label",text:y.label}),v&&R.createEl("span",{cls:"paperforge-version-current-tag",text:o("version_current")});let I=y.created_at?y.created_at.slice(0,10):"";P.createEl("div",{cls:"paperforge-meta",text:I+" \u2014 "+y.source});let M=y.fulltext_size?y.fulltext_size>1024?(y.fulltext_size/1024).toFixed(0)+"KB":y.fulltext_size+"B":"";M&&P.createEl("div",{cls:"paperforge-meta",text:M});let O=P.createEl("div",{cls:"paperforge-version-actions"});O.createEl("button",{cls:"pf-btn-primary",text:o("version_restore_btn")}).addEventListener("click",()=>{or(n,b.key,y.label)?new V.Notice(o("version_restore_done").replace("{label}",y.label)):new V.Notice("Restore failed",6e3)}),b.versions.length>1&&!v&&O.createEl("button",{cls:"pf-btn-secondary",text:o("version_compare_btn")}).addEventListener("click",()=>{m(b,y.label,b.currentLabel)})}},u=s.createEl("div",{cls:"paperforge-version-compare"});u.style.display="none";let m=(b,E,k)=>{let y=lr(n,b.key,E,k);u.style.display="block",u.empty();let v=u.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)}),v.createEl("span",{cls:"paperforge-meta",text:o("version_compare_paragraphs").replace("{n}",String(y.length))}),y.length===0){u.createEl("div",{cls:"paperforge-meta",text:"No changes"});return}let S=u.createEl("div",{cls:"paperforge-version-diff-list"});for(let C of y){let P=S.createEl("div",{cls:"paperforge-version-diff-row"}),R=C.type==="added"?"[+]":C.type==="removed"?"[-]":"[~]",I=C.heading||"paragraph "+(C.paragraphIndex+1);P.createEl("span",{cls:"paperforge-version-diff-label",text:R+" "+I}),C.oldText&&P.createEl("pre",{cls:"paperforge-version-diff-old",text:C.oldText.slice(0,200)}),C.newText&&P.createEl("pre",{cls:"paperforge-version-diff-new",text:C.newText.slice(0,200)})}},f=e.createEl("div",{cls:"paperforge-version-actions-bar"}),x=f.createEl("button",{cls:"pf-btn-primary",text:o("version_restore_selected")}),g=f.createEl("button",{cls:"pf-btn-secondary",text:o("version_clear_old").replace("{size}","")});c()}renderSearchSection(e){this._searchContainer=e.createEl("div",{cls:"paperforge-search-section"}),this._searchContainer.createEl("div",{cls:"paperforge-search-header"}).createEl("span",{cls:"pf-label",text:"Search"});let r=this._searchContainer.createEl("div",{cls:"paperforge-search-input-row"}),n=r.createEl("span",{cls:"paperforge-search-mode",text:"M"});this._searchInput=r.createEl("input",{cls:"paperforge-search-input",attr:{type:"text",placeholder:"Search papers... (@ for deep search)"}}),this._searchResultsEl=this._searchContainer.createEl("div",{cls:"paperforge-search-results"}),this._searchInput.placeholder=o("retrieval_search_placeholder"),this._searchInput.addEventListener("input",()=>{var s;let a=((s=this._searchInput)==null?void 0:s.value)||"";if(a.startsWith("@")&&!a.startsWith("@ ")?(this._searchMode="@",n.setText("@"),n.addClass("deep"),this._searchInput&&(this._searchInput.placeholder=o("retrieval_search_placeholder_deep"))):(this._searchMode="M",n.setText("M"),n.removeClass("deep"),this._searchInput&&(this._searchInput.placeholder=o("retrieval_search_placeholder"))),clearTimeout(this._searchTimer),!a.trim()){this._searchState="idle",this._searchResults=null,this._searchActiveIndex=-1,this._renderSearchState();return}a.startsWith("@")||(this._searchTimer=setTimeout(()=>{this.executeSearch()},200))}),this._searchInput.addEventListener("keydown",a=>{var s,i;if(a.key==="Escape"){a.preventDefault(),this._searchInput&&(this._searchInput.value="",this._searchInput.blur()),this._searchState="idle",this._searchResults=null,this._searchActiveIndex=-1,this._renderSearchState();return}if(a.key==="ArrowDown"||a.key==="ArrowUp"){if(this._searchState!=="results"||!((s=this._searchResults)!=null&&s.length))return;a.preventDefault();let l=this._searchResults.length;a.key==="ArrowDown"?this._searchActiveIndex=Math.min(this._searchActiveIndex+1,l-1):this._searchActiveIndex=Math.max(this._searchActiveIndex-1,-1);let c=(i=this._searchResultsEl)==null?void 0:i.querySelectorAll(".paperforge-search-result-card");c&&c.forEach((_,h)=>{h===this._searchActiveIndex?(_.setAttribute("aria-selected","true"),_.classList.add("active")):(_.setAttribute("aria-selected","false"),_.classList.remove("active"))});return}if(a.key==="Enter"&&a.ctrlKey){a.preventDefault(),this._searchTimer&&(clearTimeout(this._searchTimer),this._searchTimer=void 0);let l=this._searchMode;this._searchMode="@",this.executeSearch(),this._searchMode=l;return}a.key==="Enter"&&(a.preventDefault(),this._searchTimer&&(clearTimeout(this._searchTimer),this._searchTimer=void 0),this.executeSearch())})}_renderSearchState(){if(!this._searchResultsEl)return;switch(this._searchResultsEl.empty(),this._searchResultsEl.removeAttribute("role"),this._searchResultsEl.removeAttribute("aria-live"),this._searchInput&&(this._searchInput.disabled=!1),this._searchState){case"idle":break;case"searching":{let t=this._searchMode==="@";this._searchResultsEl.createEl("div",{cls:"paperforge-search-loading",text:t?o("retrieval_searching_deep"):o("retrieval_searching_metadata")}),this._searchResultsEl.setAttr("aria-live","polite"),t&&this._searchInput&&(this._searchInput.disabled=!0);break}case"results":{this._searchResultsEl.setAttr("role","listbox"),this._searchResultsEl.setAttr("aria-live","polite"),this._searchResults&&this._renderSearchResultsList(this._searchResults,this._searchMode==="@"),setTimeout(()=>{var r;let t=(r=this._searchResultsEl)==null?void 0:r.querySelector(".paperforge-search-result-card");t&&t instanceof HTMLElement&&t.focus()},100);break}case"empty":{let t=this._searchResultsEl.createEl("div",{cls:"paperforge-search-empty"});t.setAttr("role","alert"),t.createEl("div",{text:o("retrieval_empty")}),t.createEl("div",{cls:"paperforge-search-empty-tips",text:o("retrieval_empty_tips")});break}case"vectors-not-built":{let t=this._searchResultsEl.createEl("div",{cls:"paperforge-search-state-card",attr:{role:"alert"}});t.addClass("warning-soft"),t.createEl("div",{cls:"paperforge-search-state-title",text:o("retrieval_vectors_not_built")}),t.createEl("div",{cls:"paperforge-search-state-desc",text:o("retrieval_vectors_not_built_desc")});let r=t.createEl("button",{cls:"pf-btn-link",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")}}),setTimeout(()=>{r.focus()},100);break}case"backend-unavailable":{let t=this._searchResultsEl.createEl("div",{cls:"paperforge-search-state-card",attr:{role:"alert"}});t.addClass("error-soft"),t.createEl("div",{cls:"paperforge-search-state-title",text:o("retrieval_backend_unavailable")}),t.createEl("div",{cls:"paperforge-search-state-desc",text:o("retrieval_backend_unavailable_desc")});let r=t.createEl("div",{cls:"paperforge-search-state-actions"}),n=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=[]}=z(s,null,void 0,void 0);(0,be.spawn)(i,[...l,"-m","paperforge","doctor"],{cwd:s,stdio:"inherit"})}}),r.createEl("button",{cls:"pf-btn-secondary",text:o("retrieval_retry")}).addEventListener("click",()=>{this.executeSearch()}),setTimeout(()=>{n.focus()},100);break}case"timeout":{let t=this._searchResultsEl.createEl("div",{cls:"paperforge-search-state-card",attr:{role:"alert"}});t.addClass("warning-soft"),t.createEl("div",{cls:"paperforge-search-state-title",text:o("retrieval_timeout_title")}),t.createEl("div",{cls:"paperforge-search-state-desc",text:o("retrieval_timeout_desc")});let r=t.createEl("button",{cls:"pf-btn-primary",text:o("retrieval_retry")});r.addEventListener("click",()=>{this.executeSearch()}),setTimeout(()=>{r.focus()},100);break}case"model-changed":{let t=this._searchResultsEl.createEl("div",{cls:"paperforge-search-state-card",attr:{role:"alert"}});t.addClass("warning-soft"),t.createEl("div",{cls:"paperforge-search-state-title",text:o("retrieval_model_changed")}),t.createEl("div",{cls:"paperforge-search-state-desc",text:o("retrieval_model_changed_desc")});let r=t.createEl("button",{cls:"pf-btn-primary",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")}}),setTimeout(()=>{r.focus()},100);break}default:{this._searchResultsEl.createEl("div",{cls:"paperforge-search-state-card",text:o("retrieval_internal_error"),attr:{role:"alert"}}).addClass("error-soft"),setTimeout(()=>{this._searchInput&&this._searchInput.focus()},100);break}}}async executeSearch(){if(!this._searchInput||!this._searchResultsEl)return;let e=this._searchInput.value.trim();if(!e)return;let t=this._searchMode==="@"||e.startsWith("@"),r=t?e.replace(/^@\s*/,"").trim():e;if(!r)return;let n=t?"retrieve":"search";this._searchState="searching",this._searchResults=null,this._searchActiveIndex=-1,this._renderSearchState();let a=this.app.vault.adapter,s="";if(a&&typeof a=="object"&&"basePath"in a){let x=a.basePath;s=typeof x=="string"?x:""}if(!s){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 g=x.paperforge;g&&typeof g=="object"&&"settings"in g&&(i=g.settings)}}let{path:_,extraArgs:h=[]}=z(s,i,void 0,void 0),u=n==="retrieve"?["--deep"]:[],m=(0,be.spawn)(_,[...h,"-m","paperforge","--vault",s,n,r,...u,"--json"],{cwd:s,timeout:3e4}),f=[];m.stdout.on("data",x=>{f.push(x.toString("utf-8"))}),m.stderr.on("data",()=>{}),m.on("close",x=>{if(x!==0){let y=gt(String(x));this._searchState=this._mapErrorToSearchState(y.type),this._renderSearchState();return}let g=f.join(""),b=g.indexOf("{"),E=g.lastIndexOf("}"),k="";if(b!==-1&&E>b)k=g.slice(b,E+1);else{let y=g.indexOf("["),v=g.lastIndexOf("]");y!==-1&&v>y&&(k=g.slice(y,v+1))}if(!k){this._searchState="internal-error",this._renderSearchState();return}try{let y=JSON.parse(k),v=[];if(y&&typeof y=="object"&&"data"in y){let S=y.data;if(S&&typeof S=="object"){let C=S;"matches"in C&&Array.isArray(C.matches)&&(v=C.matches)}}this._searchResults=v,this._searchState=v.length>0?"results":"empty",this._renderSearchState()}catch(y){let v=y instanceof Error?y.message:String(y);this._searchState="internal-error",this._renderSearchState()}}),m.on("error",x=>{let g=x.code;if(typeof g=="string"){let b=gt(g);this._searchState=this._mapErrorToSearchState(b.type)}else this._searchState="backend-unavailable";this._renderSearchState()})}_mapErrorToSearchState(e){switch(e){case"vectors_not_built":return"vectors-not-built";case"vectors_corrupted":return"vectors-not-built";case"backend_unavailable":return"backend-unavailable";case"model_changed":return"model-changed";case"timeout":return"timeout";case"no_python":case"python_missing":case"import_failed":case"version_mismatch":return"backend-unavailable";default:return"backend-unavailable"}}_renderSearchResultsList(e,t){if(!this._searchResultsEl)return;if(this._searchResultsEl.setAttr("aria-live","polite"),e.length===0){this._searchResultsEl.createEl("div",{cls:"paperforge-search-empty",text:"No results found."});return}let r=this._searchResultsEl.createEl("div",{cls:"paperforge-search-results-header"});r.createEl("span",{text:o("retrieval_results_count").replace("{n}",String(e.length)).replace("{s}",e.length!==1?"s":"")}).setAttr("aria-live","polite"),r.createEl("span",{cls:"paperforge-search-mode",text:t?"@":"M"});for(let a=0;aE!==null&&typeof E=="object"&&"zotero_key"in E&&E.zotero_key===h);if(b&&typeof b=="object"){let E=b;f=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:null}}f?c.addEventListener("click",g=>{let b=g.ctrlKey||g.metaKey;this.app.workspace.openLinkText(f,"",b)}):c.addEventListener("click",()=>{new V.Notice("[!!] Note not found: "+(h||"unknown"),6e3)}),c.addEventListener("keydown",g=>{if(g.key==="Enter"&&f){g.preventDefault();let b=g.ctrlKey||g.metaKey;this.app.workspace.openLinkText(f,"",b)}});let x=c.createEl("div",{cls:"paperforge-search-result-meta"});if(typeof i.first_author=="string"&&i.first_author&&x.createEl("span",{cls:"paperforge-search-result-author",text:i.first_author}),typeof i.journal=="string"&&i.journal&&x.createEl("span",{cls:"paperforge-search-result-journal",text:i.journal}),i.score!==void 0){let g=i.score,b=typeof g=="number"?g.toFixed(3):String(g);x.createEl("span",{cls:"paperforge-search-result-score",text:"Score: "+b})}if(typeof i.domain=="string"&&i.domain&&c.createEl("span",{cls:"paperforge-search-result-tag",text:i.domain}),typeof i.abstract=="string"&&i.abstract){let g=i.abstract;c.createEl("div",{cls:"paperforge-search-result-abstract",text:g.length>200?g.slice(0,200)+"...":g})}if(t&&typeof i.text=="string"&&i.text){let g=i.text;c.createEl("div",{cls:"paperforge-search-result-source",text:g.length>300?g.slice(0,300)+"...":g})}}}_renderSearchError(e){this._searchResultsEl&&(this._searchResultsEl.empty(),this._searchResultsEl.createEl("div",{cls:"paperforge-search-error",text:e}))}_runAction(e,t){var u,m;if(e.disabled){new V.Notice(`[i] ${e.disabledMsg||"This action is not yet available."}`,6e3);return}if(t.classList.contains("running"))return;t.addClass("running");let r=this.app.vault.adapter.basePath;this._showMessage("Processing...","running");let n=Array.isArray(e.args)?[...e.args]:[];if(e.needsKey){let f=this.app.workspace.getActiveFile(),x=null;if(f){let g=this.app.metadataCache.getFileCache(f);if(g&&g.frontmatter&&g.frontmatter.zotero_key?x=g.frontmatter.zotero_key:x=this._extractZoteroKeyFromPath(f.path),x)n=[...n,x];else if(g&&g.frontmatter){this._showMessage("[!!] No zotero_key in active note frontmatter","error"),new V.Notice("[!!] Open a paper note with a zotero_key in its frontmatter first",6e3),t.removeClass("running");return}else{this._showMessage("[!!] No frontmatter in active note","error"),new V.Notice("[!!] The active note has no frontmatter with a zotero_key",6e3),t.removeClass("running");return}}else{this._showMessage("[!!] No active note open","error"),new V.Notice("[!!] Open a paper note with a zotero_key in its frontmatter first",6e3),t.removeClass("running");return}}e.needsFilter&&(n=[...n,"--all"]);let a=e.needsFilter?6e4:e.needsKey?3e4:6e5,{path:s,extraArgs:i=[]}=z(r,(m=(u=this.app.plugins.plugins.paperforge)==null?void 0:u.settings)!=null?m:null,void 0,void 0),l=(0,be.spawn)(s,[...i,"-m","paperforge",e.cmd,...n],{cwd:r,timeout:a}),c=[],_=Date.now(),h=setInterval(()=>this._fetchStats(!0),4e3);l.stdout.on("data",f=>{let x=f.toString("utf-8").split(` -`).filter(Boolean);for(let g of x){let b=g.trim();b&&(c.push(b),this._showMessage(c.slice(-8).join(` -`),"running"))}}),l.stderr.on("data",f=>{let x=f.toString("utf-8").split(` -`).filter(Boolean);for(let g of x){if(g.includes("\r")||g.includes("%")||g.includes("\u2588"))continue;let b=g.trim();b&&!b.match(/^\d+%|^\|/)&&(c.push(b),this._showMessage(c.slice(-8).join(` -`),"running"))}}),l.on("close",f=>{clearInterval(h),t.removeClass("running");let x=((Date.now()-_)/1e3).toFixed(1);if(f!==0){let g=c.slice(-3).join(" | ")||"exit code "+f;(e.cmd==="repair"||e.cmd==="ocr")&&f===1?(this._showMessage("[WARN] "+g,"running"),new V.Notice("[WARN] "+e.cmd+" partial: "+g,8e3),this._fetchStats(!0)):(this._showMessage("[!!] "+g,"error"),new V.Notice("[!!] "+e.cmd+" failed: "+g,8e3))}else if(e.needsKey||e.needsFilter){let g=c.join(` -`);if(g.trim())try{JSON.parse(g),navigator.clipboard.writeText(g).then(()=>{let b=`${x}s \u2014 ${g.length} chars copied`;this._showMessage("[OK] "+e.title+": "+b,"ok"),new V.Notice("[OK] "+e.okMsg+" \u2014 "+g.length+" chars")}).catch(b=>{this._showMessage("[!!] Clipboard write failed: "+b.message,"error"),new V.Notice("[!!] Clipboard error",6e3)})}catch(b){this._showMessage("[!!] Invalid JSON from "+e.title,"error"),new V.Notice("[!!] "+e.title+" returned invalid JSON: "+b.message.slice(0,100),8e3)}else this._showMessage("[!!] No output from context command","error"),new V.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]||"",E=`${x}s \u2014 ${b}`;this._showMessage("[OK] "+e.title+": "+E,"ok"),new V.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)}console.log("[PF] close cmd="+e.cmd+" id="+e.id),e.cmd==="sync"&&Xe(this.app,this.app.plugins.plugins.paperforge,r)}}),l.on("error",f=>{t.removeClass("running"),this._contentEl&&this._contentEl.removeClass("switching"),this._showMessage("[!!] "+f.message,"error"),new V.Notice("[!!] Cannot start: "+f.message,8e3)})}_showMessage(e,t){this._messageEl&&(this._messageEl.setText(e),this._messageEl.className=`paperforge-message msg-${t}`)}_renderModeHeader(e){if(!this._modeContextEl)return;this._modeContextEl.empty();let t=this._modeContextEl.createEl("span",{cls:"paperforge-mode-badge"}),r="";switch(e){case"global":t.addClass("global"),t.setText("Global"),this._headerTitle&&this._headerTitle.setText("PaperForge");break;case"paper":t.addClass("paper"),t.setText("Paper"),this._headerTitle&&this._headerTitle.setText("Paper"),this._currentPaperEntry&&this._currentPaperEntry.title?r=this._currentPaperEntry.title:this._currentPaperKey?(r=this._currentPaperKey,this._modeContextEl.createEl("span",{cls:"paperforge-mode-warning",text:"Not found in index"})):r="Unknown paper";break;case"collection":t.addClass("collection"),t.setText("Collection"),this._headerTitle&&this._headerTitle.setText("Collection"),r=this._currentDomain||"Unknown Domain";break;case"versions":t.addClass("versions"),t.setText(o("version_panel_title")),this._headerTitle&&this._headerTitle.setText(o("version_panel_title"));break}r&&this._modeContextEl.createEl("span",{cls:"paperforge-mode-name",text:r})}_setupEventSubscriptions(){let e=this.app.workspace.on("active-leaf-change",()=>{this._leafChangeTimer&&clearTimeout(this._leafChangeTimer),this._leafChangeTimer=setTimeout(()=>{let r=this._resolveModeForFile(this.app.workspace.getActiveFile()),n=r.mode,a=r.filePath;this._currentMode===n&&this._currentFilePath===a||this._detectAndSwitch()},300)});this._modeSubscribers.push({event:"active-leaf-change",ref:e});let t=this.app.vault.on("modify",r=>{r&&r.path&&r.path.endsWith("formal-library.json")&&(this._invalidateIndex(),this._refreshCurrentMode())});this._modeSubscribers.push({event:"modify",ref:t})}static async open(e){let t=e.app.workspace.getLeavesOfType(Pe);if(t.length>0){e.app.workspace.revealLeaf(t[0]);return}let r=e.app.workspace.getRightLeaf(!1);r&&(await r.setViewState({type:Pe,active:!0}),e.app.workspace.revealLeaf(r))}};var it=class extends Y.Plugin{constructor(){super(...arguments);this._lastExportMtime=0;this._lastOcrMtimes={};this._autoSyncRunning=!1;this._lastSyncTime=null;this._pollTimer=null;this._embedProcess=null;this._embedProgress={current:0,total:0,key:""};this._embedStderr="";this._memoryStatusText=null}async onload(){await this.loadSettings(),this.saveSettings(),Bt(this.app),this.registerView(Pe,t=>new Ie(t));try{(0,Y.addIcon)(Ne,Tt)}catch(t){}this.addRibbonIcon(Ne,"PaperForge Dashboard",()=>Ie.open(this)),ie.find(t=>t.id==="paperforge-ocr-redo")&&this.addRibbonIcon("reset","PaperForge: Redo OCR",()=>{let t=this.app.vault.adapter.basePath;new Y.Notice("PaperForge: Redo OCR starting...");let{path:r,extraArgs:n}=z(t,this.settings,void 0,void 0);(0,ge.execFile)(r,[...n,"-m","paperforge","ocr","redo"],{cwd:t,timeout:6e5},(a,s,i)=>{if(a){new Y.Notice("PaperForge: Redo OCR failed");return}new Y.Notice("PaperForge: Redo OCR done")})}),this.addSettingTab(new at(this.app,this)),this.addCommand({id:"paperforge-status-panel",name:`PaperForge: ${o("guide_open")}`,callback:()=>Ie.open(this)});for(let t of ie)this.addCommand({id:t.id,name:`PaperForge: ${t.title}`,callback:()=>{if(t.disabled){new Y.Notice(`[i] ${t.disabledMsg||"This action is not yet available."}`,6e3);return}let r=this.app.vault.adapter.basePath;new Y.Notice(`PaperForge: running ${t.cmd}...`);let{path:n,extraArgs:a=[]}=z(r,this.settings,void 0,void 0),s=Array.isArray(t.args)?[...t.args]:[];(0,ge.execFile)(n,[...a,"-m","paperforge",t.cmd,...s],{cwd:r,timeout:3e5},(i,l,c)=>{if(i){new Y.Notice(`[!!] ${t.cmd} failed: ${(c||i.message).slice(0,120)}`,8e3);return}new Y.Notice(`[OK] ${t.okMsg||l.trim().split(` -`)[0].slice(0,80)}`)})}});this.settings.auto_update_on_startup===!0&&this.settings.setup_complete&&setTimeout(()=>this._autoUpdate(),3e3),this._startFilePolling(),this._firstLaunchSnapshotMigration(),this._checkReleaseNotes()}_firstLaunchSnapshotMigration(){let e=this.app.vault.adapter.basePath;if(!e)return;let r=ce(e).memoryStatePath;if(!q.existsSync(r)){let n=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,ge.execFile)(n.path,i,{cwd:e,timeout:6e4,windowsHide:!0},()=>{})})}}_autoUpdate(){let e=this.app.vault.adapter.basePath,{path:t,extraArgs:r=[]}=z(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,ge.spawn)(t,[...r,"-m","pip","install","--upgrade",l],{cwd:e,timeout:12e4,env:Se()}).on("close",h=>c(h===0))};(0,ge.execFile)(t,[...r,"-c","import paperforge; print(paperforge.__version__)"],{cwd:e,timeout:1e4},(l,c)=>{let _=u=>{console.log(`[PaperForge] Auto-update: trying PyPI (paperforge==${n})`),i(a,m=>{if(m){console.log("[PaperForge] Auto-update: installed via PyPI"),new Y.Notice(`[OK] PaperForge CLI ${u}`,5e3);return}console.warn("[PaperForge] Auto-update: PyPI failed, falling back to git..."),i(s,f=>{f&&(console.log("[PaperForge] Auto-update: installed via git"),new Y.Notice(`[OK] PaperForge CLI ${u} (via git)`,5e3))})})};if(l){_("installed");return}let h=c.trim();h!==n&&_(`${h} -> ${n}`)})}_startFilePolling(){let e=this.app.vault.adapter.basePath;this._pollTimer=setInterval(()=>{this._checkExports(e),this._checkOcr(e)},12e4)}_checkExports(e){if(this._autoSyncRunning)return;let t=ce(e).exportsDir;if(!q.existsSync(t))return;let r=0;try{q.readdirSync(t).forEach(n=>{if(!n.endsWith(".json"))return;let a=q.statSync(Me.join(t,n));a.mtimeMs>r&&(r=a.mtimeMs)})}catch(n){return}r>this._lastExportMtime&&(this._lastExportMtime=r,this._autoSync(e))}_autoSync(e){if(this._autoSyncRunning)return;this._autoSyncRunning=!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,ge.exec)(r,{timeout:12e4,encoding:"utf-8"},(n,a,s)=>{this._autoSyncRunning=!1,this._memoryStatusText=null,n||(this._lastSyncTime=new Date().toLocaleTimeString());try{let i=ce(e).exportsDir,l=0;q.readdirSync(i).forEach(c=>{c.endsWith(".json")&&(l=Math.max(l,q.statSync(Me.join(i,c)).mtimeMs))}),this._lastExportMtime=l}catch(i){}})}_checkOcr(e){if(this._autoSyncRunning)return;let t=ce(e).ocrDir;if(q.existsSync(t))try{q.readdirSync(t,{withFileTypes:!0}).forEach(r=>{if(!r.isDirectory())return;let n=Me.join(t,r.name,"meta.json");if(!q.existsSync(n))return;let a=q.statSync(n),s=this._lastOcrMtimes[r.name]||0;if(a.mtimeMs<=s||(this._lastOcrMtimes[r.name]=a.mtimeMs,this._autoSyncRunning))return;this._autoSyncRunning=!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,ge.exec)(l,{timeout:3e4,encoding:"utf-8"},()=>{this._autoSyncRunning=!1,this._memoryStatusText=null})})}catch(r){}}readPaperforgeJson(){let e=this.app.vault.adapter.basePath,t=Me.join(e,"paperforge.json"),r={system_dir:"System",resources_dir:"Resources",literature_dir:"Literature",base_dir:"Bases"};try{if(!q.existsSync(t))return r;let n=q.readFileSync(t,"utf-8"),a=JSON.parse(n),s=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}}catch(n){return console.warn("PaperForge: Failed to read paperforge.json, using defaults",n),r}}savePaperforgeJson(e){let t=this.app.vault.adapter.basePath,r=Me.join(t,"paperforge.json"),n={};try{q.existsSync(r)&&(n=JSON.parse(q.readFileSync(r,"utf-8")))}catch(s){console.warn("PaperForge: Failed to read paperforge.json for update",s)}(!n.vault_config||typeof n.vault_config!="object")&&(n.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];try{if(q.writeFileSync(r,JSON.stringify(n,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}}catch(s){console.error("PaperForge: Failed to write paperforge.json",s),new Y.Notice("PaperForge: Failed to save configuration to paperforge.json")}}onunload(){this._pollTimer&&clearInterval(this._pollTimer),this.app.workspace.detachLeavesOfType(Pe)}async loadSettings(){this.settings=Object.assign({},Ve,await this.loadData()),this.settings.features&&Ve.features&&(this.settings.features=Object.assign({},Ve.features,this.settings.features||{})),this.settings.frozen_skills||(this.settings.frozen_skills={});let e=this.readPaperforgeJson();if(this.settings.system_dir=e.system_dir,this.settings.resources_dir=e.resources_dir,this.settings.literature_dir=e.literature_dir,this.settings.base_dir=e.base_dir,this.settings.python_path&&this.settings.python_path.trim()){let t=this.settings.python_path.trim();q.existsSync(t)?this.settings._python_path_stale=!1:(console.warn(`PaperForge: Saved python_path "${t}" no longer exists - showing stale warning`),this.settings._python_path_stale=!0)}}async saveSettings(){let e={};for(let t of Object.keys(Ve))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=(ht().versions||[]).find(i=>i.version===e);class s extends Y.Modal{constructor(l,c){super(l),this._entry=c}onOpen(){let{contentEl:l}=this;if(l.createEl("h2",{text:`PaperForge v${e} \u66F4\u65B0\u8BF4\u660E`}),this._entry){if(l.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",{text:"\u884C\u4E3A\u53D8\u66F4 / \u8FC1\u79FB\u6CE8\u610F"});for(let c of this._entry.breaking_or_migration)l.createEl("p",{text:`\u2022 ${c}`,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}`,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}`,cls:"paperforge-modal-item"})}if(this._entry.recommended_actions&&this._entry.recommended_actions.length>0){let c=l.createEl("div",{cls:"paperforge-release-recommended"});c.createEl("h4",{text:"\u5EFA\u8BAE\u64CD\u4F5C",cls:""}),c.style.marginBottom="8px";for(let _ of this._entry.recommended_actions)c.createEl("p",{text:`\u2022 ${_}`,cls:"paperforge-release-item-bold"})}}else l.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 Y.Setting(l).addButton(c=>c.setButtonText("\u77E5\u9053\u4E86").setCta().onClick(()=>{this.close()}))}onClose(){let{contentEl:l}=this;l.empty()}}new s(this.app,a).open(),this.settings.last_seen_version=e,this.saveSettings()}}; +`)[0].trim();return!s||s.startsWith("###")||s.startsWith("##")?null:s.length>300?s.slice(0,300)+"...":s}_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";this.app.vault.adapter.exists(s).then(o=>{if(o)return this.app.vault.adapter.read(s)}).then(async o=>{if(!o)return;let l=this._parseDiscussionMD(o);if(!l||l.length===0)return;r.style.display="block",r.createEl("div",{cls:"paperforge-discussion-header"}).createEl("span",{cls:"paperforge-discussion-title",text:"\u6700\u8FD1\u8BA8\u8BBA"});for(let h of l){let _=r.createEl("div",{cls:"paperforge-discussion-item"}),f=_.createEl("div",{cls:"paperforge-discussion-q"});f.createEl("span",{cls:"paperforge-discussion-q-label",text:"\u63D0\u95EE\uFF1A"}),f.createEl("span",{cls:"paperforge-discussion-q-text",text:h.question});let g=_.createEl("div",{cls:"paperforge-discussion-a"}),E=!1;if(h.answer&&h.answer.length>500&&(E=!0,g.classList.add("paperforge-discussion-a-collapsed")),await $.MarkdownRenderer.render(this.app,h.answer||"",g,s,this),E){let m=!1;_.style.cursor="pointer",_.addEventListener("click",()=>{m=!m,g.classList.toggle("paperforge-discussion-a-collapsed",!m),g.classList.toggle("paperforge-discussion-a-expanded",m)})}}r.createEl("a",{cls:"paperforge-discussion-viewall",text:"\u67E5\u770B\u5168\u90E8\u8BA8\u8BBA \u2192"}).addEventListener("click",h=>{h.preventDefault(),this.app.vault.getAbstractFileByPath(s)?this.app.workspace.openLinkText(s,""):new $.Notice("\u8BA8\u8BBA\u6587\u4EF6\u5C1A\u672A\u751F\u6210")})}).catch(o=>{console.error("PaperForge: discussion.md read error",s,o.message)})}_parseDiscussionMD(e){let t=e.split(/\n## /).slice(1);if(t.length===0)return null;let r=t[t.length-1],n=[],a=r.split(/\*\*\u95EE\u9898:\*\*/).slice(1);for(let s of a){let o=s.match(/\*\*\u89E3\u7B54:\*\*/);if(!o)continue;let l=s.substring(0,o.index).trim(),c=s.substring(o.index+3+4).trim();n.push({question:l,answer:c})}return n.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",this._techDetailsExpanded?(s.style.display="block",a.setText("\u6280\u672F\u8BE6\u60C5 \u25BE")):a.setText("\u6280\u672F\u8BE6\u60C5 \u25B8"),a.addEventListener("click",()=>{let _=s.style.display!=="none";s.style.display=_?"none":"block",a.setText(_?"\u6280\u672F\u8BE6\u60C5 \u25B8":"\u6280\u672F\u8BE6\u60C5 \u25BE"),this._techDetailsExpanded=!_});let o=s.createEl("div",{cls:"paperforge-workflow-toggles"}),l=[{key:"do_ocr",label:"OCR",hint:"\u52A0\u5165 OCR"},{key:"analyze",label:"\u7CBE\u8BFB",hint:"\u6807\u8BB0\u7CBE\u8BFB"}];for(let _ of l){let f=o.createEl("label",{cls:"paperforge-workflow-toggle"}),g=f.createEl("input",{type:"checkbox",cls:"paperforge-workflow-checkbox"});g.checked=t[_.key]===!0,f.createEl("span",{cls:"paperforge-workflow-toggle-label",text:_.label}),f.createEl("span",{cls:"paperforge-workflow-toggle-hint",text:_.hint}),g.addEventListener("change",async()=>{let E=t.note_path?this.app.vault.getAbstractFileByPath(t.note_path):null;if(!E){new $.Notice("[!!] Note file not found",6e3);return}let m=g.checked;await this.app.fileManager.processFrontMatter(E,b=>{b[_.key]=m}),this._patchCachedEntry(r,{[_.key]:m}),this._currentPaperEntry=lt(this._currentPaperEntry,{[_.key]:m})})}let c=t.health||{},u=[["PDF Health",c.pdf_health||"\u2014"],["OCR Status",t.ocr_status||"\u2014"],["Asset Health",c.asset_health||"\u2014"],["Note Path",t.note_path||"\u2014"],["Fulltext Path",t.fulltext_path||"\u2014"]],h=new Set(["Note Path","Fulltext Path","Key"]);for(let[_,f]of u){let g=s.createEl("div",{cls:"paperforge-technical-row"});g.createEl("span",{cls:"paperforge-technical-label",text:_});let E=g.createEl("span",{cls:"paperforge-technical-value",text:String(f)});h.has(_)&&f&&f!=="\u2014"&&(E.addClass("pf-copy"),E.addEventListener("click",()=>{navigator.clipboard.writeText(f),new $.Notice(_+" copied")}))}}_renderNextStepCard(e,t,r){var l,c;let n=t.next_step||"ready",a={sync:{label:"Sync Needed",text:"This paper needs to be synced from Zotero. Click to run sync.",cmd:"sync",icon:"\u21BB"},ocr:{label:"OCR Needed",text:"Fulltext is missing but PDF is present. Click to run OCR.",cmd:"ocr",icon:"\u229E"},repair:{label:"Repair Needed",text:"State divergence or path errors detected. Click to repair.",cmd:"repair",icon:"\u21BA"},"rebuild index":{label:"Rebuild Needed",text:"Index may be stale. Click to run sync to rebuild.",cmd:"sync",icon:"\u21BB"},"/pf-deep":{label:"Ready for Deep Reading",text:"Fulltext is ready. Copy /pf-deep command and run in your agent.",cmd:null,icon:"\u{1F50D}"},ready:{label:"All Set",text:"This paper is fully processed and ready for use.",cmd:"ready",icon:"\u2713"}},s=a[n]||a.ready,o=e.createEl("div",{cls:"paperforge-next-step-card"});if(n==="ready"&&o.addClass("ready"),o.createEl("div",{cls:"paperforge-next-step-label",text:"Recommended Next Step"}),o.createEl("div",{cls:"paperforge-next-step-text",text:s.text}),s.cmd&&s.cmd!=="ready"){let u=o.createEl("button",{cls:"paperforge-next-step-trigger"});u.createEl("span",{text:s.icon+" "+s.label}),u.addEventListener("click",()=>{let h=ie.find(_=>_.cmd===s.cmd);h&&this._runAction(h,u)})}else if(n==="/pf-deep"){let u=o.createEl("button",{cls:"paperforge-next-step-trigger"});u.createEl("span",{text:"\u{1F4CB} "+i("copy_pf_deep_cmd")}),u.addEventListener("click",()=>{let E="/pf-deep "+r;navigator.clipboard.writeText(E).then(()=>{u.setText("\u2713 "+i("copied")),new $.Notice(E+" copied")}).catch(()=>{new $.Notice("[!!] Clipboard write failed",6e3)})});let h=((c=(l=this.app.plugins.plugins.paperforge)==null?void 0:l.settings)==null?void 0:c.agent_platform)||"opencode",f={opencode:"OpenCode",claude:"Claude Code",cursor:"Cursor",github_copilot:"GitHub Copilot",windsurf:"Windsurf",codex:"Codex",gemini:"Gemini CLI",cline:"Cline"}[h]||h;o.createEl("div",{cls:"paperforge-agent-platform-label"}).setText(i("run_in_agent").replace("{0}",f))}else n==="ready"&&o.createEl("button",{cls:"paperforge-next-step-trigger"}).createEl("span",{text:"\u2713 "+s.label})}_openFulltext(e){if(!e){new $.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 $.Notice("[!!] Fulltext file not found: "+e,6e3)}_renderCollectionMode(){let e=this._currentDomain||"Unknown",t=this._filterByDomain(e);if(t.length===0){this._renderGlobalMode();return}if(!this._contentEl)return;let r=this._contentEl.createEl("div",{cls:"paperforge-collection-view"}),n=t.length,a=0,s=0,o=0,l=0,c=0,u=0,h=0;for(let y of t){y.has_pdf&&a++,y.ocr_status==="done"&&s++,y.ocr_status==="done"&&y.analyze===!0&&o++,y.deep_reading_status==="done"&&l++;let v=y.ocr_status||"";v==="pending"||v==="queued"?c++:v==="processing"?u++:(v==="failed"||v==="blocked"||v==="done_incomplete"||v==="nopdf")&&h++}r.createEl("div",{cls:"paperforge-collection-header"}).createEl("div",{cls:"paperforge-collection-title",text:e});let f=r.createEl("div",{cls:"paperforge-workflow-overview"});f.createEl("div",{cls:"paperforge-section-label",text:"Workflow Overview"});let g=f.createEl("div",{cls:"paperforge-workflow-funnel"}),E=[{value:n,label:"Total"},{value:a,label:"PDF Ready"},{value:s,label:"OCR Done"},{value:l,label:"Deep Read"}];for(let y=0;y0){let y=r.createEl("div",{cls:"paperforge-ocr-section"}),v=y.createEl("div",{cls:"paperforge-collection-ocr-header"});v.createEl("h4",{cls:"paperforge-ocr-title",text:"OCR Pipeline"});let S=v.createEl("span",{cls:"paperforge-ocr-badge idle"});u>0?(S.addClass("active"),S.setText("Processing")):c>0?S.setText("Pending"):(S.addClass("idle"),S.setText("Idle"));let R=y.createEl("div",{cls:"paperforge-progress-track"});u>0&&R.addClass("paperforge-processing");let P=c+u+s+h,F=[{cls:"pending",count:c},{cls:"active",count:u},{cls:"done",count:s},{cls:"failed",count:h}];for(let D of F)if(D.count>0){let T=(D.count/P*100).toFixed(1);R.createEl("div",{cls:`paperforge-progress-seg ${D.cls}`,attr:{style:`width:${T}%`}})}let M=y.createEl("div",{cls:"paperforge-ocr-counts"}),I=[{cls:"pending",value:c,label:"Pending"},{cls:"active",value:u,label:"Processing"},{cls:"done",value:s,label:"Done"},{cls:"failed",value:h,label:"Attention"}];for(let D of I){let T=M.createEl("div",{cls:"paperforge-ocr-count"});T.createEl("div",{cls:"paperforge-ocr-count-value",text:D.value.toString()}),T.createEl("div",{cls:"paperforge-ocr-count-label",text:D.label})}}let m=r.createEl("div",{cls:"paperforge-collection-actions"}),b=m.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 y=ie.find(v=>v.id==="paperforge-ocr");y&&this._runAction(y,b)});let x=m.createEl("button",{cls:"paperforge-contextual-btn"});x.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u21BB"}),x.createEl("span",{text:"Sync Library"}),x.addEventListener("click",()=>{let y=ie.find(v=>v.id==="paperforge-sync");y&&this._runAction(y,x)});let k=m.createEl("button",{cls:"paperforge-contextual-btn warn"});k.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u21BA"}),k.createEl("span",{text:"Redo OCR"}),k.addEventListener("click",()=>{let y=ie.find(v=>v.id==="paperforge-ocr-redo");y&&this._runAction(y,k)}),this.renderSearchSection(r)}_refreshCurrentMode(){if(!(!this._currentMode||!this._contentEl)){this._contentEl.empty(),this._contentEl.addClass("switching"),this._invalidateIndex(),this._currentPaperEntry=this._currentPaperKey?this._findEntry(this._currentPaperKey):null,this._renderModeHeader(this._currentMode);try{switch(this._currentMode){case"global":this._renderGlobalMode();break;case"paper":this._renderPaperMode();break;case"collection":this._renderCollectionMode();break;case"versions":this._renderVersionMode();break}}finally{setTimeout(()=>{this._contentEl&&this._contentEl.removeClass("switching")},50)}}}_switchToVersionMode(e){let r=this.app.vault.adapter.basePath,n=typeof r=="string"?r:"";if(!n){new $.Notice("Cannot determine vault path");return}this._versionPapers=Rt(n),this._versionFilter="",this._currentMode="versions",this._currentFilePath=null,this._techDetailsExpanded=!1,this._contentEl&&(this._contentEl.empty(),this._contentEl.removeClass("switching"),this._renderModeHeader("versions"),this._renderVersionMode())}_renderVersionMode(){if(!this._contentEl)return;let e=this._contentEl.createEl("div",{cls:"paperforge-version-panel"}),r=this.app.vault.adapter.basePath,n=typeof r=="string"?r:"";if(!n){e.createEl("div",{cls:"paperforge-status-error",text:"Could not determine vault path"});return}(!this._versionPapers||this._versionPapers.length===0)&&(this._versionPapers=Rt(n));let a=e.createEl("div",{cls:"paperforge-version-left"}),s=e.createEl("div",{cls:"paperforge-version-right"}),o=a.createEl("input",{cls:"paperforge-version-filter",attr:{type:"text",placeholder:i("version_filter_placeholder")}});o.value=this._versionFilter;let l=a.createEl("div",{cls:"paperforge-version-paper-list"}),c=()=>{l.empty();let b=this._versionFilter.toLowerCase(),x=this._versionPapers?this._versionPapers.filter(y=>!b||y.key.toLowerCase().includes(b)||y.title.toLowerCase().includes(b)):[];if(x.length===0){l.createEl("div",{cls:"paperforge-meta",text:i("version_no_backups")});return}let k=l.createEl("div",{cls:"paperforge-meta",text:i("version_papers_count").replace("{n}",String(x.length))});for(let y of x){let v=l.createEl("div",{cls:"paperforge-version-paper-item"}),S=v.createEl("span",{cls:"paperforge-version-paper-title",text:y.title}),R=v.createEl("span",{cls:"paperforge-version-paper-versions",text:y.versions.map(P=>P.label).join(" ")});v.addEventListener("click",()=>{l.querySelectorAll(".paperforge-version-paper-item.selected").forEach(P=>P.removeClass("selected")),v.addClass("selected"),h(y)})}};o.addEventListener("input",()=>{this._versionFilter=o.value,c()});let u=s.createEl("div",{cls:"paperforge-version-timeline-area"}),h=b=>{if(u.empty(),u.createEl("div",{cls:"paperforge-version-timeline-header"}).createEl("span",{cls:"pf-title",text:b.title}),b.versions.length===0){u.createEl("div",{cls:"paperforge-meta",text:i("version_no_backups")});return}let k=u.createEl("div",{cls:"paperforge-version-timeline"});for(let y of b.versions){let v=y.label===b.currentLabel,S=k.createEl("div",{cls:"paperforge-version-entry"+(v?" paperforge-version-current":"")}),R=S.createEl("div",{cls:"paperforge-version-dot"}),P=S.createEl("div",{cls:"paperforge-version-content"}),F=P.createEl("div",{cls:"paperforge-version-label-row"});F.createEl("span",{cls:"paperforge-version-label",text:y.label}),v&&F.createEl("span",{cls:"paperforge-version-current-tag",text:i("version_current")});let M=y.created_at?y.created_at.slice(0,10):"";P.createEl("div",{cls:"paperforge-meta",text:M+" \u2014 "+y.source});let I=y.fulltext_size?y.fulltext_size>1024?(y.fulltext_size/1024).toFixed(0)+"KB":y.fulltext_size+"B":"";I&&P.createEl("div",{cls:"paperforge-meta",text:I});let D=P.createEl("div",{cls:"paperforge-version-actions"});D.createEl("button",{cls:"pf-btn-primary",text:i("version_restore_btn")}).addEventListener("click",()=>{or(n,b.key,y.label)?new $.Notice(i("version_restore_done").replace("{label}",y.label)):new $.Notice("Restore failed",6e3)}),b.versions.length>1&&!v&&D.createEl("button",{cls:"pf-btn-secondary",text:i("version_compare_btn")}).addEventListener("click",()=>{f(b,y.label,b.currentLabel)})}},_=s.createEl("div",{cls:"paperforge-version-compare"});_.style.display="none";let f=(b,x,k)=>{let y=lr(n,b.key,x,k);_.style.display="block",_.empty();let v=_.createEl("div",{cls:"paperforge-version-compare-header"});if(v.createEl("span",{cls:"pf-title",text:i("version_compare_title").replace("{vA}",x).replace("{vB}",k)}),v.createEl("span",{cls:"paperforge-meta",text:i("version_compare_paragraphs").replace("{n}",String(y.length))}),y.length===0){_.createEl("div",{cls:"paperforge-meta",text:"No changes"});return}let S=_.createEl("div",{cls:"paperforge-version-diff-list"});for(let R of y){let P=S.createEl("div",{cls:"paperforge-version-diff-row"}),F=R.type==="added"?"[+]":R.type==="removed"?"[-]":"[~]",M=R.heading||"paragraph "+(R.paragraphIndex+1);P.createEl("span",{cls:"paperforge-version-diff-label",text:F+" "+M}),R.oldText&&P.createEl("pre",{cls:"paperforge-version-diff-old",text:R.oldText.slice(0,200)}),R.newText&&P.createEl("pre",{cls:"paperforge-version-diff-new",text:R.newText.slice(0,200)})}},g=e.createEl("div",{cls:"paperforge-version-actions-bar"}),E=g.createEl("button",{cls:"pf-btn-primary",text:i("version_restore_selected")}),m=g.createEl("button",{cls:"pf-btn-secondary",text:i("version_clear_old").replace("{size}","")});c()}renderSearchSection(e){this._searchContainer=e.createEl("div",{cls:"paperforge-search-section"}),this._searchContainer.createEl("div",{cls:"paperforge-search-header"}).createEl("span",{cls:"pf-label",text:"Search"});let r=this._searchContainer.createEl("div",{cls:"paperforge-search-input-row"}),n=r.createEl("span",{cls:"paperforge-search-mode",text:"M"});this._searchInput=r.createEl("input",{cls:"paperforge-search-input",attr:{type:"text",placeholder:"Search papers... (@ for deep search)"}}),this._searchResultsEl=this._searchContainer.createEl("div",{cls:"paperforge-search-results"}),this._searchInput.placeholder=i("retrieval_search_placeholder"),this._searchInput.addEventListener("input",()=>{var s;let a=((s=this._searchInput)==null?void 0:s.value)||"";if(a.startsWith("@")&&!a.startsWith("@ ")?(this._searchMode="@",n.setText("@"),n.addClass("deep"),this._searchInput&&(this._searchInput.placeholder=i("retrieval_search_placeholder_deep"))):(this._searchMode="M",n.setText("M"),n.removeClass("deep"),this._searchInput&&(this._searchInput.placeholder=i("retrieval_search_placeholder"))),clearTimeout(this._searchTimer),!a.trim()){this._searchState="idle",this._searchResults=null,this._searchActiveIndex=-1,this._renderSearchState();return}a.startsWith("@")||(this._searchTimer=setTimeout(()=>{this.executeSearch()},200))}),this._searchInput.addEventListener("keydown",a=>{var s,o;if(a.key==="Escape"){a.preventDefault(),this._searchInput&&(this._searchInput.value="",this._searchInput.blur()),this._searchState="idle",this._searchResults=null,this._searchActiveIndex=-1,this._renderSearchState();return}if(a.key==="ArrowDown"||a.key==="ArrowUp"){if(this._searchState!=="results"||!((s=this._searchResults)!=null&&s.length))return;a.preventDefault();let l=this._searchResults.length;a.key==="ArrowDown"?this._searchActiveIndex=Math.min(this._searchActiveIndex+1,l-1):this._searchActiveIndex=Math.max(this._searchActiveIndex-1,-1);let c=(o=this._searchResultsEl)==null?void 0:o.querySelectorAll(".paperforge-search-result-card");c&&c.forEach((u,h)=>{h===this._searchActiveIndex?(u.setAttribute("aria-selected","true"),u.classList.add("active")):(u.setAttribute("aria-selected","false"),u.classList.remove("active"))});return}if(a.key==="Enter"&&a.ctrlKey){a.preventDefault(),this._searchTimer&&(clearTimeout(this._searchTimer),this._searchTimer=void 0);let l=this._searchMode;this._searchMode="@",this.executeSearch(),this._searchMode=l;return}a.key==="Enter"&&(a.preventDefault(),this._searchTimer&&(clearTimeout(this._searchTimer),this._searchTimer=void 0),this.executeSearch())})}_renderSearchState(){if(!this._searchResultsEl)return;switch(this._searchResultsEl.empty(),this._searchResultsEl.removeAttribute("role"),this._searchResultsEl.removeAttribute("aria-live"),this._searchInput&&(this._searchInput.disabled=!1),this._searchState){case"idle":break;case"searching":{let t=this._searchMode==="@";this._searchResultsEl.createEl("div",{cls:"paperforge-search-loading",text:t?i("retrieval_searching_deep"):i("retrieval_searching_metadata")}),this._searchResultsEl.setAttr("aria-live","polite"),t&&this._searchInput&&(this._searchInput.disabled=!0);break}case"results":{this._searchResultsEl.setAttr("role","listbox"),this._searchResultsEl.setAttr("aria-live","polite"),this._searchResults&&this._renderSearchResultsList(this._searchResults,this._searchMode==="@"),setTimeout(()=>{var r;let t=(r=this._searchResultsEl)==null?void 0:r.querySelector(".paperforge-search-result-card");t&&t instanceof HTMLElement&&t.focus()},100);break}case"empty":{let t=this._searchResultsEl.createEl("div",{cls:"paperforge-search-empty"});t.setAttr("role","alert"),t.createEl("div",{text:i("retrieval_empty")}),t.createEl("div",{cls:"paperforge-search-empty-tips",text:i("retrieval_empty_tips")});break}case"vectors-not-built":{let t=this._searchResultsEl.createEl("div",{cls:"paperforge-search-state-card",attr:{role:"alert"}});t.addClass("warning-soft"),t.createEl("div",{cls:"paperforge-search-state-title",text:i("retrieval_vectors_not_built")}),t.createEl("div",{cls:"paperforge-search-state-desc",text:i("retrieval_vectors_not_built_desc")});let r=t.createEl("button",{cls:"pf-btn-link",text:i("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")}}),setTimeout(()=>{r.focus()},100);break}case"backend-unavailable":{let t=this._searchResultsEl.createEl("div",{cls:"paperforge-search-state-card",attr:{role:"alert"}});t.addClass("error-soft"),t.createEl("div",{cls:"paperforge-search-state-title",text:i("retrieval_backend_unavailable")}),t.createEl("div",{cls:"paperforge-search-state-desc",text:i("retrieval_backend_unavailable_desc")});let r=t.createEl("div",{cls:"paperforge-search-state-actions"}),n=r.createEl("button",{cls:"pf-btn-primary",text:i("retrieval_run_doctor")});n.addEventListener("click",()=>{let s=this.app.vault.adapter.basePath;if(typeof s=="string"){let{path:o,extraArgs:l=[]}=z(s,null,void 0,void 0);(0,be.spawn)(o,[...l,"-m","paperforge","doctor"],{cwd:s,stdio:"inherit"})}}),r.createEl("button",{cls:"pf-btn-secondary",text:i("retrieval_retry")}).addEventListener("click",()=>{this.executeSearch()}),setTimeout(()=>{n.focus()},100);break}case"timeout":{let t=this._searchResultsEl.createEl("div",{cls:"paperforge-search-state-card",attr:{role:"alert"}});t.addClass("warning-soft"),t.createEl("div",{cls:"paperforge-search-state-title",text:i("retrieval_timeout_title")}),t.createEl("div",{cls:"paperforge-search-state-desc",text:i("retrieval_timeout_desc")});let r=t.createEl("button",{cls:"pf-btn-primary",text:i("retrieval_retry")});r.addEventListener("click",()=>{this.executeSearch()}),setTimeout(()=>{r.focus()},100);break}case"model-changed":{let t=this._searchResultsEl.createEl("div",{cls:"paperforge-search-state-card",attr:{role:"alert"}});t.addClass("warning-soft"),t.createEl("div",{cls:"paperforge-search-state-title",text:i("retrieval_model_changed")}),t.createEl("div",{cls:"paperforge-search-state-desc",text:i("retrieval_model_changed_desc")});let r=t.createEl("button",{cls:"pf-btn-primary",text:i("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")}}),setTimeout(()=>{r.focus()},100);break}default:{this._searchResultsEl.createEl("div",{cls:"paperforge-search-state-card",text:i("retrieval_internal_error"),attr:{role:"alert"}}).addClass("error-soft"),setTimeout(()=>{this._searchInput&&this._searchInput.focus()},100);break}}}async executeSearch(){if(!this._searchInput||!this._searchResultsEl)return;let e=this._searchInput.value.trim();if(!e)return;let t=this._searchMode==="@"||e.startsWith("@"),r=t?e.replace(/^@\s*/,"").trim():e;if(!r)return;let n=t?"retrieve":"search";this._searchState="searching",this._searchResults=null,this._searchActiveIndex=-1,this._renderSearchState();let a=this.app.vault.adapter,s="";if(a&&typeof a=="object"&&"basePath"in a){let E=a.basePath;s=typeof E=="string"?E:""}if(!s){this._searchState="backend-unavailable",this._renderSearchState();return}let o=null,c=this.app.plugins;if(c&&typeof c=="object"&&"plugins"in c){let E=c.plugins;if(E&&typeof E=="object"&&"paperforge"in E){let m=E.paperforge;m&&typeof m=="object"&&"settings"in m&&(o=m.settings)}}let{path:u,extraArgs:h=[]}=z(s,o,void 0,void 0),_=n==="retrieve"?["--deep"]:[],f=(0,be.spawn)(u,[...h,"-m","paperforge","--vault",s,n,r,..._,"--json"],{cwd:s,timeout:3e4}),g=[];f.stdout.on("data",E=>{g.push(E.toString("utf-8"))}),f.stderr.on("data",()=>{}),f.on("close",E=>{if(E!==0){let y=mt(String(E));this._searchState=this._mapErrorToSearchState(y.type),this._renderSearchState();return}let m=g.join(""),b=m.indexOf("{"),x=m.lastIndexOf("}"),k="";if(b!==-1&&x>b)k=m.slice(b,x+1);else{let y=m.indexOf("["),v=m.lastIndexOf("]");y!==-1&&v>y&&(k=m.slice(y,v+1))}if(!k){this._searchState="internal-error",this._renderSearchState();return}try{let y=JSON.parse(k),v=[];if(y&&typeof y=="object"&&"data"in y){let S=y.data;if(S&&typeof S=="object"){let R=S;"matches"in R&&Array.isArray(R.matches)&&(v=R.matches)}}this._searchResults=v,this._searchState=v.length>0?"results":"empty",this._renderSearchState()}catch(y){let v=y instanceof Error?y.message:String(y);this._searchState="internal-error",this._renderSearchState()}}),f.on("error",E=>{let m=E.code;if(typeof m=="string"){let b=mt(m);this._searchState=this._mapErrorToSearchState(b.type)}else this._searchState="backend-unavailable";this._renderSearchState()})}_mapErrorToSearchState(e){switch(e){case"vectors_not_built":return"vectors-not-built";case"vectors_corrupted":return"vectors-not-built";case"backend_unavailable":return"backend-unavailable";case"model_changed":return"model-changed";case"timeout":return"timeout";case"no_python":case"python_missing":case"import_failed":case"version_mismatch":return"backend-unavailable";default:return"backend-unavailable"}}_renderSearchResultsList(e,t){if(!this._searchResultsEl)return;if(this._searchResultsEl.setAttr("aria-live","polite"),e.length===0){this._searchResultsEl.createEl("div",{cls:"paperforge-search-empty",text:"No results found."});return}let r=this._searchResultsEl.createEl("div",{cls:"paperforge-search-results-header"});r.createEl("span",{text:i("retrieval_results_count").replace("{n}",String(e.length)).replace("{s}",e.length!==1?"s":"")}).setAttr("aria-live","polite"),r.createEl("span",{cls:"paperforge-search-mode",text:t?"@":"M"});for(let a=0;ax!==null&&typeof x=="object"&&"zotero_key"in x&&x.zotero_key===h);if(b&&typeof b=="object"){let x=b;g=typeof x.main_note_path=="string"&&x.main_note_path?x.main_note_path:typeof x.note_path=="string"&&x.note_path?x.note_path:null}}g?c.addEventListener("click",m=>{let b=m.ctrlKey||m.metaKey;this.app.workspace.openLinkText(g,"",b)}):c.addEventListener("click",()=>{new $.Notice("[!!] Note not found: "+(h||"unknown"),6e3)}),c.addEventListener("keydown",m=>{if(m.key==="Enter"&&g){m.preventDefault();let b=m.ctrlKey||m.metaKey;this.app.workspace.openLinkText(g,"",b)}});let E=c.createEl("div",{cls:"paperforge-search-result-meta"});if(typeof o.first_author=="string"&&o.first_author&&E.createEl("span",{cls:"paperforge-search-result-author",text:o.first_author}),typeof o.journal=="string"&&o.journal&&E.createEl("span",{cls:"paperforge-search-result-journal",text:o.journal}),o.score!==void 0){let m=o.score,b=typeof m=="number"?m.toFixed(3):String(m);E.createEl("span",{cls:"paperforge-search-result-score",text:"Score: "+b})}if(typeof o.domain=="string"&&o.domain&&c.createEl("span",{cls:"paperforge-search-result-tag",text:o.domain}),typeof o.abstract=="string"&&o.abstract){let m=o.abstract;c.createEl("div",{cls:"paperforge-search-result-abstract",text:m.length>200?m.slice(0,200)+"...":m})}if(t&&typeof o.text=="string"&&o.text){let m=o.text;c.createEl("div",{cls:"paperforge-search-result-source",text:m.length>300?m.slice(0,300)+"...":m})}}}_renderSearchError(e){this._searchResultsEl&&(this._searchResultsEl.empty(),this._searchResultsEl.createEl("div",{cls:"paperforge-search-error",text:e}))}_runAction(e,t){var _,f;if(e.disabled){new $.Notice(`[i] ${e.disabledMsg||"This action is not yet available."}`,6e3);return}if(t.classList.contains("running"))return;t.addClass("running");let r=this.app.vault.adapter.basePath;this._showMessage("Processing...","running");let n=Array.isArray(e.args)?[...e.args]:[];if(e.needsKey){let g=this.app.workspace.getActiveFile(),E=null;if(g){let m=this.app.metadataCache.getFileCache(g);if(m&&m.frontmatter&&m.frontmatter.zotero_key?E=m.frontmatter.zotero_key:E=this._extractZoteroKeyFromPath(g.path),E)n=[...n,E];else if(m&&m.frontmatter){this._showMessage("[!!] No zotero_key in active note frontmatter","error"),new $.Notice("[!!] Open a paper note with a zotero_key in its frontmatter first",6e3),t.removeClass("running");return}else{this._showMessage("[!!] No frontmatter in active note","error"),new $.Notice("[!!] The active note has no frontmatter with a zotero_key",6e3),t.removeClass("running");return}}else{this._showMessage("[!!] No active note open","error"),new $.Notice("[!!] Open a paper note with a zotero_key in its frontmatter first",6e3),t.removeClass("running");return}}e.needsFilter&&(n=[...n,"--all"]);let a=e.needsFilter?6e4:e.needsKey?3e4:6e5,{path:s,extraArgs:o=[]}=z(r,(f=(_=this.app.plugins.plugins.paperforge)==null?void 0:_.settings)!=null?f:null,void 0,void 0),l=(0,be.spawn)(s,[...o,"-m","paperforge",e.cmd,...n],{cwd:r,timeout:a}),c=[],u=Date.now(),h=setInterval(()=>this._fetchStats(!0),4e3);l.stdout.on("data",g=>{let E=g.toString("utf-8").split(` +`).filter(Boolean);for(let m of E){let b=m.trim();b&&(c.push(b),this._showMessage(c.slice(-8).join(` +`),"running"))}}),l.stderr.on("data",g=>{let E=g.toString("utf-8").split(` +`).filter(Boolean);for(let m of E){if(m.includes("\r")||m.includes("%")||m.includes("\u2588"))continue;let b=m.trim();b&&!b.match(/^\d+%|^\|/)&&(c.push(b),this._showMessage(c.slice(-8).join(` +`),"running"))}}),l.on("close",g=>{clearInterval(h),t.removeClass("running");let E=((Date.now()-u)/1e3).toFixed(1);if(g!==0){let m=c.slice(-3).join(" | ")||"exit code "+g;(e.cmd==="repair"||e.cmd==="ocr")&&g===1?(this._showMessage("[WARN] "+m,"running"),new $.Notice("[WARN] "+e.cmd+" partial: "+m,8e3),this._fetchStats(!0)):(this._showMessage("[!!] "+m,"error"),new $.Notice("[!!] "+e.cmd+" failed: "+m,8e3))}else if(e.needsKey||e.needsFilter){let m=c.join(` +`);if(m.trim())try{JSON.parse(m),navigator.clipboard.writeText(m).then(()=>{let b=`${E}s \u2014 ${m.length} chars copied`;this._showMessage("[OK] "+e.title+": "+b,"ok"),new $.Notice("[OK] "+e.okMsg+" \u2014 "+m.length+" chars")}).catch(b=>{this._showMessage("[!!] Clipboard write failed: "+b.message,"error"),new $.Notice("[!!] Clipboard error",6e3)})}catch(b){this._showMessage("[!!] Invalid JSON from "+e.title,"error"),new $.Notice("[!!] "+e.title+" returned invalid JSON: "+b.message.slice(0,100),8e3)}else this._showMessage("[!!] No output from context command","error"),new $.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]||"",x=`${E}s \u2014 ${b}`;this._showMessage("[OK] "+e.title+": "+x,"ok"),new $.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)}console.log("[PF] close cmd="+e.cmd+" id="+e.id),e.cmd==="sync"&&Qe(this.app,this.app.plugins.plugins.paperforge,r)}}),l.on("error",g=>{t.removeClass("running"),this._contentEl&&this._contentEl.removeClass("switching"),this._showMessage("[!!] "+g.message,"error"),new $.Notice("[!!] Cannot start: "+g.message,8e3)})}_showMessage(e,t){this._messageEl&&(this._messageEl.setText(e),this._messageEl.className=`paperforge-message msg-${t}`)}_renderModeHeader(e){if(!this._modeContextEl)return;this._modeContextEl.empty();let t=this._modeContextEl.createEl("span",{cls:"paperforge-mode-badge"}),r="";switch(e){case"global":t.addClass("global"),t.setText("Global"),this._headerTitle&&this._headerTitle.setText("PaperForge");break;case"paper":t.addClass("paper"),t.setText("Paper"),this._headerTitle&&this._headerTitle.setText("Paper"),this._currentPaperEntry&&this._currentPaperEntry.title?r=this._currentPaperEntry.title:this._currentPaperKey?(r=this._currentPaperKey,this._modeContextEl.createEl("span",{cls:"paperforge-mode-warning",text:"Not found in index"})):r="Unknown paper";break;case"collection":t.addClass("collection"),t.setText("Collection"),this._headerTitle&&this._headerTitle.setText("Collection"),r=this._currentDomain||"Unknown Domain";break;case"versions":t.addClass("versions"),t.setText(i("version_panel_title")),this._headerTitle&&this._headerTitle.setText(i("version_panel_title"));break}r&&this._modeContextEl.createEl("span",{cls:"paperforge-mode-name",text:r})}_setupEventSubscriptions(){let e=this.app.workspace.on("active-leaf-change",()=>{this._leafChangeTimer&&clearTimeout(this._leafChangeTimer),this._leafChangeTimer=setTimeout(()=>{let r=this._resolveModeForFile(this.app.workspace.getActiveFile()),n=r.mode,a=r.filePath;this._currentMode===n&&this._currentFilePath===a||this._detectAndSwitch()},300)});this._modeSubscribers.push({event:"active-leaf-change",ref:e});let t=this.app.vault.on("modify",r=>{r&&r.path&&r.path.endsWith("formal-library.json")&&(this._invalidateIndex(),this._refreshCurrentMode())});this._modeSubscribers.push({event:"modify",ref:t})}static async open(e){let t=e.app.workspace.getLeavesOfType(Pe);if(t.length>0){e.app.workspace.revealLeaf(t[0]);return}let r=e.app.workspace.getRightLeaf(!1);r&&(await r.setViewState({type:Pe,active:!0}),e.app.workspace.revealLeaf(r))}};var ot=class extends X.Plugin{constructor(){super(...arguments);this._lastExportMtime=0;this._lastOcrMtimes={};this._autoSyncRunning=!1;this._lastSyncTime=null;this._pollTimer=null;this._embedProcess=null;this._embedProgress={current:0,total:0,key:""};this._embedStderr="";this._memoryStatusText=null}async onload(){await this.loadSettings(),this.saveSettings(),Bt(this.app),this.registerView(Pe,t=>new Me(t));try{(0,X.addIcon)(Ne,Tt)}catch(t){}this.addRibbonIcon(Ne,"PaperForge Dashboard",()=>Me.open(this)),ie.find(t=>t.id==="paperforge-ocr-redo")&&this.addRibbonIcon("reset","PaperForge: Redo OCR",()=>{let t=this.app.vault.adapter.basePath;new X.Notice("PaperForge: Redo OCR starting...");let{path:r,extraArgs:n}=z(t,this.settings,void 0,void 0);(0,ge.execFile)(r,[...n,"-m","paperforge","ocr","redo"],{cwd:t,timeout:6e5},(a,s,o)=>{if(a){new X.Notice("PaperForge: Redo OCR failed");return}new X.Notice("PaperForge: Redo OCR done")})}),this.addSettingTab(new it(this.app,this)),this.addCommand({id:"paperforge-status-panel",name:`PaperForge: ${i("guide_open")}`,callback:()=>Me.open(this)});for(let t of ie)this.addCommand({id:t.id,name:`PaperForge: ${t.title}`,callback:()=>{if(t.disabled){new X.Notice(`[i] ${t.disabledMsg||"This action is not yet available."}`,6e3);return}let r=this.app.vault.adapter.basePath;new X.Notice(`PaperForge: running ${t.cmd}...`);let{path:n,extraArgs:a=[]}=z(r,this.settings,void 0,void 0),s=Array.isArray(t.args)?[...t.args]:[];(0,ge.execFile)(n,[...a,"-m","paperforge",t.cmd,...s],{cwd:r,timeout:3e5},(o,l,c)=>{if(o){new X.Notice(`[!!] ${t.cmd} failed: ${(c||o.message).slice(0,120)}`,8e3);return}new X.Notice(`[OK] ${t.okMsg||l.trim().split(` +`)[0].slice(0,80)}`)})}});this.settings.auto_update_on_startup===!0&&this.settings.setup_complete&&setTimeout(()=>this._autoUpdate(),3e3),this._startFilePolling(),this._firstLaunchSnapshotMigration(),this._checkReleaseNotes()}_firstLaunchSnapshotMigration(){let e=this.app.vault.adapter.basePath;if(!e)return;let r=ce(e).memoryStatePath;if(!q.existsSync(r)){let n=z(e,this.settings,void 0,void 0);[["runtime-health","--json"],["memory","status","--json"],["embed","status","--json"]].forEach(s=>{let o=[...n.extraArgs,"-m","paperforge","--vault",e,...s];(0,ge.execFile)(n.path,o,{cwd:e,timeout:6e4,windowsHide:!0},()=>{})})}}_autoUpdate(){let e=this.app.vault.adapter.basePath,{path:t,extraArgs:r=[]}=z(e,this.settings,void 0,void 0),n=this.manifest.version,a=`paperforge==${n}`,s=`git+https://github.com/LLLin000/PaperForge.git@${n}`,o=(l,c)=>{(0,ge.spawn)(t,[...r,"-m","pip","install","--upgrade",l],{cwd:e,timeout:12e4,env:Ce()}).on("close",h=>c(h===0))};(0,ge.execFile)(t,[...r,"-c","import paperforge; print(paperforge.__version__)"],{cwd:e,timeout:1e4},(l,c)=>{let u=_=>{console.log(`[PaperForge] Auto-update: trying PyPI (paperforge==${n})`),o(a,f=>{if(f){console.log("[PaperForge] Auto-update: installed via PyPI"),new X.Notice(`[OK] PaperForge CLI ${_}`,5e3);return}console.warn("[PaperForge] Auto-update: PyPI failed, falling back to git..."),o(s,g=>{g&&(console.log("[PaperForge] Auto-update: installed via git"),new X.Notice(`[OK] PaperForge CLI ${_} (via git)`,5e3))})})};if(l){u("installed");return}let h=c.trim();h!==n&&u(`${h} -> ${n}`)})}_startFilePolling(){let e=this.app.vault.adapter.basePath;this._pollTimer=setInterval(()=>{this._checkExports(e),this._checkOcr(e)},12e4)}_checkExports(e){if(this._autoSyncRunning)return;let t=ce(e).exportsDir;if(!q.existsSync(t))return;let r=0;try{q.readdirSync(t).forEach(n=>{if(!n.endsWith(".json"))return;let a=q.statSync(Ie.join(t,n));a.mtimeMs>r&&(r=a.mtimeMs)})}catch(n){return}r>this._lastExportMtime&&(this._lastExportMtime=r,this._autoSync(e))}_autoSync(e){if(this._autoSyncRunning)return;this._autoSyncRunning=!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,ge.exec)(r,{timeout:12e4,encoding:"utf-8"},(n,a,s)=>{this._autoSyncRunning=!1,this._memoryStatusText=null,n||(this._lastSyncTime=new Date().toLocaleTimeString());try{let o=ce(e).exportsDir,l=0;q.readdirSync(o).forEach(c=>{c.endsWith(".json")&&(l=Math.max(l,q.statSync(Ie.join(o,c)).mtimeMs))}),this._lastExportMtime=l}catch(o){}})}_checkOcr(e){if(this._autoSyncRunning)return;let t=ce(e).ocrDir;if(q.existsSync(t))try{q.readdirSync(t,{withFileTypes:!0}).forEach(r=>{if(!r.isDirectory())return;let n=Ie.join(t,r.name,"meta.json");if(!q.existsSync(n))return;let a=q.statSync(n),s=this._lastOcrMtimes[r.name]||0;if(a.mtimeMs<=s||(this._lastOcrMtimes[r.name]=a.mtimeMs,this._autoSyncRunning))return;this._autoSyncRunning=!0;let o=z(e,this.settings,void 0,void 0);if(!o.path){this._autoSyncRunning=!1;return}let l=`"${o.path}" -m paperforge --vault "${e}" sync`;(0,ge.exec)(l,{timeout:3e4,encoding:"utf-8"},()=>{this._autoSyncRunning=!1,this._memoryStatusText=null})})}catch(r){}}readPaperforgeJson(){let e=this.app.vault.adapter.basePath,t=Ie.join(e,"paperforge.json"),r={system_dir:"System",resources_dir:"Resources",literature_dir:"Literature",base_dir:"Bases"};try{if(!q.existsSync(t))return r;let n=q.readFileSync(t,"utf-8"),a=JSON.parse(n),s=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}}catch(n){return console.warn("PaperForge: Failed to read paperforge.json, using defaults",n),r}}savePaperforgeJson(e){let t=this.app.vault.adapter.basePath,r=Ie.join(t,"paperforge.json"),n={};try{q.existsSync(r)&&(n=JSON.parse(q.readFileSync(r,"utf-8")))}catch(s){console.warn("PaperForge: Failed to read paperforge.json for update",s)}(!n.vault_config||typeof n.vault_config!="object")&&(n.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];try{if(q.writeFileSync(r,JSON.stringify(n,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}}catch(s){console.error("PaperForge: Failed to write paperforge.json",s),new X.Notice("PaperForge: Failed to save configuration to paperforge.json")}}onunload(){this._pollTimer&&clearInterval(this._pollTimer),this.app.workspace.detachLeavesOfType(Pe)}async loadSettings(){this.settings=Object.assign({},Ve,await this.loadData()),this.settings.features&&Ve.features&&(this.settings.features=Object.assign({},Ve.features,this.settings.features||{})),this.settings.frozen_skills||(this.settings.frozen_skills={});let e=this.readPaperforgeJson();if(this.settings.system_dir=e.system_dir,this.settings.resources_dir=e.resources_dir,this.settings.literature_dir=e.literature_dir,this.settings.base_dir=e.base_dir,this.settings.python_path&&this.settings.python_path.trim()){let t=this.settings.python_path.trim();q.existsSync(t)?this.settings._python_path_stale=!1:(console.warn(`PaperForge: Saved python_path "${t}" no longer exists - showing stale warning`),this.settings._python_path_stale=!0)}}async saveSettings(){let e={};for(let t of Object.keys(Ve))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=(ft().versions||[]).find(o=>o.version===e);class s extends X.Modal{constructor(l,c){super(l),this._entry=c}onOpen(){let{contentEl:l}=this;if(l.createEl("h2",{text:`PaperForge v${e} \u66F4\u65B0\u8BF4\u660E`}),this._entry){if(l.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",{text:"\u884C\u4E3A\u53D8\u66F4 / \u8FC1\u79FB\u6CE8\u610F"});for(let c of this._entry.breaking_or_migration)l.createEl("p",{text:`\u2022 ${c}`,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}`,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}`,cls:"paperforge-modal-item"})}if(this._entry.recommended_actions&&this._entry.recommended_actions.length>0){let c=l.createEl("div",{cls:"paperforge-release-recommended"});c.createEl("h4",{text:"\u5EFA\u8BAE\u64CD\u4F5C",cls:""}),c.style.marginBottom="8px";for(let u of this._entry.recommended_actions)c.createEl("p",{text:`\u2022 ${u}`,cls:"paperforge-release-item-bold"})}}else l.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 X.Setting(l).addButton(c=>c.setButtonText("\u77E5\u9053\u4E86").setCta().onClick(()=>{this.close()}))}onClose(){let{contentEl:l}=this;l.empty()}}new s(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 a15f2870..a4c9a5c0 100644 --- a/paperforge/plugin/src/i18n.ts +++ b/paperforge/plugin/src/i18n.ts @@ -346,7 +346,16 @@ const LANG: Record> = { module_detail_open_installation: "Open Installation", module_detail_open_help: "Help & Docs", module_detail_open_maintenance: "Maintenance", + module_detail_open_library: "Open Library", + module_detail_open_ocr: "Open OCR", + module_detail_open_memory: "Open Memory", + action_unknown_pair: "Unknown action: {verb}", + ocr_stop_batch: "Stop OCR batch", + runtime_not_available: "No Python runtime available", md_unavailable_module: "Not available yet", + library_detail_heading: "Library Details", + ocr_detail_heading: "OCR Details", + memory_detail_heading: "Memory Details", managed_runtime_status: "Runtime Status", managed_runtime_install: "Install Runtime", managed_runtime_repair: "Repair Runtime", @@ -412,6 +421,22 @@ const LANG: Record> = { cc_reason_help_ready: "Help documentation is available.", cc_reason_docs_missing: "Help documentation is not yet installed.", cc_reason_placeholder: "Detection pending — will show live status once connected.", + cc_reason_library_ready: "Library is synced and indexed.", + cc_reason_library_config_missing: "Configuration not found — run setup to configure library.", + cc_reason_library_config_corrupt: "Configuration file is corrupt — library cannot proceed.", + cc_reason_library_zotero_missing: "Zotero data directory is not configured.", + cc_reason_library_zotero_not_found: "Zotero data directory path does not exist.", + cc_reason_library_index_missing: "Library index has not been built yet — run sync.", + cc_reason_library_index_stale: "Library index is stale — sync to refresh.", + cc_reason_ocr_ready: "OCR pipeline is configured and functional.", + cc_reason_ocr_config_missing: "Configuration not found — run setup to configure OCR.", + cc_reason_ocr_config_corrupt: "Configuration file is corrupt — OCR cannot proceed.", + cc_reason_ocr_api_key_missing: "No OCR API key configured — add one in setup.", + cc_reason_ocr_artifacts_missing: "No OCR output found — run OCR on papers.", + cc_reason_memory_ready: "Memory database is healthy and indexed.", + cc_reason_memory_db_missing: "Memory database not built — run memory build.", + cc_reason_memory_db_corrupt: "Memory database is corrupted — restore from backup.", + cc_reason_memory_index_stale: "Memory index needs rebuild to match current library.", cc_diag_module: "Module", cc_diag_state: "State", cc_diag_severity: "Severity", @@ -706,7 +731,16 @@ const LANG: Record> = { module_detail_open_installation: "打开安装", module_detail_open_help: "帮助与文档", module_detail_open_maintenance: "维护", + module_detail_open_library: "打开文献库", + module_detail_open_ocr: "打开 OCR", + module_detail_open_memory: "打开记忆", + action_unknown_pair: "未知操作: {verb}", + ocr_stop_batch: "停止 OCR 批处理", + runtime_not_available: "Python 运行时不可用", md_unavailable_module: "暂不可用", + library_detail_heading: "文献库详情", + ocr_detail_heading: "OCR 详情", + memory_detail_heading: "记忆层详情", managed_runtime_status: "运行时状态", managed_runtime_install: "安装运行时", managed_runtime_repair: "修复运行时", diff --git a/paperforge/plugin/src/settings.ts b/paperforge/plugin/src/settings.ts index 256ceecb..c8251667 100644 --- a/paperforge/plugin/src/settings.ts +++ b/paperforge/plugin/src/settings.ts @@ -116,6 +116,8 @@ export class PaperForgeSettingTab extends PluginSettingTab { private _managedRuntime: ManagedRuntime | null = null; /** True while a runtime operation is in flight. */ private _runtimeBusy: boolean = false; + /** True while a library sync or memory build is in flight. */ + _libraryRunning: boolean = false; constructor(app: App, plugin: ISettingPlugin) { super(app, plugin as any); @@ -247,7 +249,7 @@ export class PaperForgeSettingTab extends PluginSettingTab { const env = this._capabilityState?.[mod]; if (env && env.capability_state === "unknown" && env.updated_at === new Date(0).toISOString() && !this._attemptedProbes.has(mod)) { this._attemptedProbes.add(mod); - if (mod === "installation" || mod === "help") { + if (mod !== "maintenance") { this._probeModule(mod); } } @@ -847,6 +849,12 @@ export class PaperForgeSettingTab extends PluginSettingTab { } if (this._selectedDetailModule === "installation") { this._renderInstallationDetail(containerEl); + } else if (this._selectedDetailModule === "library") { + this._renderLibraryDetail(containerEl); + } else if (this._selectedDetailModule === "ocr") { + this._renderOcrDetail(containerEl); + } else if (this._selectedDetailModule === "memory") { + this._renderMemoryDetail(containerEl); } else { // Fallback to installation this._selectedDetailModule = "installation"; @@ -854,6 +862,475 @@ export class PaperForgeSettingTab extends PluginSettingTab { } } + + /** Render the Library detail view (Issue #78). */ + _renderLibraryDetail(containerEl: HTMLElement): void { + this._renderModuleDetailShell(containerEl, "library"); + // Library detail surface consumes the shared envelope shell — no duplicate CTA. + } + + /** Render the OCR detail view (Issue #78). */ + _renderOcrDetail(containerEl: HTMLElement): void { + this._renderModuleDetailShell(containerEl, "ocr"); + // ── Owner controls: cooperative stop only when _ocrProcess exists ── + const isRunning = this.plugin._ocrProcess != null; + if (isRunning) { + const ctrl = containerEl.createEl("div", { cls: "pf-detail-controls" }); + const stopBtn = ctrl.createEl("button", { + cls: "mod-warning", + text: t("ocr_stop_batch") || "Stop OCR batch", + }); + stopBtn.addEventListener("click", () => { + const child = this.plugin._ocrProcess as unknown as { + stdin?: { write: (_: string) => boolean }; + kill?: (_: string) => void; + }; + if (child?.stdin?.write) { + child.stdin.write("PAPERFORGE_STOP\n"); + this.plugin._ocrWasStopped = true; + } else if (child?.kill) { + child.kill("SIGINT"); + } + }); + const prog = this.plugin._ocrProgress; + if (prog && prog.total > 0) { + ctrl.createEl("span", { + cls: "pf-detail-progress", + text: `${prog.current}/${prog.total} papers`, + }); + } + } + } + /** Render the Memory detail view (Issue #78). */ + _renderMemoryDetail(containerEl: HTMLElement): void { + this._renderModuleDetailShell(containerEl, "memory"); + // Memory detail surface consumes the shared envelope shell — no duplicate CTA. + } + /** Dispatch a backend action command through exact (verb, command) allowlist (Issue #78). */ + _dispatchModuleAction(mod: CapabilityModule, env: ProbeEnvelope): void { + const primary = env.action?.primary; + if (!primary) { + this._probeModule(mod); + return; + } + const verb = primary.verb; + const cmd = primary.command ?? ""; + + // Destructive confirmation from backend envelope + if (primary.destructive && primary.confirmation_required) { + const prompt = primary.confirmation_prompt ?? "Proceed?"; + if (!confirm(prompt)) return; + } + + // Setup/set_config verbs → exact command allowlist + if ((verb === "setup" || verb === "set_config") && cmd === "paperforge setup") { + if (mod === "installation" || mod === "library" || mod === "ocr") { + const probeMods: CapabilityModule[] = [mod]; + if (mod === "installation") { + probeMods.push("help"); + } + new PaperForgeSetupModal(this.app, this.plugin, () => { + for (const m of probeMods) this._probeModule(m); + }).open(); + return; + } + } + + // Probe verb → exact command match, directly re-probe without Notice + if (verb === "probe" && cmd === "probe " + mod) { + this._probeModule(mod); + return; + } + + // Exact (verb, command) allowlist per module + if (mod === "installation") { + // setup/set_config handled above + } else if (mod === "library") { + if (verb === "sync" && cmd === "paperforge sync") { + this._runManualSync(); + return; + } + // setup/set_config handled above + } else if (mod === "ocr") { + if (verb === "run" && cmd === "paperforge ocr run") { + this._dispatchOcrAction("run"); + return; + } + if (verb === "rebuild_derived" && cmd === "paperforge ocr rebuild --all") { + this._dispatchOcrAction("rebuild"); + return; + } + if (verb === "redo" && cmd === "paperforge ocr redo") { + this._dispatchOcrAction("redo"); + return; + } + if (verb === "investigate") { + if (cmd === "paperforge ocr doctor") { + this._callPython(["ocr", "doctor"], { + timeout: 30000, + onClose: (_code: number | null) => { + this._probeModule("ocr"); + this.display(); + }, + }); + return; + } + if (cmd === "paperforge ocr list --json") { + this._callPython(["ocr", "list", "--json"], { + timeout: 30000, + onClose: (_code: number | null) => { + this._probeModule("ocr"); + this.display(); + }, + }); + return; + } + } + // setup/set_config handled above + } else if (mod === "memory") { + if ((verb === "run" || verb === "rebuild_index") && cmd === "paperforge memory build") { + this._dispatchMemoryBuild("build"); + return; + } + if (verb === "rebuild_index" && cmd === "paperforge embed build --force") { + this._dispatchMemoryBuild("embed"); + return; + } + if (verb === "restore_backup" && cmd === "paperforge memory restore-backup") { + this._callPython(["memory", "restore-backup"], { + timeout: 30000, + onClose: (_code: number | null) => { + this._probeModule("memory"); + this.display(); + }, + }); + return; + } + } + + // Unknown pair → Notice + re-probe + new Notice( + (t("action_unknown_pair") || "Unknown action: {verb}").replace("{verb}", verb), + 5000, + ); + this._probeModule(mod); + }/** Dispatch OCR action with exact CLI args, progress tracking, cooperative stop (Issue #78). */ + _dispatchOcrAction(mode: "run" | "rebuild" | "redo"): void { + const vp = (this.app.vault.adapter as any).basePath as string; + const resolved = this._resolveRuntimeCommand(vp); + if (!resolved) { + new Notice(t("runtime_not_available") || "No Python runtime available"); + return; + } + + // Map mode to exact CLI args + const cliArgs: string[] = mode === "run" + ? ["ocr", "run"] + : mode === "rebuild" + ? ["ocr", "rebuild", "--all"] + : ["ocr", "redo"]; + const labelMap: Record = { + run: "Running OCR…", + rebuild: "Rebuilding OCR derived artifacts…", + redo: "Running OCR redo…", + }; + + // Set envelope activity overlay without changing capability/severity/reason + const envelopes = this._capabilityState ?? {}; + if (envelopes["ocr"]) { + envelopes["ocr"].activity_state = "running"; + envelopes["ocr"].activity_label = labelMap[mode] || "Running…"; + envelopes["ocr"].activity_progress = { current: 0, total: 1 }; + } + this.plugin._ocrBuffer = ""; + this.plugin._ocrProgress = { current: 0, total: 1, key: "" }; + this.plugin._ocrWasStopped = false; + this.display(); + + const child = this._callPython( + cliArgs, + { + stream: true, + onData: (data: unknown) => { + const text = typeof data === "string" ? data : Buffer.isBuffer(data) ? data.toString("utf-8") : String(data); + 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 || 1; + } + if (envelopes["ocr"]) { + envelopes["ocr"].activity_progress = { current: 0, total: ev.total || 1 }; + } + } else if (ev.event === "PROGRESS") { + this.plugin._ocrProgress = { current: ev.current || 0, total: ev.total || 1, key: ev.key || "" }; + if (envelopes["ocr"]) { + envelopes["ocr"].activity_progress = { current: ev.current || 0, total: ev.total || 1 }; + } + } + } + this.display(); + }, + onError: (err: Error) => { + this.plugin._ocrProcess = null; + if (envelopes["ocr"]) { + envelopes["ocr"].activity_state = "idle"; + envelopes["ocr"].activity_label = null; + envelopes["ocr"].activity_progress = null; + } + new Notice("OCR error: " + (err.message || err), 8000); + this._probeModule("ocr"); + this.display(); + }, + onClose: (code: number | null) => { + this.plugin._ocrProcess = null; + if (envelopes["ocr"]) { + envelopes["ocr"].activity_state = "idle"; + envelopes["ocr"].activity_label = null; + envelopes["ocr"].activity_progress = null; + } + if (code === 0) { + new Notice(mode === "run" ? "OCR run complete." : mode === "rebuild" ? "OCR rebuild complete." : "OCR redo complete."); + } else if (code === 130 || this.plugin._ocrWasStopped) { + this.plugin._ocrWasStopped = false; + new Notice("OCR batch stopped by user."); + } else { + new Notice("OCR operation failed with exit code " + (code ?? "?"), 8000); + } + // Terminal re-probe + this._probeModule("ocr"); + this.display(); + }, + }, + ); + this.plugin._ocrProcess = child; + } /** Dispatch memory build: distinct build vs embed modes, overlay activity, terminal re-probe (Issue #78). */ + _dispatchMemoryBuild(kind: "build" | "embed"): void { + const vp = (this.app.vault.adapter as any).basePath as string; + // Set activity overlay on Memory + const envelopes = this._capabilityState ?? {}; + if (envelopes["memory"]) { + envelopes["memory"].activity_state = "running"; + envelopes["memory"].activity_label = kind === "embed" ? "Building vector index…" : "Building memory…"; + } + this.display(); + + const cliArgs = kind === "embed" ? ["embed", "build", "--force"] : ["memory", "build"]; + const label = kind === "embed" ? "Vector index" : "Memory"; + + if (kind === "embed") { + // Embed build: stream progress + this.plugin._embedBuffer = ""; + this.plugin._embedProgress = { current: 0, total: 0, key: "" }; + const child = this._callPython( + cliArgs, + { + stream: true, + onData: (data: unknown) => { + const text = typeof data === "string" ? data : Buffer.isBuffer(data) ? data.toString("utf-8") : String(data); + const { events, buffer } = processProgressChunk(text, this.plugin._embedBuffer ?? ""); + this.plugin._embedBuffer = buffer; + for (const ev of events) { + if (ev.event === "PROGRESS") { + this.plugin._embedProgress = { current: ev.current || 0, total: ev.total || 0, key: ev.key || "" }; + if (envelopes["memory"]) { + envelopes["memory"].activity_progress = { current: ev.current || 0, total: ev.total || 1 }; + } + } + } + this.display(); + }, + onError: (err: Error) => { + this.plugin._embedProcess = null; + if (envelopes["memory"]) { + envelopes["memory"].activity_state = "idle"; + envelopes["memory"].activity_label = null; + envelopes["memory"].activity_progress = null; + } + new Notice(label + " build error: " + (err.message || err), 8000); + this._probeModule("memory"); + this.display(); + }, + onClose: (code: number | null) => { + this.plugin._embedProcess = null; + if (envelopes["memory"]) { + envelopes["memory"].activity_state = "idle"; + envelopes["memory"].activity_label = null; + envelopes["memory"].activity_progress = null; + } + if (code === 0) { + new Notice(label + " build complete."); + } else { + new Notice(label + " build failed with exit code " + (code ?? "?"), 8000); + } + this._probeModule("memory"); + this.display(); + }, + }, + ); + this.plugin._embedProcess = child; + } else { + // Memory build: timeout-based (no streaming) + this._callPython(cliArgs, { + timeout: 120000, + onClose: (code: number | null, _stdout: string, stderr: string) => { + if (envelopes["memory"]) { + envelopes["memory"].activity_state = "idle"; + envelopes["memory"].activity_label = null; + } + if (code === 0) { + new Notice(label + " rebuild complete"); + } else { + new Notice( + label + " build failed" + (stderr ? ": " + stderr.slice(0, 120) : ""), + 8000, + ); + } + this._probeModule("memory"); + this.display(); + }, + }); + } + }/** Shared module detail shell for Library, OCR, and Memory (Issue #78). */ + _renderModuleDetailShell(containerEl: HTMLElement, mod: CapabilityModule): void { + const headingKey = mod + "_detail_heading"; + const headingId = "pf-" + mod + "-detail-heading"; + + // ── Back button ── + const backBtn = containerEl.createEl("button", { + cls: "pf-back-btn", + text: t("btn_back_to_overview"), + }); + backBtn.addEventListener("click", () => { + this.activeTab = "overview"; + this._selectedDetailModule = ""; + this._focusTargetId = "button.pf-open-module-btn[data-module=" + mod + "]"; + this.display(); + }); + + // ── Heading ── + const heading = containerEl.createEl("h2", { + cls: "pf-module-detail-heading", + text: t(headingKey) || t("cc_module_" + mod), + attr: { id: headingId, tabindex: "-1" }, + }); + + // ── Module detail selector (all implemented modules) ── + const detailModules: { id: string; labelKey: string }[] = [ + { id: "installation", labelKey: "md_select_installation" }, + { id: "library", labelKey: "md_select_library" }, + { id: "ocr", labelKey: "md_select_ocr" }, + { id: "memory", labelKey: "md_select_memory" }, + ]; + const selector = containerEl.createEl("div", { cls: "pf-module-detail-selector" }); + for (const dm of detailModules) { + const btn = selector.createEl("button", { + cls: "pf-module-detail-btn" + + (dm.id === mod ? " pf-module-detail-btn--active" : ""), + text: t(dm.labelKey), + }); + btn.addEventListener("click", () => { + this._selectedDetailModule = dm.id; + this._focusTargetId = dm.id === "installation" + ? "#pf-installation-detail-heading" + : "#pf-" + dm.id + "-detail-heading"; + this.display(); + }); + } + + // ── Backend envelope summary card ── + const envelopes: Record = this._capabilityState ?? {}; + const env: ProbeEnvelope = envelopes[mod] ?? createUnknownEnvelope(mod); + const sevClass: string = this._sevClass(env.severity); + const isReady: boolean = isReadyEnvelope(env); + + const summaryRow = containerEl.createEl("div", { cls: "pf-cc-card pf-module-detail-card" }); + const summaryHeader = summaryRow.createEl("div", { cls: "pf-cc-card-header" }); + summaryHeader.createEl("span", { cls: "pf-cc-card-name", text: t("cc_module_" + mod) }); + summaryHeader.createEl("span", { + cls: "pf-cc-card-badge pf-cc-card-badge--" + sevClass, + text: t(this._ccBadgeKey(env, mod)), + }); + + // Activity label + if (env.activity_state === "running" && env.activity_label) { + const activityRow = summaryRow.createEl("div", { cls: "pf-cc-card-activity", attr: { "aria-live": "polite" } }); + activityRow.createEl("span", { text: env.activity_label }); + if (env.activity_progress && env.activity_progress.total > 0) { + const pct = Math.round((env.activity_progress.current / env.activity_progress.total) * 100); + const bar = activityRow.createEl("div", { cls: "pf-cc-card-progress", attr: { role: "progressbar", "aria-valuenow": String(env.activity_progress.current), "aria-valuemin": "0", "aria-valuemax": String(env.activity_progress.total) } }); + const fill = bar.createEl("div", { cls: "pf-cc-card-progress-fill" }); + fill.style.width = pct + "%"; + } + } + + const l10nReason = this._localizeReason(env.reason.code, mod); + summaryRow.createEl("div", { cls: "pf-cc-card-reason", text: l10nReason ?? env.reason.text }); + + // Destructive metadata before action + const primary = env.action?.primary; + if (primary && !isReady) { + if (primary.destructive && primary.confirmation_required) { + const destructiveRow = summaryRow.createEl("div", { cls: "pf-destructive-notice" }); + destructiveRow.createEl("span", { text: primary.destructive_effect ?? "" }); + } + + // Action button — disabled while this module's activity is running + const isModuleRunning = env.activity_state === "running"; + const action = classifyCapabilityAction(env); + const actionBtn = summaryRow.createEl("button", { + cls: "pf-cc-card-action pf-cc-card-action--primary", + text: action.label, + }); + if (isModuleRunning) { + actionBtn.setAttr("disabled", "disabled"); + } + actionBtn.addEventListener("click", () => { + if (isModuleRunning) return; + this._dispatchModuleAction(mod, env); + }); + } + + // Timestamp and TTL + const metaRow = summaryRow.createEl("div", { cls: "pf-meta" }); + let dateLabel: string; + try { dateLabel = new Date(env.updated_at).toLocaleString(); } catch { dateLabel = env.updated_at; } + metaRow.createEl("span", { text: t("cc_diag_updated") + ": " + dateLabel + " | TTL: " + String(env.ttl_seconds) + "s" }); + + // Notices + if (env.notices && env.notices.length > 0) { + for (const notice of env.notices) { + containerEl.createEl("div", { + cls: "pf-notice pf-notice--" + (notice.level || "info"), + text: notice.message, + }); + } + } + + // Diagnostics disclosure + const details = summaryRow.createEl("details", { cls: "pf-cc-card-diagnostic" }); + details.createEl("summary", { text: t("cc_diagnostic_toggle") }); + const body = details.createEl("div", { cls: "pf-cc-card-diagnostic-body" }); + const stateLabel = t("cc_state_" + env.capability_state) || env.capability_state; + const sevLabel = t("cc_severity_" + env.severity) || env.severity; + const activityLabel = t("cc_activity_" + env.activity_state) || env.activity_state; + body.createEl("div", { text: t("cc_diag_module") + ": " + env.module }); + body.createEl("div", { text: t("cc_diag_state") + ": " + stateLabel }); + body.createEl("div", { text: t("cc_diag_severity") + ": " + sevLabel }); + body.createEl("div", { text: t("cc_diag_activity") + ": " + activityLabel }); + const reasonRow = body.createEl("div"); + reasonRow.appendText(t("cc_diag_reason") + ": " + (l10nReason ?? env.reason.text) + " "); + reasonRow.createEl("code", { text: env.reason.code }); + + // Focus heading on render + try { + heading.focus(); + } catch { + // ignore focus failure + } + } + /** Render the Help tab (top-level destination with docs + release notes). */ _renderHelpTab(containerEl: HTMLElement): void { const envelopes: Record = this._capabilityState ?? {}; @@ -1119,6 +1596,13 @@ export class PaperForgeSettingTab extends PluginSettingTab { const py = getCachedPython(vp, this.plugin.settings); if (!py.path) return; + // Overlay envelope activity + const envelopes = this._capabilityState ?? {}; + if (envelopes["library"]) { + envelopes["library"].activity_state = "running"; + envelopes["library"].activity_label = "Syncing library…"; + } + const statusRow = document.querySelector(".paperforge-memory-status"); if (statusRow) { this._renderMemoryStatusText( @@ -1129,15 +1613,25 @@ export class PaperForgeSettingTab extends PluginSettingTab { } this.plugin._autoSyncRunning = true; + this._libraryRunning = true; + this.display(); this._callPython(["sync"], { timeout: 120000, onClose: (code: number | null) => { this.plugin._autoSyncRunning = false; + this._libraryRunning = false; this._memoryStatusText = null; + // Clear activity overlay + if (envelopes["library"]) { + envelopes["library"].activity_state = "idle"; + envelopes["library"].activity_label = null; + } if (code === 0) { this._lastSyncTime = new Date().toLocaleTimeString(); this.plugin._lastSyncTime = this._lastSyncTime; } + // Re-probe library on every terminal outcome — pass exit code for sync failure detection + this._probeModule("library", code ?? 1); this.display(); this._refreshSnapshots(vp); checkOrphanState(this.app, this.plugin, vp); @@ -2832,7 +3326,7 @@ export class PaperForgeSettingTab extends PluginSettingTab { } /** Call `paperforge probe --json` and store the validated envelope unchanged. */ - _probeModule(mod: CapabilityModule): void { + _probeModule(mod: CapabilityModule, lastOperationExitCode?: number): void { if (this._probing.has(mod)) return; this._probing.add(mod); @@ -2895,6 +3389,9 @@ export class PaperForgeSettingTab extends PluginSettingTab { mod, "--json", ]; + if (mod === "library" && lastOperationExitCode != null && lastOperationExitCode !== 0) { + args.push("--last-operation-exit-code", String(lastOperationExitCode)); + } execFile( resolved.path, @@ -2974,9 +3471,9 @@ export class PaperForgeSettingTab extends PluginSettingTab { } /** Modules with real Python probe support. */ - private static _REAL_PROBE = new Set(["installation", "help"]); + private static _REAL_PROBE = new Set(["installation", "library", "ocr", "memory", "help"]); /** Modules that have a navigation entry in the overview card grid. */ - private static _NAVIGABLE = new Set(["installation", "maintenance", "help"]); + private static _NAVIGABLE = new Set(["installation", "library", "ocr", "memory", "maintenance", "help"]); _renderCard(container: HTMLElement, mod: CapabilityModule, envelope: ProbeEnvelope): void { const env = envelope; @@ -2995,11 +3492,17 @@ export class PaperForgeSettingTab extends PluginSettingTab { // Navigation entry — Enter/Space or click opens module detail const openLabel = mod === "installation" ? t("module_detail_open_installation") - : mod === "help" - ? t("module_detail_open_help") - : mod === "maintenance" - ? t("module_detail_open_maintenance") - : t("md_select_installation"); + : mod === "library" + ? t("module_detail_open_library") + : mod === "ocr" + ? t("module_detail_open_ocr") + : mod === "memory" + ? t("module_detail_open_memory") + : mod === "help" + ? t("module_detail_open_help") + : mod === "maintenance" + ? t("module_detail_open_maintenance") + : t("md_select_installation"); const navBtn = nameArea.createEl("button", { cls: "pf-open-module-btn", text: t("cc_module_" + mod), @@ -3063,7 +3566,7 @@ export class PaperForgeSettingTab extends PluginSettingTab { this._probeModule("help"); }).open(); } else { - this._probeModule(mod); + this._dispatchModuleAction(mod, env); } }); } @@ -3104,6 +3607,18 @@ export class PaperForgeSettingTab extends PluginSettingTab { this.activeTab = "module-detail"; this._selectedDetailModule = "installation"; this._focusTargetId = "#pf-installation-detail-heading"; + } else if (mod === "library") { + this.activeTab = "module-detail"; + this._selectedDetailModule = "library"; + this._focusTargetId = "#pf-library-detail-heading"; + } else if (mod === "ocr") { + this.activeTab = "module-detail"; + this._selectedDetailModule = "ocr"; + this._focusTargetId = "#pf-ocr-detail-heading"; + } else if (mod === "memory") { + this.activeTab = "module-detail"; + this._selectedDetailModule = "memory"; + this._focusTargetId = "#pf-memory-detail-heading"; } else if (mod === "help") { this.activeTab = "help"; this._selectedDetailModule = ""; diff --git a/paperforge/plugin/styles.css b/paperforge/plugin/styles.css index 2c6786cc..be37c740 100644 --- a/paperforge/plugin/styles.css +++ b/paperforge/plugin/styles.css @@ -4766,12 +4766,55 @@ } /* ── Installation Detail ── */ -.pf-installation-detail-heading { +.pf-installation-detail-heading, +.pf-library-detail-heading, +.pf-ocr-detail-heading, +.pf-memory-detail-heading { font-size: 16px; font-weight: 600; margin: 0 0 12px 0; } +.pf-notice { + padding: 6px 10px; + border-radius: var(--radius-s); + border: 1px solid var(--background-modifier-border); + margin-bottom: 8px; + color: var(--text-muted); +} +.pf-notice--info { + background: color-mix(in srgb, var(--text-accent) 8%, transparent); +} +.pf-notice--warning { + background: color-mix(in srgb, var(--text-warning) 8%, transparent); + border-color: var(--text-warning); + color: var(--text-warning); +} +.pf-notice--error { + background: color-mix(in srgb, var(--text-error) 8%, transparent); + border-color: var(--text-error); + color: var(--text-error); +} + +/* ── Module Detail Controls ── */ +.pf-detail-controls { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-top: 12px; + padding: 10px 0; +} +.pf-detail-controls .mod-cta { + font-weight: 500; +} +.pf-detail-status { + font-size: 12px; + color: var(--text-muted); + margin-top: 6px; + width: 100%; +} + .pf-runtime-status-card { background: var(--background-primary); border: 1px solid var(--background-modifier-border); @@ -4877,3 +4920,24 @@ margin-top: 2px; font-style: italic; } + + +/* ── Module Detail heading responsive wrap ── */ +.pf-module-detail-heading { + font-size: 18px; + font-weight: 600; + line-height: 1.35; + margin: 0 0 12px 0; + word-break: break-word; + overflow-wrap: break-word; + max-width: 100%; +} + +.pf-module-detail-card { + margin-bottom: 12px; +} + +.pf-detail-progress { + margin-left: 8px; + font-size: 13px; +} \ No newline at end of file diff --git a/paperforge/plugin/tests/installation-navigation.test.ts b/paperforge/plugin/tests/installation-navigation.test.ts index 4764e939..56d72968 100644 --- a/paperforge/plugin/tests/installation-navigation.test.ts +++ b/paperforge/plugin/tests/installation-navigation.test.ts @@ -1914,7 +1914,7 @@ describe("Issue #77 RED 1: Maintenance navigation entry", () => { for (const mod of ["library", "ocr", "memory"] as CapabilityModule[]) { const container = document.createElement("div"); tab._renderCard(container, mod, createUnknownEnvelope(mod)); - expect(container.querySelector("button.pf-open-module-btn")).toBeFalsy(); + expect(container.querySelector("button.pf-open-module-btn")).toBeTruthy(); } }); diff --git a/paperforge/plugin/tests/module-detail-navigation.test.ts b/paperforge/plugin/tests/module-detail-navigation.test.ts new file mode 100644 index 00000000..8c227666 --- /dev/null +++ b/paperforge/plugin/tests/module-detail-navigation.test.ts @@ -0,0 +1,782 @@ +/** + * Issue #78 — Library, OCR, and Memory module detail end-to-end tests. + * + * Uses JSDOM + Vitest mocks. Instantiates PaperForgeSettingTab, calls production + * render functions, clicks production buttons. NO standalone DOM lookalikes. + */ +import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; +import { JSDOM } from "jsdom"; + +// ── Hoisted mutable state ── +const { noticeCalls, spawnedProcesses, execFileCalls } = vi.hoisted(() => { + const noticeCalls: string[] = []; + const spawnedProcesses: Array<{ + args: string[]; + onData?: (data: unknown) => void; + onError?: (err: Error) => void; + onClose?: (code: number | null) => void; + }> = []; + const execFileCalls: Array<{ args: string[]; cb?: (err: Error | null, stdout: string, stderr: string) => void }> = []; + return { noticeCalls, spawnedProcesses, execFileCalls }; +}); + +// ── Mocks ── +vi.mock("../src/release-notes.json", () => ({ default: { versions: [] } })); + +vi.mock("obsidian", () => { + return { + PluginSettingTab: class { + containerEl: HTMLDivElement; + app: Record; + constructor(app: Record, _plugin: Record) { + this.app = app; + this.containerEl = document.createElement("div"); + } + }, + App: class {}, + Setting: class { + settingEl: HTMLDivElement; + descEl: HTMLDivElement & { setText?: (t: string) => void }; + nameEl: HTMLDivElement; + controlEl: HTMLDivElement; + constructor(containerEl: HTMLElement) { + this.settingEl = document.createElement("div"); + this.settingEl.className = "setting-item"; + this.nameEl = document.createElement("div"); + this.nameEl.className = "setting-item-name"; + this.descEl = Object.assign(document.createElement("div"), { + className: "setting-item-description", + setText: (t: string) => { this.descEl.textContent = t; }, + }); + this.controlEl = document.createElement("div"); + this.controlEl.className = "setting-item-control"; + this.settingEl.appendChild(this.nameEl); + this.settingEl.appendChild(this.descEl); + this.settingEl.appendChild(this.controlEl); + containerEl.appendChild(this.settingEl); + } + setName(text: string) { this.nameEl.textContent = text; return this; } + setDesc(text: string) { this.descEl.textContent = text; return this; } + addText(cb: (text: Record) => void) { return this; } + addToggle(cb: (toggle: Record) => void) { return this; } + addDropdown(cb: (dropdown: Record) => void) { + const select = document.createElement("select"); + this.controlEl.appendChild(select); + const dropdown = { + addOption: () => {}, + setValue: function () { return this; }, + onChange: function () { return this; }, + }; + cb(dropdown); + return this; + } + addButton(cb: (button: Record) => void) { return this; } + addExtraButton(cb: (btn: Record) => void) { return this; } + }, + Modal: class { + app: Record; + contentEl: HTMLDivElement; + constructor(app: Record) { this.app = app; this.contentEl = document.createElement("div"); } + open() {} + close() {} + }, + Notice: class { + constructor(msg: string, timeout?: number) { + noticeCalls.push({ msg, timeout }); + } + }, + setTooltip: () => {}, + Platform: {}, + }; +}); + +// Mock node built-ins used by settings.ts +vi.mock("fs", () => ({ default: {}, existsSync: () => false, readFileSync: () => "{}", writeFileSync: () => {}, readdirSync: () => [], statSync: () => ({}), accessSync: () => {}, constants: { X_OK: 1 } })); +vi.mock("path", () => ({ default: {}, join: (...args: string[]) => args.join("/"), dirname: (p: string) => p.split("/").slice(0, -1).join("/"), resolve: (...args: string[]) => args.join("/") })); +vi.mock("os", () => ({ default: {}, homedir: () => "/home/user", platform: () => "win32" })); +vi.mock("child_process", () => { + const mod = { + execFile: (_path: string, args: string[], _opts: Record, cb?: (err: Error | null, stdout: string, stderr: string) => void) => { + execFileCalls.push({ args: [...args], cb }); + // Defer callback so tests can inspect state before terminal cleanup + if (cb) setTimeout(() => cb(null, "{}", ""), 0); + }, + execFileSync: () => "Python 3.11.0", + exec: () => {}, + spawn: (_path: string, args: string[], _opts: Record) => { + const self = { + args: [...args], + stdout: { on: (_ev: string, cb: (data: unknown) => void) => { self.onData = cb; } }, + stderr: { on: () => {} }, + stdin: { write: (_s: string) => true }, + kill: (_sig: string) => {}, + onData: undefined as ((data: unknown) => void) | undefined, + onError: undefined as ((err: Error) => void) | undefined, + onClose: undefined as ((code: number | null) => void) | undefined, + }; + spawnedProcesses.push(self); + return { + args: [...args], + stdout: { on: (_ev: string, cb: (data: unknown) => void) => { self.onData = cb; } }, + stderr: { on: () => {} }, + stdin: { write: (_s: string) => true }, + kill: (_sig: string) => {}, + on: (ev: string, cb: (arg: unknown) => void) => { + if (ev === "error") self.onError = cb as (err: Error) => void; + if (ev === "close") self.onClose = cb as (code: number | null) => void; + }, + }; + }, + }; + return { ...mod, default: mod }; +}); + + +vi.mock("../src/services/python-bridge", () => ({ + resolvePythonExecutable: () => ({ path: "/usr/bin/python3", extraArgs: [] }), + buildRuntimeInstallCommand: () => "pip install", + paperforgeEnrichedEnv: () => ({}), + scanBbtUnderProfiles: () => [], + scanBbtDirectChildren: () => [], + runSubprocess: () => {}, +})); + +vi.mock("../src/services/memory-state", () => ({ + resolveVaultPaths: () => ({}), + getMemoryRuntime: () => ({}), + getVectorRuntime: () => ({}), + getRuntimeHealth: () => ({}), + isMemoryReady: () => false, + isVectorReady: () => false, + getMemoryStatusText: () => "", + getVectorStatusText: () => "", + getCachedPython: () => ({ path: "/usr/bin/python3", extraArgs: [] }), +})); + +vi.mock("../src/services/ocr-maintenance-ui", () => ({ + categorizeMaintenanceRow: () => [], + buildMaintenanceSummary: () => ({ items: [], summary: "" }), + maintenanceActionForRow: () => null, + maintenanceActionRequiresConfirmation: () => false, + readMaintenanceCache: () => ({ items: [], updated_at: "" }), + refreshMaintenanceData: () => Promise.resolve({ data: [] }), +})); + +vi.mock("../src/services/managed-runtime", () => ({ + ManagedRuntime: class {}, + runtimeActionsForHealth: () => [], + resolveRuntimeCommand: (run: unknown) => ({ command: "/usr/bin/python3", args: [] }), +})); + +vi.mock("../src/utils/disclosure", () => ({ + getDisclosureState: () => false, + toggleDisclosureState: () => {}, +})); + +vi.mock("../src/services/progress-parser", () => ({ + processProgressChunk: (chunk: string, _buffer: string) => { + const events: Array<{ event: string; current?: number; total?: number; key?: string }> = []; + for (const line of chunk.split("\n")) { + const m = line.match(/^(\S+)\s+START\s+(\d+)/); + if (m) { events.push({ event: "START", total: parseInt(m[2]) }); continue; } + const m2 = line.match(/^(\S+)\s+PROGRESS\s+(\d+)\s+(\d+)(?:\s+(\S+))?/); + if (m2) { events.push({ event: "PROGRESS", current: parseInt(m2[2]), total: parseInt(m2[3]), key: m2[4] || "" }); continue; } + } + return { events, buffer: "" }; + }, +})); + +import { createUnknownEnvelope, ProbeEnvelope } from "../src/constants"; +import { PaperForgeSettingTab } from "../src/settings"; +import { setLanguage } from "../src/i18n"; + +// ── Helpers ── +function fakeApp() { + return { + vault: { adapter: { basePath: "/vault" }, getConfig: () => "en" }, + workspace: { getLeavesOfType: () => [], onLayoutReady: (cb: () => void) => cb?.() }, + }; +} + +function fakePlugin(overrides: Record = {}) { + return { + app: fakeApp(), + manifest: { id: "paperforge", version: "2.0.0" }, + settings: {} as Record, + saveSettings: vi.fn(), + loadSettings: vi.fn(), + readPaperforgeJson: () => ({}), + _ocrProcess: null as unknown, + _ocrProgress: null as { current: number; total: number; key: string } | null, + _ocrBuffer: "", + _ocrWasStopped: false, + _embedProcess: null as unknown, + _embedProgress: { current: 0, total: 0, key: "" }, + _embedBuffer: "", + _autoSyncRunning: false, + _lastSyncTime: "", + ...overrides, + }; +} + +function makeTab(data: Record = {}) { + const tab = new PaperForgeSettingTab(fakeApp() as any, fakePlugin(data)); + // wire managed runtime mock + const rt = { current: () => ({ path: "/usr/bin/python3", version: "3.11", state: "ready" }), status: () => Promise.resolve({ state: "ready" }) }; + (tab as any)._ensureManagedRuntime = () => rt; + (tab as any)._resolveRuntimeCommand = () => ({ path: "/usr/bin/python3", args: [] }); + (tab as any)._capabilityState = data.capabilityState || {}; + (tab as any)._selectedDetailModule = ""; + (tab as any)._probing = new Set(); + // Override display() to avoid triggering full render chain in tests + (tab as any).display = () => {}; + return tab; +} + +let dom: JSDOM; + + +// ── Obsidian DOM polyfills ── +function polyfillObsidianDom(doc: Document) { + // Polyfill JSDOM's own HTMLElement, not the global one + const Win = doc.defaultView; + if (!Win) return; + const HTMLEl = (Win as any).HTMLElement; + if (!HTMLEl) return; + const ht = HTMLEl.prototype; + const origCreate = doc.createElement.bind(doc); + + if (!ht.createEl) { + ht.createEl = function(tag: string, opts?: { cls?: string; text?: string; attr?: Record; title?: string }, cb?: (el: HTMLElement) => void) { + const el = origCreate(tag); + if (opts?.cls) el.className = opts.cls; + if (opts?.text) el.textContent = opts.text; + if (opts?.attr) { for (const [k, v] of Object.entries(opts.attr)) { el.setAttribute(k, v); } } + if (opts?.title) el.title = opts.title; + this.appendChild(el); + if (cb) cb(el); + return el; + }; + ht.createDiv = function(opts?: { cls?: string; text?: string; attr?: Record }, cb?: (el: HTMLElement) => void) { + return (this as any).createEl("div", opts, cb); + }; + ht.createSpan = function(opts?: { cls?: string; text?: string; attr?: Record }, cb?: (el: HTMLElement) => void) { + return (this as any).createEl("span", opts, cb); + }; + ht.empty = function() { while (this.firstChild) this.removeChild(this.firstChild); }; + ht.setText = function(text: string) { this.textContent = text; }; + ht.setAttr = function(name: string, value: string) { this.setAttribute(name, value); }; + ht.appendText = function(text: string) { this.appendChild(doc.createTextNode(text)); }; + } +} + + +beforeEach(() => { + dom = new JSDOM("", { url: "http://localhost", pretendToBeVisual: true }); + polyfillObsidianDom(dom.window.document); + (globalThis as any).window = dom.window; + (globalThis as any).document = dom.window.document; + (globalThis as any).confirm = () => true; + + noticeCalls.length = 0; + spawnedProcesses.length = 0; + execFileCalls.length = 0; + setLanguage(fakeApp() as any); +}); + +afterEach(() => { dom.window.close(); }); + +// ════════════════════════════════ 1. Library Detail ════════════════════ +describe("Library module detail (Issue #78)", () => { + it("renders envelope shell with heading, no duplicate CTA", () => { + const tab = makeTab(); + const el = dom.window.document.createElement("div"); + (tab as any)._renderLibraryDetail(el); + expect(el.querySelector(".pf-module-detail-heading")).not.toBeNull(); + expect(el.querySelector(".mod-cta")).toBeNull(); + }); + + it("renders reason, timestamp, TTL, notices, diagnostics", () => { + const env = { + ...createUnknownEnvelope("library"), + capability_state: "needs_action", + severity: "warning", + reason: { code: "library.index_stale", text: "Index is stale" }, + updated_at: "2026-01-15T12:00:00Z", + ttl_seconds: 300, + notices: [{ level: "warning", message: "Test notice" }], + action: { primary: { verb: "sync", label: "Sync", command: "paperforge sync", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } }, + } as any; + const tab = makeTab({ capabilityState: { library: env } }); + const el = dom.window.document.createElement("div"); + (tab as any)._renderLibraryDetail(el); + expect(el.textContent).toContain("stale"); + expect(el.textContent).toContain("300s"); + expect(el.textContent).toContain("Test notice"); + expect(el.querySelector(".pf-cc-card-diagnostic")).not.toBeNull(); + }); + + it("primary action click overlays envelope running", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { + library: { + ...createUnknownEnvelope("library"), + capability_state: "needs_action", + severity: "warning", + reason: { code: "library.index_stale", text: "stale" }, + action: { primary: { verb: "sync", label: "Sync", command: "paperforge sync", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } }, + }, + }; + const el = dom.window.document.createElement("div"); + (tab as any)._renderLibraryDetail(el); + (el.querySelector(".pf-cc-card-action") as HTMLButtonElement)?.click(); + const envelopes = (tab as any)._capabilityState as any; + expect(envelopes?.library?.activity_state).toBe("running"); + expect(envelopes?.library?.activity_label).toContain("Syncing"); + }); +}); + +// ════════════════════════════════ 2. OCR Detail ════════════════════════ +describe("OCR module detail (Issue #78)", () => { + it("renders stop only when _ocrProcess exists", () => { + const tab = makeTab({ + _ocrProcess: { stdin: { write: () => true }, kill: () => {} }, + _ocrProgress: { current: 3, total: 10, key: "TEST" }, + }); + const ocrEnv = { + ...createUnknownEnvelope("ocr"), + capability_state: "needs_action", + severity: "warning", + reason: { code: "ocr.artifacts_stale", text: "Stale" }, + action: { primary: { verb: "rebuild_derived", label: "Rebuild", command: "paperforge ocr rebuild --all", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } }, + } as any; + (tab as any)._capabilityState = { ocr: ocrEnv }; + const el = dom.window.document.createElement("div"); + (tab as any)._renderOcrDetail(el); + expect(el.querySelector(".mod-warning")).not.toBeNull(); + expect(el.textContent).toContain("3/10"); + + const tab2 = makeTab({ _ocrProcess: null }); + (tab2 as any)._capabilityState = { ocr: ocrEnv }; + const el2 = dom.window.document.createElement("div"); + (tab2 as any)._renderOcrDetail(el2); + expect(el2.querySelector(".mod-warning")).toBeNull(); + }); + + it("stop sends PAPERFORGE_STOP\n via stdin", () => { + let written = ""; + const tab = makeTab({ + _ocrProcess: { stdin: { write: (s: string) => { written = s; return true; } }, kill: () => {} }, + }); + (tab as any)._capabilityState = { + ocr: { ...createUnknownEnvelope("ocr"), capability_state: "needs_action", severity: "warning", reason: { code: "x", text: "x" }, action: { primary: { verb: "rebuild_derived", label: "Rebuild", command: "paperforge ocr rebuild --all", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } } }, + }; + const el = dom.window.document.createElement("div"); + (tab as any)._renderOcrDetail(el); + (el.querySelector(".mod-warning") as HTMLButtonElement)?.click(); + expect(written).toBe("PAPERFORGE_STOP\n"); + expect((tab.plugin as any)._ocrWasStopped).toBe(true); + }); + + it("stop falls back to SIGINT when stdin unavailable", () => { + let killed = ""; + const tab = makeTab({ _ocrProcess: { kill: (sig: string) => { killed = sig; } } }); + (tab as any)._capabilityState = { + ocr: { ...createUnknownEnvelope("ocr"), capability_state: "needs_action", severity: "warning", reason: { code: "x", text: "x" }, action: { primary: { verb: "rebuild_derived", label: "Rebuild", command: "paperforge ocr rebuild --all", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } } }, + }; + const el = dom.window.document.createElement("div"); + (tab as any)._renderOcrDetail(el); + (el.querySelector(".mod-warning") as HTMLButtonElement)?.click(); + expect(killed).toBe("SIGINT"); + }); +}); + +// ════════════════════════════════ 3. Memory Detail ════════════════════ +describe("Memory module detail (Issue #78)", () => { + it("renders envelope shell without duplicate CTA", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { + memory: { ...createUnknownEnvelope("memory"), capability_state: "needs_action", severity: "warning", reason: { code: "x", text: "x" }, action: { primary: { verb: "run", label: "Build", command: "paperforge memory build", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } } }, + }; + const el = dom.window.document.createElement("div"); + (tab as any)._renderMemoryDetail(el); + expect(el.querySelector(".pf-module-detail-heading")).not.toBeNull(); + expect(el.querySelector(".mod-cta")).toBeNull(); + }); +}); + +// ════════════════════════════════ 4. Dispatch allowlist ════════════════ +describe("_dispatchModuleAction allowlist (Issue #78)", () => { + it("unknown pair -> Notice + re-probe", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { library: createUnknownEnvelope("library") }; + (tab as any)._probing = new Set(); + const env = { ...createUnknownEnvelope("library"), action: { primary: { verb: "bogus", label: "X", command: "x", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } } } as any; + noticeCalls.length = 0; + (tab as any)._dispatchModuleAction("library", env); + // Should emit a Notice with "Unknown" + const msgs = noticeCalls.map((c: { msg: string }) => c.msg).join(" "); + expect(msgs.length).toBeGreaterThan(0); + expect(msgs.toLowerCase()).toMatch(/unknown|bogus/); + }); + + it("run + paperforge ocr run -> spawns ['ocr', 'run']", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { ocr: createUnknownEnvelope("ocr") }; + const env = { ...createUnknownEnvelope("ocr"), action: { primary: { verb: "run", label: "Run", command: "paperforge ocr run", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } } } as any; + (tab as any)._dispatchModuleAction("ocr", env); + const last = spawnedProcesses[spawnedProcesses.length - 1]; + expect(last.args).toContain("run"); + expect(last.args).not.toContain("rebuild"); + }); + + it("rebuild_derived -> spawns rebuild --all", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { ocr: createUnknownEnvelope("ocr") }; + const env = { ...createUnknownEnvelope("ocr"), action: { primary: { verb: "rebuild_derived", label: "Rebuild", command: "paperforge ocr rebuild --all", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } } } as any; + (tab as any)._dispatchModuleAction("ocr", env); + const last = spawnedProcesses[spawnedProcesses.length - 1]; + expect(last.args).toContain("rebuild"); + expect(last.args).toContain("--all"); + }); + + it("redo -> spawns redo args", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { ocr: createUnknownEnvelope("ocr") }; + const env = { ...createUnknownEnvelope("ocr"), action: { primary: { verb: "redo", label: "Redo", command: "paperforge ocr redo", destructive: true, destructive_scope: "selection", destructive_effect: "Deletes.", confirmation_required: true, confirmation_prompt: "Proceed?", scope: "module", scope_count: 1 } } } as any; + (globalThis as any).confirm = () => true; + (tab as any)._dispatchModuleAction("ocr", env); + expect(spawnedProcesses[spawnedProcesses.length - 1].args).toContain("redo"); + }); + + it("memory build -> uses execFile", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { memory: createUnknownEnvelope("memory") }; + const env = { ...createUnknownEnvelope("memory"), action: { primary: { verb: "run", label: "Build", command: "paperforge memory build", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } } } as any; + execFileCalls.length = 0; + (tab as any)._dispatchModuleAction("memory", env); + expect(execFileCalls.some((c: { args: string[] }) => c.args.includes("build"))).toBe(true); + }); + + it("embed build --force -> spawns embed", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { memory: createUnknownEnvelope("memory") }; + const env = { ...createUnknownEnvelope("memory"), action: { primary: { verb: "rebuild_index", label: "Embed", command: "paperforge embed build --force", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } } } as any; + (tab as any)._dispatchModuleAction("memory", env); + const es = spawnedProcesses.find((p: { args: string[] }) => p.args.includes("embed")); + expect(es?.args).toContain("--force"); + }); + + it("destructive confirm called before dispatch", () => { + const tab = makeTab(); + let called = false; + (globalThis as any).confirm = (msg: string) => { called = true; expect(msg).toContain("Proceed"); return false; }; + const env = { ...createUnknownEnvelope("ocr"), action: { primary: { verb: "redo", label: "Redo", command: "paperforge ocr redo", destructive: true, destructive_scope: "selection", destructive_effect: "Deletes.", confirmation_required: true, confirmation_prompt: "Proceed?", scope: "module", scope_count: 1 } } } as any; + (tab as any)._dispatchModuleAction("ocr", env); + expect(called).toBe(true); + }); + + it("setup verb with wrong command falls through to Notice", () => { + const tab = makeTab(); + noticeCalls.length = 0; + const env = { ...createUnknownEnvelope("library"), action: { primary: { verb: "setup", label: "Setup", command: "paperforge sync", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } } } as any; + (tab as any)._dispatchModuleAction("library", env); + const msgs = noticeCalls.map((c: { msg: string }) => c.msg).join(" "); + expect(msgs.toLowerCase()).toMatch(/unknown|setup/); + }); + + it("probe verb with wrong command falls through to Notice", () => { + const tab = makeTab(); + noticeCalls.length = 0; + const env = { ...createUnknownEnvelope("ocr"), action: { primary: { verb: "probe", label: "Probe", command: "probe installation", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } } } as any; + (tab as any)._dispatchModuleAction("ocr", env); + const msgs = noticeCalls.map((c: { msg: string }) => c.msg).join(" "); + expect(msgs.toLowerCase()).toMatch(/unknown|probe/); + }); +}); + +// ════════════════════════════════ 5. _dispatchOcrAction ══════════════ +describe("_dispatchOcrAction lifecycle (Issue #78)", () => { + it("assigns _ocrProcess, sets activity overlay", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { ocr: createUnknownEnvelope("ocr") }; + spawnedProcesses.length = 0; + (tab as any)._dispatchOcrAction("run"); + // Verify spawn was called with correct args + expect(spawnedProcesses.length).toBeGreaterThan(0); + expect(spawnedProcesses[0].args).toContain("run"); + // Verify _ocrProcess was assigned (mock spawn returns truthy) + expect((tab.plugin as any)._ocrProcess).toBeTruthy(); + // Verify activity overlay + const e = (tab as any)._capabilityState as any; + expect(e?.ocr?.activity_state).toBe("running"); + }); + + it("parses START/PROGRESS into activity_progress", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { ocr: createUnknownEnvelope("ocr") }; + (tab as any)._dispatchOcrAction("rebuild"); + const proc = spawnedProcesses[spawnedProcesses.length - 1]; + proc?.onData?.("OCR_REBUILD START 20\n"); + expect(((tab.plugin as any)._ocrProgress).total).toBe(20); + proc?.onData?.("OCR_REBUILD PROGRESS 5 20 KEY1\n"); + expect(((tab.plugin as any)._ocrProgress).current).toBe(5); + }); + + it("clears activity and re-probes on close/error/stop", () => { + const probes: string[] = []; + const tab = makeTab(); + (tab as any)._probeModule = (mod: string) => { probes.push(mod); }; + (tab as any)._capabilityState = { ocr: createUnknownEnvelope("ocr") }; + (tab as any)._dispatchOcrAction("run"); + spawnedProcesses[spawnedProcesses.length - 1]?.onClose?.(0); + expect((tab.plugin as any)._ocrProcess).toBeNull(); + expect(probes).toContain("ocr"); + expect(((tab as any)._capabilityState as any)?.ocr?.activity_state).toBe("idle"); + }); + + it("exact CLI args: run/rebuild/redo", () => { + for (const [mode, expected] of [["run", "run"], ["rebuild", "rebuild"], ["redo", "redo"]] as const) { + const tab = makeTab(); + (tab as any)._capabilityState = { ocr: createUnknownEnvelope("ocr") }; + spawnedProcesses.length = 0; + (tab as any)._dispatchOcrAction(mode); + expect(spawnedProcesses[0].args).toContain(expected); + if (mode === "rebuild") expect(spawnedProcesses[0].args).toContain("--all"); + } + }); + + it("does not change capability_state/severity during activity", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { + ocr: { ...createUnknownEnvelope("ocr"), capability_state: "needs_action", severity: "warning" }, + }; + (tab as any)._dispatchOcrAction("run"); + const e = (tab as any)._capabilityState as any; + expect(e?.ocr?.capability_state).toBe("needs_action"); + expect(e?.ocr?.severity).toBe("warning"); + expect(e?.ocr?.activity_state).toBe("running"); + }); +}); + +// ════════════════════════════════ 6. _dispatchMemoryBuild ════════════ +describe("_dispatchMemoryBuild (Issue #78)", () => { + it("build mode overlays envelope, uses execFile", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { memory: createUnknownEnvelope("memory") }; + execFileCalls.length = 0; + // _dispatchMemoryBuild("build") should set activity overlay and spawn execFile + (tab as any)._dispatchMemoryBuild("build"); + const e = (tab as any)._capabilityState as any; + expect(e?.memory?.activity_state).toBe("running"); + expect(e?.memory?.activity_label).toContain("Building memory"); + // execFile should have been called with args containing "build" + const buildCalls = execFileCalls.filter((c: { args: string[] }) => c.args.some(a => a === "build")); + expect(buildCalls.length).toBeGreaterThan(0); + }); + + it("embed mode overlays envelope, spawns embed --force", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { memory: createUnknownEnvelope("memory") }; + (tab as any)._dispatchMemoryBuild("embed"); + expect(((tab as any)._capabilityState as any)?.memory?.activity_label).toContain("vector"); + const es = spawnedProcesses.find((p: { args: string[] }) => p.args.includes("embed")); + expect(es?.args).toContain("--force"); + }); + + it("embed parses PROGRESS into activity_progress", () => { + const tab = makeTab(); + (tab as any)._capabilityState = { memory: createUnknownEnvelope("memory") }; + (tab as any)._dispatchMemoryBuild("embed"); + spawnedProcesses.find((p: { args: string[] }) => p.args.includes("embed"))?.onData?.("MEMORY_EMBED PROGRESS 100 500\n"); + expect(((tab.plugin as any)._embedProgress).current).toBe(100); + }); +}); + +// ════════════════════════════════ 7. Focus/back ══════════════════════ +describe("focus and back navigation (Issue #78)", () => { + it("heading tabindex=-1, back sets focus target", () => { + const tab = makeTab(); + const el = dom.window.document.createElement("div"); + (tab as any)._renderLibraryDetail(el); + expect(el.querySelector("#pf-library-detail-heading")?.getAttribute("tabindex")).toBe("-1"); + (el.querySelector(".pf-back-btn") as HTMLButtonElement)?.click(); + expect((tab as any)._focusTargetId).toContain("library"); + }); +}); + +// ════════════════════════════════ 8. Module selector ═════════════════ +describe("module detail selector (Issue #78)", () => { + it("4 buttons, active class, click navigation", () => { + const tab = makeTab(); + const el = dom.window.document.createElement("div"); + (tab as any)._renderOcrDetail(el); + expect(el.querySelectorAll(".pf-module-detail-btn").length).toBe(4); + expect(el.querySelector(".pf-module-detail-btn--active")?.textContent).toContain("OCR"); + const memBtn = Array.from(el.querySelectorAll(".pf-module-detail-btn")).find(b => b.textContent?.includes("Memory")); + (memBtn as HTMLButtonElement)?.click(); + expect((tab as any)._selectedDetailModule).toBe("memory"); + }); +}); + +// ════════════════════════════════ 9. Destructive + disabled ══════════ +describe("destructive metadata and disabled-while-running (Issue #78)", () => { + it("renders destructive_effect notice", () => { + const ocrEnv = { + ...createUnknownEnvelope("ocr"), + action: { primary: { verb: "redo", label: "Redo", command: "paperforge ocr redo", destructive: true, destructive_scope: "selection", destructive_effect: "Deletes derived OCR artifacts.", confirmation_required: true, confirmation_prompt: "Proceed?", scope: "module", scope_count: 1 } }, + } as any; + const tab = makeTab({ capabilityState: { ocr: ocrEnv } }); + const el = dom.window.document.createElement("div"); + (tab as any)._renderOcrDetail(el); + const notice = el.querySelector(".pf-destructive-notice"); + expect(notice).not.toBeNull(); + expect(notice?.textContent).toContain("Deletes derived OCR"); + }); + + it("action button disabled when running", () => { + const ocrEnv = { + ...createUnknownEnvelope("ocr"), + activity_state: "running", + action: { primary: { verb: "rebuild_derived", label: "Rebuild", command: "paperforge ocr rebuild --all", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } }, + } as any; + const tab = makeTab({ capabilityState: { ocr: ocrEnv } }); + const el = dom.window.document.createElement("div"); + (tab as any)._renderOcrDetail(el); + expect((el.querySelector(".pf-cc-card-action") as HTMLButtonElement)?.hasAttribute("disabled")).toBe(true); + }); +}); + + +// ════════════════════════════════ 10. Library sync failure probe ═══════ +describe("Library sync failure probe (Issue #78)", () => { + it("_probeModule library with nonzero exit code appends --last-operation-exit-code", () => { + const tab = makeTab(); + execFileCalls.length = 0; + + (tab as any)._probeModule("library", 1); + + // Probe execFile call must include --last-operation-exit-code 1 + const probeCall = execFileCalls.find(c => c.args.includes("probe") && c.args.includes("library")); + expect(probeCall).toBeDefined(); + const lastOpIdx = probeCall!.args.indexOf("--last-operation-exit-code"); + expect(lastOpIdx).toBeGreaterThan(-1); + expect(probeCall!.args[lastOpIdx + 1]).toBe("1"); + }); + + it("_probeModule library without exit code omits --last-operation-exit-code", () => { + const tab = makeTab(); + execFileCalls.length = 0; + + (tab as any)._probeModule("library"); + + const probeCall = execFileCalls.find(c => c.args.includes("probe") && c.args.includes("library")); + expect(probeCall).toBeDefined(); + expect(probeCall!.args.indexOf("--last-operation-exit-code")).toBe(-1); + }); + + it("_probeModule non-library with exit code omits --last-operation-exit-code", () => { + const tab = makeTab(); + execFileCalls.length = 0; + + (tab as any)._probeModule("ocr", 1); + + // OCR must NOT get --last-operation-exit-code + const probeCall = execFileCalls.find(c => c.args.includes("probe") && c.args.includes("ocr")); + expect(probeCall).toBeDefined(); + expect(probeCall!.args.indexOf("--last-operation-exit-code")).toBe(-1); + }); + + it("failed _runManualSync onClose passes nonzero code to probe", () => { + const tab = makeTab(); + execFileCalls.length = 0; + + (tab as any)._runManualSync(); + + // First call is sync, second (after onClose) is probe + const syncCall = execFileCalls.find(c => c.args.includes("sync") && !c.args.includes("probe")); + expect(syncCall).toBeDefined(); + + // Simulate sync failure by invoking the onClose directly + if (syncCall!.cb) syncCall!.cb(new Error("sync failed"), "", "error"); + + // After sync failure, _probeModule should append --last-operation-exit-code 1 + const probeCall = execFileCalls.find(c => c.args.includes("probe") && c.args.includes("library") && c.args.includes("--last-operation-exit-code")); + expect(probeCall).toBeDefined(); + const lastOpIdx = probeCall!.args.indexOf("--last-operation-exit-code"); + expect(probeCall!.args[lastOpIdx + 1]).toBe("1"); + }); + + it("successful _runManualSync onClose passes 0 to probe (no flag)", () => { + const tab = makeTab(); + execFileCalls.length = 0; + + (tab as any)._runManualSync(); + + const syncCall = execFileCalls.find(c => c.args.includes("sync") && !c.args.includes("probe")); + expect(syncCall).toBeDefined(); + + // Simulate sync success: code=0 + if (syncCall!.cb) syncCall!.cb(null, "success", ""); + + // After success (code=0), probe should NOT have --last-operation-exit-code + const probeCalls = execFileCalls.filter(c => c.args.includes("probe") && c.args.includes("library")); + const failureProbe = probeCalls.find(c => c.args.includes("--last-operation-exit-code")); + expect(failureProbe).toBeUndefined(); + }); + + it("sync_failed envelope renders actionable on failure probe", () => { + const syncFailedEnv: ProbeEnvelope = { + schema_version: 1, + module: "library", + capability_state: "needs_action", + activity_state: "idle", + activity_label: null, + activity_progress: null, + severity: "error", + reason: { code: "library.sync_failed", text: "Library sync failed (exit code 1)" }, + action: { primary: { verb: "sync", label: "Sync library", command: "paperforge sync", destructive: false, destructive_scope: null, destructive_effect: null, confirmation_required: false, confirmation_prompt: null, scope: "module", scope_count: 1 } }, + notices: [], + updated_at: new Date().toISOString(), + ttl_seconds: 300, + }; + const tab = makeTab({ capabilityState: { library: syncFailedEnv } }); + const el = dom.window.document.createElement("div"); + (tab as any)._renderLibraryDetail(el); + + // Must show failure reason + expect(el.textContent).toContain("sync failed"); + expect(el.textContent).toContain("exit code 1"); + // Must have actionable sync button + expect(el.querySelector(".pf-cc-card-action")).not.toBeNull(); + expect((el.querySelector(".pf-cc-card-action") as HTMLButtonElement)?.textContent).toContain("Sync"); + }); + + it("null _runManualSync onClose forwards sentinel 1 via code ?? 1", () => { + const tab = makeTab(); + execFileCalls.length = 0; + + // Override _callPython on this tab to invoke opts.onClose(null, ...) + // simulating a process exit with null code (timeout/kill). + const origCallPython = (tab as any)._callPython.bind(tab); + (tab as any)._callPython = (args: string[], opts: any) => { + // Only intercept sync; pass other calls through + if (args.includes("sync")) { + // Simulate process close with null code + if (opts.onClose) opts.onClose(null, "", "sync killed"); + return null; + } + return origCallPython(args, opts); + }; + + (tab as any)._runManualSync(); + + // After _runManualSync triggers onClose(null), probe should have --last-operation-exit-code 1 + const probeCall = execFileCalls.find(c => c.args.includes("probe") && c.args.includes("library") && c.args.includes("--last-operation-exit-code")); + expect(probeCall).toBeDefined(); + const lastOpIdx = probeCall!.args.indexOf("--last-operation-exit-code"); + expect(probeCall!.args[lastOpIdx + 1]).toBe("1"); + }); + +}); + diff --git a/paperforge/worker/ocr.py b/paperforge/worker/ocr.py index 09908187..fbe9e449 100644 --- a/paperforge/worker/ocr.py +++ b/paperforge/worker/ocr.py @@ -35,6 +35,30 @@ def _read_dotenv(vault: Path, key: str) -> str: return "" +def _resolve_paddleocr_token(vault: Path) -> str: + """Resolve PaddleOCR API token from canonical sources. + + Priority: PADDLEOCR_API_TOKEN env → PADDLEOCR_API_TOKEN_USER env → + Windows HKCU Environment → vault/.env or System/PaperForge/.env. + Returns empty string when no token is found. + """ + token = os.environ.get("PADDLEOCR_API_TOKEN", "").strip() + if not token: + token = os.environ.get("PADDLEOCR_API_TOKEN_USER", "").strip() + if not token: + try: + import winreg + + with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment") as env_key: + token = str(winreg.QueryValueEx(env_key, "PADDLEOCR_API_TOKEN")[0]).strip() + except Exception: + token = "" + if not token: + token = _read_dotenv(vault, "PADDLEOCR_API_TOKEN") + return token + + + try: from paperforge.worker.ocr_roles import assign_block_role # noqa: F401 except ImportError: @@ -2461,20 +2485,7 @@ def run_ocr( max_items = max(1, int(max_items_raw)) except ValueError: max_items = 3 - token = os.environ.get("PADDLEOCR_API_TOKEN", "").strip() - if not token: - token = os.environ.get("PADDLEOCR_API_TOKEN_USER", "").strip() - if not token: - try: - import winreg - - with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment") as env_key: - token = str(winreg.QueryValueEx(env_key, "PADDLEOCR_API_TOKEN")[0]).strip() - except Exception: - token = "" - if not token: - # Fallback: parse vault-root .env - token = _read_dotenv(vault, "PADDLEOCR_API_TOKEN") + token = _resolve_paddleocr_token(vault) job_url = os.environ.get("PADDLEOCR_JOB_URL", "https://paddleocr.aistudio-app.com/api/v2/ocr/jobs").strip() model = os.environ.get("PADDLEOCR_MODEL", "PaddleOCR-VL-1.6").strip() optional_payload = {"useDocOrientationClassify": False, "useDocUnwarping": False, "useChartRecognition": False} diff --git a/project/current/ocr-v2-active-queue.md b/project/current/ocr-v2-active-queue.md index 6b8fb3f7..2d5750d3 100644 --- a/project/current/ocr-v2-active-queue.md +++ b/project/current/ocr-v2-active-queue.md @@ -1,9 +1,8 @@ # OCR-v2 Active Queue -> Status: OCR-v2 is stable; #75 canonical setup/config migration and #76 Installation/Help capability tracer are implemented; #77 Managed Runtime lifecycle is the next unblocked control-center slice. -> Last updated: 2026-07-15 +> Status: OCR-v2 is stable; #75–#78 are implemented; #79 SecretStorage is the next unblocked control-center slice. +> Last updated: 2026-07-18 ## Current checkpoint - - Retrieval recovery is merged to `master`; the real Literature-hub vault has a healthy 2560-dimensional vec0 index and working M / @ search paths. - [OCR rebuild: streaming progress + maintenance UI redesign](https://github.com/LLLin000/PaperForge/issues/64) is implemented and reviewed. - Multi-key `ocr rebuild` and full `ocr redo` emit separate, flushed progress streams and accept a cross-platform cooperative stop request between papers. @@ -17,7 +16,6 @@ - [Desktop runtime/recovery research](https://github.com/LLLin000/PaperForge/issues/68#issuecomment-4970660288) establishes module-scoped repair, compatibility-gated updates, local redacted diagnostics, and user-reviewed issue drafts. - **[#69](https://github.com/LLLin000/PaperForge/issues/69) resolved** at `issuecomment-4971161072`: orthogonal availability/activity/attention axes, 6-state capability ordinal, 12 canonical verbs, backend-owned severity and primary actions, maintenance projection. - **[#70](https://github.com/LLLin000/PaperForge/issues/70) resolved** at `issuecomment-4971239398`: plugin-managed immutable runtime slots, system-Python bootstrap with validated-triplet fallback, single `active-runtime.json` pointer, `ManagedRuntime` class with `current()`/`status()`/`ensure()`, fail-closed command resolution. - - **[#71](https://github.com/LLLin000/PaperForge/issues/71) resolved**: six-module control-center HTML prototype with 5 scenarios, plain-button switcher, primary attention zone, responsive layout (768px breakpoint), and capability-gated actions. Independent Critical/Important PASS review. Design decisions recorded in `docs/prototypes/2026-07-14-six-module-control-center.{html,md}`. - **[#72](https://github.com/LLLin000/PaperForge/issues/72) resolved**: actionable-only maintenance inbox prototype with single-action rows, inline issue-draft review, local redacted export, and confirmation-first report flow. Independent Critical/Important PASS review. Design decisions recorded in `docs/prototypes/2026-07-14-maintenance-issue-reporting.{html,md}`. - **[#73](https://github.com/LLLin000/PaperForge/issues/73) resolved**: locked migration, security, platform, accessibility, and release-gate acceptance contract after five-domain audit and independent review. @@ -25,9 +23,9 @@ - **[#75](https://github.com/LLLin000/PaperForge/issues/75) implemented and reviewed**: bare/headless/modular setup share `SetupPlan`; schema-v2 `vault_config` wins; v1 path keys are warned read fallback; all configured directories are forwarded; required failures return non-zero. - **[#76](https://github.com/LLLin000/PaperForge/issues/76) implemented**: schema-v1 Installation/Help probes flow through the six-module Overview; persisted malformed/stale envelopes fail closed; backend set_config/update actions route to setup; unimplemented modules remain explicit placeholders. - **Wayfinder navigation refinement approved**: preserve Overview and stage `概览 / 模块详情 / 维护 / 帮助` across #77/#78/#80. Installation owns Agent platform/Skills under Agent 集成; no empty placeholder detail pages. - +- **[#77](https://github.com/LLLin000/PaperForge/issues/77) implemented**: immutable runtime slots, synchronous fail-closed `current`, probed `status`, install/repair/update/rollback/cancel/retention, managed-first dispatch, Release-N fallback, four-destination navigation, Installation detail, Agent integration, Help focus restoration. +- **[#78](https://github.com/LLLin000/PaperForge/issues/78) implemented**: Library/OCR/Memory real capability probes with module-detail-navigation, installation-navigation, and capability-state views. Python owns capability fact definitions; TypeScript renders via exact allowlist, fails closed on unknown keys. ## Verification status - - Focused Python OCR paths: **99 passed, 1 Windows SIGINT test skipped, 1 unrelated empty-result regression deselected**. - Plugin: **93 passed**; TypeScript check and production build passed. - Maintenance regression tests: **19/19 passed** (canonical action routing, confirmation gate, cache manifest preservation). @@ -38,20 +36,20 @@ - Both prototypes browser-verified at 768px viewport with scenario-switching, action-button interactions, expand/collapse diagnostics, and issue-draft flow. - Issue #75 verification: **61/61 focused tests passed**; independent review returned **Spec PASS / Quality APPROVED**. - Issue #76 verification: **21/21 backend probe tests and 169/169 plugin tests passed**; TypeScript check and production build passed; live Obsidian stale-cache/action-label smoke test and independent review passed. +- Issue #77 verification: **192/192 focused + 289/289 full tests passed**; typecheck/build clean; merged to `master` in `173a4e8..4ef9e98`. +- Issue #78 verification: **65/65 backend tests and 178/178 focused plugin tests, 324/324 full plugin tests passed** across 11 files; typecheck clean; production build clean; fail-closed recognizable config for Library/OCR; red rebuild_result stays non-destructive rebuild; queued OCR progress starts at 0; failed/null Library sync exit outcome is forwarded into fresh Python probe and remains sync actionable. - No production plugin code was modified during prototype work. - The repository-wide Python suite remains blocked during collection by the pre-existing `test_pr9a_resume_rebuild.py` import of removed `_assert_collections_healthy`. - ## Frontier - - [x] Prototype the six-module control center ([#71](https://github.com/LLLin000/PaperForge/issues/71)). - [x] Design the actionable-only maintenance inbox ([#72](https://github.com/LLLin000/PaperForge/issues/72)). - [x] Lock migration/acceptance contract (#73), publish PRD #74, and create dependency-linked issues #75–#82. - [x] Canonicalize setup and configuration migration ([#75](https://github.com/LLLin000/PaperForge/issues/75)). - [x] Implement [#76](https://github.com/LLLin000/PaperForge/issues/76): Installation/Help capability envelope through the existing settings surface. -- [ ] Start [#77](https://github.com/LLLin000/PaperForge/issues/77) in a fresh Matt `/implement` session: Managed Runtime lifecycle plus the approved Installation-detail navigation shell. - +- [x] Implement [#77](https://github.com/LLLin000/PaperForge/issues/77): Managed Runtime lifecycle plus the approved Installation-detail navigation shell. +- [x] Implement [#78](https://github.com/LLLin000/PaperForge/issues/78): Library, OCR, and Memory capability tracers end to end. +- [ ] Start [#79](https://github.com/LLLin000/PaperForge/issues/79) in a fresh Matt `/implement` session: SecretStorage for capability secrets. ## Deferred - - Vector rebuild UX (PRD Slice 1): deferred. - Memory/global maintenance cleanup (PRD Slice 3): deferred. - OCR ETA and real-time per-row mutation: out of scope for the completed OCR slice. diff --git a/tests/test_memory_restore.py b/tests/test_memory_restore.py new file mode 100644 index 00000000..31219880 --- /dev/null +++ b/tests/test_memory_restore.py @@ -0,0 +1,226 @@ +"""Focused tests for memory restore-backup (Issue #78 repair). + +Covers: +- CLI restore-backup subcommand +- Backup validation (integrity_check) +- Corrupt DB preservation +- Atomic replace (os.replace) +- Invalid backup safety (no restore) +- Probe emits restore_backup action +""" + +from __future__ import annotations + +import json +import sqlite3 +import subprocess +import sys +from pathlib import Path + +import pytest + + +def _run_memory_cmd(vault: Path, subcmd: str, extra_args: list[str] | None = None) -> subprocess.CompletedProcess: + cmd = [sys.executable, "-m", "paperforge", "--vault", str(vault), "memory", subcmd] + if extra_args: + cmd.extend(extra_args) + return subprocess.run(cmd, capture_output=True, text=True, timeout=15) + + +def _setup_memory_dirs(tmp_path: Path) -> Path: + """Create paperforge.json and indexes dir, return indexes dir.""" + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + indexes = tmp_path / "99_System" / "PaperForge" / "indexes" + indexes.mkdir(parents=True, exist_ok=True) + return indexes + + +def _create_valid_db(db_path: Path, paper_count: int = 3) -> None: + """Create a valid paperforge.db with schema_version and papers.""" + conn = sqlite3.connect(str(db_path)) + conn.execute("CREATE TABLE papers (zotero_key TEXT PRIMARY KEY, title TEXT)") + conn.execute("CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT)") + conn.execute("INSERT INTO meta VALUES ('schema_version', '8')") + for i in range(paper_count): + conn.execute("INSERT INTO papers VALUES (?, ?)", (f"KEY{i}", f"Paper {i}")) + conn.commit() + conn.close() + + +class TestMemoryRestoreCli: + """CLI restore-backup subcommand acceptance.""" + + def test_restore_backup_no_backup_file(self, tmp_path: Path) -> None: + """No backup file -> exits nonzero.""" + indexes = _setup_memory_dirs(tmp_path) + _create_valid_db(indexes / "paperforge.db") + result = _run_memory_cmd(tmp_path, "restore-backup", ["--json"]) + data = json.loads(result.stdout) + assert data["ok"] is False + assert "No backup found" in data["error"]["message"] + + def test_restore_backup_invalid_backup(self, tmp_path: Path) -> None: + """Invalid backup (not a database) -> exits nonzero, current DB untouched.""" + indexes = _setup_memory_dirs(tmp_path) + db_path = indexes / "paperforge.db" + backup_path = indexes / "paperforge.db.backup" + _create_valid_db(db_path) + backup_path.write_text("not a database", encoding="utf-8") + result = _run_memory_cmd(tmp_path, "restore-backup", ["--json"]) + data = json.loads(result.stdout) + assert data["ok"] is False + assert "integrity check" in data["error"]["message"].lower() or "validate backup" in data["error"]["message"].lower() or "not a database" in data["error"]["message"].lower() + # Current DB still intact + assert db_path.exists() + + def test_restore_backup_success(self, tmp_path: Path) -> None: + """Valid backup -> restores successfully, old DB preserved.""" + indexes = _setup_memory_dirs(tmp_path) + db_path = indexes / "paperforge.db" + backup_path = indexes / "paperforge.db.backup" + + # Create current DB with old content + _create_valid_db(db_path, paper_count=1) + + # Create backup with more papers + _create_valid_db(backup_path, paper_count=5) + + result = _run_memory_cmd(tmp_path, "restore-backup", ["--json"]) + assert result.returncode == 0 + data = json.loads(result.stdout) + assert data["ok"] is True + assert data["data"]["action"] == "restore_backup" + + # Verify restored DB has 5 papers + conn = sqlite3.connect(str(db_path)) + row = conn.execute("SELECT COUNT(*) FROM papers").fetchone() + conn.close() + assert row[0] == 5 + + # Verify backup still exists (not consumed) + assert backup_path.exists() + + # Verify old DB preserved as corrupt snapshot + corrupt_files = list(indexes.glob("paperforge.corrupt-*.db")) + assert len(corrupt_files) == 1 + + def test_restore_backup_without_current_db(self, tmp_path: Path) -> None: + """No current DB, just backup -> restores directly.""" + indexes = _setup_memory_dirs(tmp_path) + db_path = indexes / "paperforge.db" + backup_path = indexes / "paperforge.db.backup" + _create_valid_db(backup_path, paper_count=3) + + result = _run_memory_cmd(tmp_path, "restore-backup", ["--json"]) + assert result.returncode == 0 + data = json.loads(result.stdout) + assert data["ok"] is True + + # Verify restored + conn = sqlite3.connect(str(db_path)) + row = conn.execute("SELECT COUNT(*) FROM papers").fetchone() + conn.close() + assert row[0] == 3 + + # No corrupt snapshot since there was no current DB + corrupt_files = list(indexes.glob("paperforge.corrupt-*.db")) + assert len(corrupt_files) == 0 + + + def test_replace_failure_leaves_db_and_backup_untouched(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """When os.replace fails, current DB and backup remain unchanged (direct call).""" + import sqlite3 + from paperforge.commands.memory import _restore_backup + + indexes = _setup_memory_dirs(tmp_path) + db_path = indexes / "paperforge.db" + backup_path = indexes / "paperforge.db.backup" + + # Create current DB with known content + conn = sqlite3.connect(str(db_path)) + conn.execute("CREATE TABLE papers (zotero_key TEXT PRIMARY KEY, title TEXT)") + conn.execute("CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT)") + conn.execute("INSERT INTO meta VALUES ('schema_version', '8')") + conn.execute("INSERT INTO papers VALUES ('OLD', 'Old Paper')") + conn.commit() + conn.close() + original_bytes = db_path.read_bytes() + + # Create backup with different content + conn = sqlite3.connect(str(backup_path)) + conn.execute("CREATE TABLE papers (zotero_key TEXT PRIMARY KEY, title TEXT)") + conn.execute("CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT)") + conn.execute("INSERT INTO meta VALUES ('schema_version', '8')") + conn.execute("INSERT INTO papers VALUES ('NEW', 'New Paper')") + conn.commit() + conn.close() + backup_bytes = backup_path.read_bytes() + + # Monkeypatch os.replace to fail (direct call, so monkeypatch works) + import os as _os + def _failing_replace(src, dst): + raise OSError("simulated replace failure") + monkeypatch.setattr(_os, "replace", _failing_replace) + + result = _restore_backup(tmp_path) + assert result.ok is False + assert "Failed to restore backup" in result.error.message + + # Original DB still intact (byte-identical) + assert db_path.read_bytes() == original_bytes + + # Backup still intact + assert backup_path.exists() + assert backup_path.read_bytes() == backup_bytes + + # Clean up temp files + for f in indexes.glob("paperforge.restore-*"): + f.unlink(missing_ok=True) + + +class TestMemoryRestoreProbe: + """Probe emits restore_backup when corrupt DB + backup exists.""" + + def test_corrupt_db_with_backup_emits_restore(self, tmp_path: Path) -> None: + """Corrupt DB + valid backup -> restore_backup action.""" + from paperforge.commands import probe as probe_mod + + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + indexes = tmp_path / "99_System" / "PaperForge" / "indexes" + indexes.mkdir(parents=True, exist_ok=True) + + # Create corrupt DB + (indexes / "paperforge.db").write_text("not a database", encoding="utf-8") + # Create valid backup + _create_valid_db(indexes / "paperforge.db.backup", paper_count=3) + + data = probe_mod.probe_memory(tmp_path) + assert data["capability_state"] == "unavailable" + assert data["reason"]["code"] == "memory.db_corrupt" + assert data["action"]["primary"]["verb"] == "restore_backup" + assert data["action"]["primary"]["command"] == "paperforge memory restore-backup" + assert data["action"]["primary"]["destructive"] is True + assert data["action"]["primary"]["confirmation_required"] is True + + def test_corrupt_db_without_backup_emits_rebuild(self, tmp_path: Path) -> None: + """Corrupt DB without backup -> rebuild action.""" + from paperforge.commands import probe as probe_mod + + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + indexes = tmp_path / "99_System" / "PaperForge" / "indexes" + indexes.mkdir(parents=True, exist_ok=True) + + # Create corrupt DB, no backup + (indexes / "paperforge.db").write_text("not a database", encoding="utf-8") + + data = probe_mod.probe_memory(tmp_path) + assert data["capability_state"] == "unavailable" + assert data["reason"]["code"] == "memory.db_corrupt" + assert data["action"]["primary"]["verb"] == "run" + assert data["action"]["primary"]["command"] == "paperforge memory build" diff --git a/tests/test_probe.py b/tests/test_probe.py index ec8673c8..20c8c2fc 100644 --- a/tests/test_probe.py +++ b/tests/test_probe.py @@ -14,6 +14,7 @@ from __future__ import annotations import json import subprocess +import os import sys from pathlib import Path @@ -41,7 +42,7 @@ VALID_STATES = {"unknown", "unavailable", "missing_input", "needs_action", "limi VALID_SEVERITIES = {"ok", "warning", "error", "info"} -def _run_probe(module: str, vault: Path, extra_args: list[str] | None = None) -> dict: +def _run_probe(module: str, vault: Path, extra_args: list[str] | None = None, env: dict[str, str] | None = None) -> dict: """Run `paperforge probe --json` in a subprocess and return parsed JSON. NOTE: --vault must come BEFORE the subcommand (argparse global args rule). @@ -58,7 +59,8 @@ def _run_probe(module: str, vault: Path, extra_args: list[str] | None = None) -> ] if extra_args: cmd.extend(extra_args) - result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + run_env = env if env is not None else os.environ.copy() + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15, env=run_env) assert result.returncode == 0, ( f"CLI exited {result.returncode}\nstdout: {result.stdout[:500]}\nstderr: {result.stderr[:500]}" ) @@ -322,7 +324,7 @@ class TestProbeCliArgs: """Unknown module name is rejected by argparse.""" result = subprocess.run( [sys.executable, "-m", "paperforge", "--vault", str(tmp_path), - "probe", "ocr", "--json"], + "probe", "maintenance", "--json"], capture_output=True, text=True, timeout=15, ) assert result.returncode != 0 @@ -345,3 +347,587 @@ class TestProbeCliArgs: capture_output=True, text=True, timeout=15, ) assert result.returncode != 0 + +# --------------------------------------------------------------------------- +# Library probe states (Issue #78) +# --------------------------------------------------------------------------- + +class TestLibraryProbe: + """State mapping for the library module probe.""" + + def test_missing_paperforge_json(self, tmp_path: Path) -> None: + """No paperforge.json -> missing_input + set_config action.""" + data = _run_probe('library', tmp_path) + assert data['module'] == 'library' + assert data['capability_state'] == 'missing_input' + assert data['severity'] == 'warning' + assert data['reason']['code'] == 'library.config_missing' + assert data['action']['primary'] is not None + _assert_action_primary_shape(data['action']['primary']) + assert data['action']['primary']['verb'] == 'set_config' + + def test_corrupt_config(self, tmp_path: Path) -> None: + """Corrupt JSON -> unavailable + setup action.""" + (tmp_path / 'paperforge.json').write_text('{{{', encoding='utf-8') + data = _run_probe('library', tmp_path) + assert data['capability_state'] == 'unavailable' + assert data['severity'] == 'error' + assert data['reason']['code'] == 'library.config_corrupt' + + def test_zotero_not_configured(self, tmp_path: Path) -> None: + """Config exists but no zotero_data_dir -> missing_input.""" + (tmp_path / 'paperforge.json').write_text( + json.dumps({'system_dir': '99_System'}), encoding='utf-8', + ) + data = _run_probe('library', tmp_path) + assert data['capability_state'] == 'missing_input' + assert data['reason']['code'] == 'library.zotero_missing' + assert data['action']['primary']['verb'] == 'set_config' + + def test_envelope_shape(self, tmp_path: Path) -> None: + """Library envelope has all required fields.""" + (tmp_path / 'paperforge.json').write_text( + json.dumps({'system_dir': '99_System', 'zotero_data_dir': str(tmp_path)}), encoding='utf-8', + ) + data = _run_probe('library', tmp_path) + _assert_envelope_shape(data) + assert data['ttl_seconds'] == 300 + + def test_primitive_config_corrupt(self, tmp_path: Path) -> None: + """Non-dict config -> unavailable.""" + (tmp_path / 'paperforge.json').write_text('42', encoding='utf-8') + data = _run_probe('library', tmp_path) + assert data['capability_state'] == 'unavailable' + assert data['reason']['code'] == 'library.config_corrupt' + +# --------------------------------------------------------------------------- +# OCR probe states (Issue #78) +# --------------------------------------------------------------------------- + + def test_unrecognized_config_corrupt(self, tmp_path: Path) -> None: + """Parseable dict without recognized keys -> config_corrupt/unavailable.""" + (tmp_path / 'paperforge.json').write_text( + json.dumps({"name": "Foo", "version": "1.0"}), encoding='utf-8', + ) + data = _run_probe('library', tmp_path) + assert data['capability_state'] == 'unavailable' + assert data['severity'] == 'error' + assert data['reason']['code'] == 'library.config_corrupt' + assert data['action']['primary']['verb'] == 'setup' + + def test_sync_failed_nonzero_exit_code(self, tmp_path: Path) -> None: + """Nonzero last_operation_exit_code -> sync_failed envelope (direct call).""" + from paperforge.commands import probe as probe_mod + (tmp_path / 'paperforge.json').write_text( + json.dumps({"system_dir": "99_System", "zotero_data_dir": str(tmp_path)}), encoding='utf-8', + ) + data = probe_mod.probe_library(tmp_path, last_operation_exit_code=7) + assert data['module'] == 'library' + assert data['capability_state'] == 'needs_action' + assert data['severity'] == 'error' + assert data['reason']['code'] == 'library.sync_failed' + assert 'exit code 7' in data['reason']['text'] + assert data['action']['primary']['verb'] == 'sync' + assert data['action']['primary']['command'] == 'paperforge sync' + + def test_sync_success_zero_exit_code_normal(self, tmp_path: Path) -> None: + """Zero last_operation_exit_code -> normal probe, not sync_failed.""" + from paperforge.commands import probe as probe_mod + (tmp_path / 'paperforge.json').write_text( + json.dumps({"system_dir": "99_System", "zotero_data_dir": str(tmp_path)}), encoding='utf-8', + ) + data = probe_mod.probe_library(tmp_path, last_operation_exit_code=0) + assert data['module'] == 'library' + assert data['reason']['code'] != 'library.sync_failed' + # Normal probe falls through to index check (missing in fresh tmp_path) + assert data['reason']['code'] == 'library.index_missing' + + +class TestOcrProbe: + """State mapping for the OCR module probe.""" + + def test_missing_paperforge_json(self, tmp_path: Path) -> None: + """No paperforge.json -> missing_input + set_config action.""" + data = _run_probe('ocr', tmp_path) + assert data['module'] == 'ocr' + assert data['capability_state'] == 'missing_input' + assert data['severity'] == 'warning' + assert data['reason']['code'] == 'ocr.config_missing' + assert data['action']['primary']['verb'] == 'set_config' + + def test_corrupt_config(self, tmp_path: Path) -> None: + """Corrupt JSON -> unavailable.""" + (tmp_path / 'paperforge.json').write_text('{{{', encoding='utf-8') + data = _run_probe('ocr', tmp_path) + assert data['capability_state'] == 'unavailable' + assert data['reason']['code'] == 'ocr.config_corrupt' + + def test_api_key_missing(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Config exists but no API token anywhere -> missing_input.""" + from paperforge.commands import probe as probe_mod + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + # Mock _resolve_paddleocr_token to return empty (no token from any source) + monkeypatch.setattr("paperforge.worker.ocr._resolve_paddleocr_token", lambda v: "") + data = probe_mod.probe_ocr(tmp_path) + assert data["capability_state"] == "missing_input" + assert data["reason"]["code"] == "ocr.api_key_missing" + + def test_non_dict_config_corrupt(self, tmp_path: Path) -> None: + """Non-dict config -> unavailable.""" + (tmp_path / 'paperforge.json').write_text('true', encoding='utf-8') + data = _run_probe('ocr', tmp_path) + assert data['capability_state'] == 'unavailable' + assert data['reason']['code'] == 'ocr.config_corrupt' + def test_unrecognized_config_corrupt(self, tmp_path: Path) -> None: + """Parseable dict without recognized keys -> config_corrupt/unavailable.""" + (tmp_path / 'paperforge.json').write_text( + json.dumps({"name": "Foo", "version": "1.0"}), encoding='utf-8', + ) + data = _run_probe('ocr', tmp_path) + assert data['capability_state'] == 'unavailable' + assert data['severity'] == 'error' + assert data['reason']['code'] == 'ocr.config_corrupt' + assert data['action']['primary']['verb'] == 'setup' + + + +# --------------------------------------------------------------------------- +# Memory probe states (Issue #78) +# --------------------------------------------------------------------------- + +class TestMemoryProbe: + """State mapping for the memory module probe.""" + + def test_db_missing(self, tmp_path: Path) -> None: + """No paperforge.db -> needs_action + run action.""" + data = _run_probe("memory", tmp_path) + assert data["module"] == "memory" + assert data["capability_state"] == "needs_action" + assert data["reason"]["code"] == "memory.db_missing" + assert data["action"]["primary"] is not None + _assert_action_primary_shape(data["action"]["primary"]) + assert data["action"]["primary"]["verb"] == "run" + + def test_envelope_shape(self, tmp_path: Path) -> None: + """Memory envelope has all required fields.""" + data = _run_probe('memory', tmp_path) + _assert_envelope_shape(data) + assert data['ttl_seconds'] == 300 + + def test_db_corrupt(self, tmp_path: Path) -> None: + """Corrupt database -> unavailable + run action.""" + # Create paperforge.json so canonical path resolution works + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + indexes = tmp_path / "99_System" / "PaperForge" / "indexes" + indexes.mkdir(parents=True, exist_ok=True) + (indexes / "paperforge.db").write_text("not a database", encoding="utf-8") + data = _run_probe("memory", tmp_path) + assert data["capability_state"] == "unavailable" + assert data["reason"]["code"] == "memory.db_corrupt" + action = data["action"]["primary"] + _assert_action_primary_shape(action) + assert action["verb"] == "run" + assert action["command"] == "paperforge memory build" + + +# --------------------------------------------------------------------------- +# Library/OCR/Memory canonical probe tests (Issue #78 repair) +# --------------------------------------------------------------------------- + +class TestLibraryProbeCanonical: + """Library probe with canonical formal-library.json validation.""" + + def test_ready_with_canonical_index(self, tmp_path: Path) -> None: + """Valid formal-library.json + matching export hash -> ready.""" + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System", "zotero_data_dir": str(tmp_path)}), + encoding="utf-8", + ) + indexes_dir = tmp_path / "99_System" / "PaperForge" / "indexes" + indexes_dir.mkdir(parents=True, exist_ok=True) + (indexes_dir / "formal-library.json").write_text(json.dumps({ + "schema_version": "2", + "items": [{"zotero_key": "ABC123", "title": "Test Paper"}], + "paper_count": 1, + }), encoding="utf-8") + + data = _run_probe("library", tmp_path) + assert data["module"] == "library" + assert data["capability_state"] != "unavailable" + assert data["severity"] != "error" + _assert_envelope_shape(data) + + def test_malformed_index(self, tmp_path: Path) -> None: + """Non-dict non-list formal-library.json -> needs_action.""" + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System", "zotero_data_dir": str(tmp_path)}), + encoding="utf-8", + ) + indexes_dir = tmp_path / "99_System" / "PaperForge" / "indexes" + indexes_dir.mkdir(parents=True, exist_ok=True) + (indexes_dir / "formal-library.json").write_text('"just a string"', encoding="utf-8") + + data = _run_probe("library", tmp_path) + assert data["capability_state"] == "needs_action" + assert data["reason"]["code"] in ("library.index_corrupt", "library.index_legacy") + + +class TestOcrProbeCanonical: + """OCR probe with PADDLEOCR_API_TOKEN env credential.""" + + def test_credential_from_env(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """PADDLEOCR_API_TOKEN in env -> passes credential check.""" + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + monkeypatch.setenv("PADDLEOCR_API_TOKEN", "test-token-12345") + + data = _run_probe("ocr", tmp_path) + assert data["reason"]["code"] != "ocr.api_key_missing" + _assert_envelope_shape(data) + + def test_no_credential_no_config(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """No PADDLEOCR_API_TOKEN in env + no config -> missing_input.""" + monkeypatch.delenv("PADDLEOCR_API_TOKEN", raising=False) + monkeypatch.delenv("PADDLEOCR_API_TOKEN_USER", raising=False) + data = _run_probe("ocr", tmp_path) + assert data["capability_state"] == "missing_input" + assert data["reason"]["code"] == "ocr.config_missing" + + +class TestMemoryProbeCanonical: + """Memory probe distinguishing corruption from schema mismatch.""" + + def test_populated_db_old_schema(self, tmp_path: Path) -> None: + """DB with papers but old schema -> migration_needed.""" + import sqlite3 + # Create paperforge.json for canonical path resolution + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + indexes = tmp_path / "99_System" / "PaperForge" / "indexes" + indexes.mkdir(parents=True, exist_ok=True) + db_path = indexes / "paperforge.db" + + conn = sqlite3.connect(str(db_path)) + conn.execute("CREATE TABLE papers (zotero_key TEXT, title TEXT)") + conn.execute("CREATE TABLE meta (key TEXT, value TEXT)") + conn.execute("INSERT INTO meta VALUES ('schema_version', '3')") + conn.execute("INSERT INTO papers VALUES ('KEY1', 'Test Paper')") + conn.commit() + conn.close() + + data = _run_probe("memory", tmp_path) + _assert_envelope_shape(data) + assert data["reason"]["code"] == "memory.migration_needed" + assert data["action"]["primary"]["verb"] == "rebuild_index" + + +# --------------------------------------------------------------------------- +# All-probe smoke test (Issue #78) +# --------------------------------------------------------------------------- + +class TestAllProbes: + """Verify all five real probes emit valid envelopes.""" + + @pytest.mark.parametrize('module', ['installation', 'library', 'ocr', 'memory', 'help']) + def test_probe_emits_valid_envelope(self, module: str, tmp_path: Path) -> None: + """Every real probe emits a valid envelope.""" + if module in ('installation', 'library', 'ocr'): + (tmp_path / 'paperforge.json').write_text( + json.dumps({'system_dir': '99_System'}), encoding='utf-8', + ) + data = _run_probe(module, tmp_path) + _assert_envelope_shape(data) + assert data['module'] == module + assert data['severity'] in VALID_SEVERITIES + + def test_cli_accepts_all_modules(self, tmp_path: Path) -> None: + """CLI accepts all five module names.""" + (tmp_path / 'paperforge.json').write_text( + json.dumps({'system_dir': '99_System'}), encoding='utf-8', + ) + for mod in ('installation', 'library', 'ocr', 'memory', 'help'): + result = subprocess.run( + [sys.executable, '-m', 'paperforge', '--vault', str(tmp_path), + 'probe', mod, '--json'], + capture_output=True, text=True, timeout=15, + ) + assert result.returncode == 0, f'module={mod}: {result.stderr[:200]}' + + + +# --------------------------------------------------------------------------- +# Issue #78 concrete fix tests +# --------------------------------------------------------------------------- + +class TestOcrConcreteFixes: + """Concrete correctness fixes for Issue #78 OCR probe.""" + + def test_healthy_display_action_none_is_ready(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Row with display_action='none', no failures → ready.""" + from paperforge.commands import probe as probe_mod + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + monkeypatch.setenv("PADDLEOCR_API_TOKEN", "test-token") + + class FakeRow: + status = "ok" + health = "green" + display_action = "none" + monkeypatch.setattr("paperforge.worker.ocr_maintenance.collect_maintenance_rows", + lambda v: [FakeRow()]) + monkeypatch.setattr("paperforge.ocr_diagnostics.ocr_doctor", + lambda config=None, live=False: {"passed": True}) + + data = probe_mod.probe_ocr(tmp_path) + assert data["capability_state"] == "ready" + assert data["severity"] == "ok" + assert data["reason"]["code"] == "ocr.ready" + + def test_running_rows_independent_from_actionable(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Running rows with display_action='retry_ocr' → needs_action with activity overlay.""" + from paperforge.commands import probe as probe_mod + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + monkeypatch.setenv("PADDLEOCR_API_TOKEN", "test-token") + + class FakeRow: + status = "running" + health = "green" + display_action = "retry_ocr" + monkeypatch.setattr("paperforge.worker.ocr_maintenance.collect_maintenance_rows", + lambda v: [FakeRow()]) + monkeypatch.setattr("paperforge.ocr_diagnostics.ocr_doctor", + lambda config=None, live=False: {"passed": True}) + + data = probe_mod.probe_ocr(tmp_path) + # Running status → activity overlay + assert data["activity_state"] == "running" + assert data["activity_label"] is not None + assert data["activity_progress"] is not None + # Independent actionable state preserved + assert data["capability_state"] == "needs_action" + assert data["reason"]["code"] == "ocr.quality_failures" + + def test_collect_maintenance_rows_exception_returns_unknown(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """When collect_maintenance_rows raises → unknown/probe, not empty run.""" + from paperforge.commands import probe as probe_mod + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + monkeypatch.setenv("PADDLEOCR_API_TOKEN", "test-token") + monkeypatch.setattr("paperforge.ocr_diagnostics.ocr_doctor", + lambda config=None, live=False: {"passed": True}) + def _raise(*args, **kwargs): + raise RuntimeError("boom") + monkeypatch.setattr("paperforge.worker.ocr_maintenance.collect_maintenance_rows", _raise) + + data = probe_mod.probe_ocr(tmp_path) + assert data["capability_state"] == "unknown" + assert data["severity"] == "unknown" + assert data["reason"]["code"] == "ocr.probe_failed" + assert data["action"]["primary"]["verb"] == "probe" + + +class TestMemoryConcreteFixes: + """Concrete correctness fixes for Issue #78 Memory probe.""" + + def test_get_memory_status_exception_returns_unknown(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """When get_memory_status raises → unknown/probe, not unavailable/rebuild.""" + from paperforge.commands import probe as probe_mod + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + def _raise(*args, **kwargs): + raise RuntimeError("db broken") + monkeypatch.setattr("paperforge.memory.query.get_memory_status", _raise) + + data = probe_mod.probe_memory(tmp_path) + assert data["capability_state"] == "unknown" + assert data["severity"] == "unknown" + assert data["reason"]["code"] == "memory.probe_failed" + assert data["action"]["primary"]["verb"] == "probe" + + +class TestOcrPriorityOrdering: + """OCR probe priority: redo > run > rebuild > investigate (Issue #78 repair).""" + + def test_pending_overrides_provider_unreachable(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Pending + provider unreachable -> needs_action/run, NOT limited/investigate.""" + from paperforge.commands import probe as probe_mod + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + monkeypatch.setenv("PADDLEOCR_API_TOKEN", "test-token") + monkeypatch.setattr("paperforge.ocr_diagnostics.ocr_doctor", + lambda config=None, live=False: {"passed": False, "error": "unreachable"}) + + class FakePendingRow: + status = "pending" + health = "green" + display_action = "none" + monkeypatch.setattr("paperforge.worker.ocr_maintenance.collect_maintenance_rows", + lambda v: [FakePendingRow()]) + + data = probe_mod.probe_ocr(tmp_path) + # Pending (run) beats provider unreachable (investigate) + assert data["capability_state"] == "needs_action" + assert data["reason"]["code"] == "ocr.pending" + assert data["action"]["primary"]["verb"] == "run" + + def test_degraded_overrides_unexpected(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Degraded + unexpected action -> needs_action/rebuild, NOT limited/investigate.""" + from paperforge.commands import probe as probe_mod + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + monkeypatch.setenv("PADDLEOCR_API_TOKEN", "test-token") + monkeypatch.setattr("paperforge.ocr_diagnostics.ocr_doctor", + lambda config=None, live=False: {"passed": True}) + + class FakeDegradedRow: + status = "done" + health = "yellow" + display_action = "rebuild_result" + display_severity = "actionable" + + class FakeUnexpectedRow: + status = "done" + health = "green" + display_action = "future_action" + display_severity = "actionable" + + monkeypatch.setattr("paperforge.worker.ocr_maintenance.collect_maintenance_rows", + lambda v: [FakeDegradedRow(), FakeUnexpectedRow()]) + + data = probe_mod.probe_ocr(tmp_path) + # Degraded (rebuild) beats unexpected (investigate) + assert data["capability_state"] == "needs_action" + assert data["reason"]["code"] == "ocr.artifacts_stale" + assert data["action"]["primary"]["verb"] == "rebuild_derived" + + def test_redo_overrides_pending(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Redo + pending rows -> redo action, NOT run.""" + from paperforge.commands import probe as probe_mod + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + monkeypatch.setenv("PADDLEOCR_API_TOKEN", "test-token") + monkeypatch.setattr("paperforge.ocr_diagnostics.ocr_doctor", + lambda config=None, live=False: {"passed": True}) + + class FakeRedoPendingRow: + status = "pending" + health = "red" + display_action = "retry_ocr" + monkeypatch.setattr("paperforge.worker.ocr_maintenance.collect_maintenance_rows", + lambda v: [FakeRedoPendingRow()]) + + data = probe_mod.probe_ocr(tmp_path) + # Redo (retry_ocr) beats pending (run) + assert data["capability_state"] == "needs_action" + assert data["reason"]["code"] == "ocr.quality_failures" + assert data["action"]["primary"]["verb"] == "redo" + + + +class TestOcrRebuildResultNonDestructive: + """rebuild_result rows must never become destructive redo (Issue #78 repair).""" + + def test_red_health_rebuild_result_is_rebuild_not_redo(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Red health + display_action='rebuild_result' -> rebuild_derived, NOT redo.""" + from paperforge.commands import probe as probe_mod + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + monkeypatch.setenv("PADDLEOCR_API_TOKEN", "test-token") + monkeypatch.setattr("paperforge.ocr_diagnostics.ocr_doctor", + lambda config=None, live=False: {"passed": True}) + + class FakeRebuildRedRow: + status = "done" + health = "red" + display_action = "rebuild_result" + + monkeypatch.setattr("paperforge.worker.ocr_maintenance.collect_maintenance_rows", + lambda v: [FakeRebuildRedRow()]) + + data = probe_mod.probe_ocr(tmp_path) + # rebuild_result must never become redo, even with red health + assert data["capability_state"] == "needs_action" + assert data["reason"]["code"] == "ocr.artifacts_stale" + assert data["action"]["primary"]["verb"] == "rebuild_derived" + assert data["action"]["primary"]["destructive"] == False + + +class TestOcrActivityProgress: + """OCR activity progress current = terminal/completed count, not running/queued.""" + + def test_queued_only_batch_progress_zero_of_N(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """All queued -> progress current=0, total=N.""" + from paperforge.commands import probe as probe_mod + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + monkeypatch.setenv("PADDLEOCR_API_TOKEN", "test-token") + monkeypatch.setattr("paperforge.ocr_diagnostics.ocr_doctor", + lambda config=None, live=False: {"passed": True}) + + rows = [] + for i in range(5): + class FakeQueuedRow: + status = "queued" + health = "green" + display_action = "none" + rows.append(FakeQueuedRow()) + + monkeypatch.setattr("paperforge.worker.ocr_maintenance.collect_maintenance_rows", + lambda v: rows) + + data = probe_mod.probe_ocr(tmp_path) + assert data["activity_state"] == "running" + assert data["activity_progress"] is not None + assert data["activity_progress"]["current"] == 0 # no completed rows + assert data["activity_progress"]["total"] == 5 + assert "0/5" in data["activity_label"] + + def test_mixed_done_queued_progress_completed_count(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """3 done_degraded + 2 queued -> progress current=3, total=5.""" + from paperforge.commands import probe as probe_mod + (tmp_path / "paperforge.json").write_text( + json.dumps({"system_dir": "99_System"}), encoding="utf-8", + ) + monkeypatch.setenv("PADDLEOCR_API_TOKEN", "test-token") + monkeypatch.setattr("paperforge.ocr_diagnostics.ocr_doctor", + lambda config=None, live=False: {"passed": True}) + + rows = [] + for i in range(3): + class FakeDoneRow: + status = "done_degraded" + health = "yellow" + display_action = "rebuild_result" + rows.append(FakeDoneRow()) + for i in range(2): + class FakeQueuedRow: + status = "queued" + health = "green" + display_action = "none" + rows.append(FakeQueuedRow()) + + monkeypatch.setattr("paperforge.worker.ocr_maintenance.collect_maintenance_rows", + lambda v: rows) + + data = probe_mod.probe_ocr(tmp_path) + assert data["activity_state"] == "running" + assert data["activity_progress"] is not None + assert data["activity_progress"]["current"] == 3 # 3 completed rows + assert data["activity_progress"]["total"] == 5 + assert "3/5" in data["activity_label"]