From 85a7ae30a297a53e706bbbdbfe798941a52f38fe Mon Sep 17 00:00:00 2001
From: LLLin000 <809867916@qq.com>
Date: Wed, 15 Jul 2026 20:36:15 +0800
Subject: [PATCH] feat: add OCR rebuild UX and capability tracer
---
PROJECT-MANAGEMENT.md | 20 +-
paperforge/cli.py | 19 +
paperforge/commands/probe.py | 297 ++++++++
paperforge/plugin/main.js | 58 +-
paperforge/plugin/src/constants.ts | 303 ++++++++
paperforge/plugin/src/i18n.ts | 124 +++-
paperforge/plugin/src/settings.ts | 590 +++++++++-------
paperforge/plugin/src/views/modals.ts | 5 +-
paperforge/plugin/styles.css | 267 +++++++
.../plugin/tests/capability-state.test.ts | 664 ++++++++++++++++++
project/current/ocr-v2-active-queue.md | 8 +-
tests/test_probe.py | 347 +++++++++
12 files changed, 2415 insertions(+), 287 deletions(-)
create mode 100644 paperforge/commands/probe.py
create mode 100644 paperforge/plugin/tests/capability-state.test.ts
create mode 100644 tests/test_probe.py
diff --git a/PROJECT-MANAGEMENT.md b/PROJECT-MANAGEMENT.md
index cc6842f9..54a22a13 100644
--- a/PROJECT-MANAGEMENT.md
+++ b/PROJECT-MANAGEMENT.md
@@ -1,13 +1,13 @@
> **Branch:** `feat/ocr-rebuild-ux` | **Last Updated:** 2026-07-15
-> **Active work:** [Control-center PRD #74](https://github.com/LLLin000/PaperForge/issues/74) is split into eight dependency-linked implementation issues (#75–#82). The first slice, canonical setup/config migration (#75), is implemented and reviewed; Installation capability integration (#76) is the next unblocked issue.
+> **Active work:** [Control-center PRD #74](https://github.com/LLLin000/PaperForge/issues/74) is split into eight dependency-linked implementation issues (#75–#82). Canonical setup/config migration (#75) and the Installation/Help capability tracer (#76) are implemented; Managed Runtime lifecycle (#77) is the next production slice.
>
> ---
>
-> **Current state:** Retrieval recovery is merged and live. OCR multi-key rebuild/redo streams progress and refreshes canonical maintenance state. The control-plane capability vocabulary, managed-runtime architecture, prototypes, migration contract, and PRD are resolved. The first production slice now routes bare, headless, and modular setup through one `SetupPlan`, writes schema-v2 `vault_config`, reads legacy path keys only as warned fallback, forwards all configured paths, and returns non-zero on required-step failure.
+> **Current state:** Retrieval recovery is merged and live. OCR multi-key rebuild/redo streams progress and refreshes canonical maintenance state. The control plane now has schema-v1 capability envelopes from Python through the Obsidian settings surface: Installation and Help use real probes, stale/malformed cached evidence fails closed, and Library/OCR/Memory/Maintenance remain explicit placeholders for later tracers.
>
-> #71/#72 prototypes and #73 migration contract are complete. PRD #74 defines the six-module production program and #75–#82 form its native dependency graph. Issue #75 passed focused verification and independent spec/quality review.
+> #71/#72 prototypes and #73 migration contract are complete. PRD #74 defines the six-module production program; #75 and #76 are implemented. The approved navigation refinement preserves the Wayfinder overview and stages `概览 / 模块详情 / 维护 / 帮助` across #77/#78/#80 instead of creating dead placeholder detail pages.
>
-> Next: start [#76](https://github.com/LLLin000/PaperForge/issues/76) in a fresh Matt `/implement` session; do not continue in this context.
+> Next: implement [#77](https://github.com/LLLin000/PaperForge/issues/77) in a fresh Matt `/implement` session.
## 1. Architecture
### 1.1 The problem (pre-v2)
@@ -59,6 +59,7 @@ raw observations → structural signatures → stable anchors/families → zone
| Live Literature-hub maintenance UI | **734 All / 700 Recommended; no captured errors** ✅ |
| Maintenance regression action model | **19/19 passed** ✅ (per-row canonical action routing, redo confirmation gate, cache manifest preservation) |
| Canonical setup/config migration (#75) | **61 passed, 0 failed** ✅ (fresh/v1/v2 config, CLI routing, path forwarding, failure exit, idempotent rerun) |
+| Installation/Help capability tracer (#76) | **21 backend tests + 169 plugin tests passed; typecheck/build clean; independent review PASS** ✅ |
@@ -83,6 +84,7 @@ raw observations → structural signatures → stable anchors/families → zone
| **Vector build control** | ✅ canonical SQLite lifecycle and health state restored |
| **Control-plane contracts** | ✅ Orthogonal capability/activity/attention model and managed-runtime immutable-slot architecture chosen; documented in `docs/research/2026-07-14-capability-state-action-contract.md` and `docs/research/2026-07-14-managed-runtime-architecture.md` |
| **Setup/config migration** | ✅ Bare, headless, and modular setup share `SetupPlan`; schema-v2 `vault_config` is authoritative with warned v1 read fallback |
+| **Capability tracer** | ✅ Installation/Help backend envelopes feed the six-module Overview; malformed/stale persistence fails closed; setup/update actions route to the setup flow |
### 2.3 Fix Status
@@ -148,7 +150,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. The orthogonal capability/activity/attention model (`docs/research/2026-07-14-capability-state-action-contract.md`) and managed-runtime immutable-slot architecture (`docs/research/2026-07-14-managed-runtime-architecture.md`) are chosen, unblocking #71/#72 prototypes. The remaining control-plane contract gaps are recorded in `docs/research/2026-07-14-control-center-contract-audit.md`.
+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. Issue #76 now provides the first production capability-envelope tracer: real Installation/Help probes, a six-module Overview, strict persisted-envelope validation, stale fail-closed behavior, and backend-owned actions. Library/OCR/Memory/Maintenance remain explicit placeholders until #78/#80. The approved Wayfinder navigation refinement is recorded on PRD #74 and staged across #77/#78/#80.
### 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).
@@ -156,7 +158,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 is complete; #76 is the next unblocked production tracer.
+1. 🔴 **[Control-center PRD #74](https://github.com/LLLin000/PaperForge/issues/74)** — published and split into eight native dependency-linked issues (#75–#82). #75 and #76 are implemented; #77 Managed Runtime lifecycle is the next unblocked production tracer.
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.
@@ -181,7 +183,8 @@ Remaining legacy OCR issues (carried forward):
- [x] Design actionable-only maintenance inbox ([#72](https://github.com/LLLin000/PaperForge/issues/72))
- [x] Publish PRD #74 and eight agent-ready issues (#75–#82) with native dependencies
- [x] Canonicalize setup/config migration ([#75](https://github.com/LLLin000/PaperForge/issues/75)) — 61 focused tests; spec PASS; quality APPROVED
-- [ ] Implement Installation/Help capability tracer in a fresh session ([#76](https://github.com/LLLin000/PaperForge/issues/76))
+- [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
+- [ ] Implement Managed Runtime lifecycle and the approved Installation-detail navigation shell ([#77](https://github.com/LLLin000/PaperForge/issues/77))
---
## 5. Key File Map
@@ -336,6 +339,8 @@ Remaining legacy OCR issues (carried forward):
| 2026-07-15 | Issue-draft flow is confirmation-first, inline redacted, never auto-submitted | User reviews a prefilled GitHub draft panel inline, edits freely, then clicks "Open GitHub" to create. No token storage, no automatic submission. Docker-style opaque upload rejected. |
| 2026-07-15 | Prototype reviews use independent Critical/Important pass/fail gates | Both #71 and #72 prototypes were reviewed by independent reviewer subagents on two dimensions: Critical items (correctness/safety) and Important items (readability/maintainability). All items passed before acceptance. |
| 2026-07-15 | Canonical setup is one `SetupPlan`; configuration writes converge to schema v2 | Duplicate headless/modular/bare engines dropped path inputs and disagreed on success. One engine plus `vault_config`-first reads makes migration observable, idempotent, and reversible through the warned v1 read fallback. |
+| 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. |
---
@@ -438,6 +443,7 @@ python -m ruff check paperforge/worker/ocr_*.py
| 2026-07-14 | Obsidian and desktop recovery research | Closed #67 and #68 with primary-source pattern reports. Preserved the six-module IA, independent capability probes, Obsidian-managed plugin updates, module-scoped recovery, local diagnostics, and user-reviewed issue drafts; unblocked #70. | [#67](https://github.com/LLLin000/PaperForge/issues/67#issuecomment-4970653461), [#68](https://github.com/LLLin000/PaperForge/issues/68#issuecomment-4970660288) |
| 2026-07-15 | Six-module control-center + maintenance inbox prototypes completed | Closed #71 (six-module HTML prototype, 5 scenarios, plain-button switcher, responsive 768px) with independent Critical/Important PASS review and browser verification. Closed #72 (actionable-only inbox, inline issue-draft review, confirmation-first report) with same review gate. Both prototype pairs passed all review dimensions. No production code changed. | `docs/prototypes/2026-07-14-six-module-control-center.{html,md}`, `docs/prototypes/2026-07-14-maintenance-issue-reporting.{html,md}` |
| 2026-07-15 | Control-center PRD split + first production slice | Published PRD #74 and eight native dependency-linked issues (#75–#82). Implemented #75: one SetupPlan for all setup entry points, schema-v2 `vault_config`, warned v1 read fallback, complete path forwarding, visible deprecation, and non-zero required-step failures. Verification: 61/61 focused tests; independent Spec PASS / Quality APPROVED. | [PRD #74](https://github.com/LLLin000/PaperForge/issues/74), [Issue #75](https://github.com/LLLin000/PaperForge/issues/75) |
+| 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) |
## 9. Historical Detail Archive
diff --git a/paperforge/cli.py b/paperforge/cli.py
index c26e51c2..ac38e21a 100644
--- a/paperforge/cli.py
+++ b/paperforge/cli.py
@@ -479,6 +479,19 @@ def build_parser() -> argparse.ArgumentParser:
p_scoped_fetch.add_argument("--json", action="store_true", help="Output JSON")
p_scoped_fetch.add_argument("--limit", type=int, default=5, help="Max results (default 5)")
+ # probe
+ 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)",
+ )
+ p_probe.add_argument(
+ "--json",
+ action="store_true",
+ help="Output as schema-v1 capability envelope JSON",
+ )
+
return parser
@@ -529,6 +542,7 @@ def main(argv: list[str] | None = None) -> int:
"project-log", "search", "agent-context", "runtime-health", "doctor",
"update", "setup", "selection-sync", "index-refresh", "base-refresh",
"paper-lookup", "content-discovery", "paper-navigation", "scoped-fetch",
+ "probe",
}
if args.command in lightweight_commands:
_resolve_pipeline()
@@ -761,6 +775,11 @@ def main(argv: list[str] | None = None) -> int:
)
return plan.execute(json_output=getattr(args, "json_output", False))
+ if args.command == "probe":
+ from paperforge.commands.probe import run as run_probe
+
+ return run_probe(args)
+
print(f"Error: unknown command {args.command}", file=sys.stderr)
return 1
diff --git a/paperforge/commands/probe.py b/paperforge/commands/probe.py
new file mode 100644
index 00000000..f67f1584
--- /dev/null
+++ b/paperforge/commands/probe.py
@@ -0,0 +1,297 @@
+"""paperforge.commands.probe — Capability probe command (Issue #76, 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.
+"""
+
+from __future__ import annotations
+
+import json
+import sys
+from datetime import datetime, timezone
+from pathlib import Path
+from typing import Any
+
+# ---------------------------------------------------------------------------
+# Constants
+# ---------------------------------------------------------------------------
+
+SCHEMA_VERSION = 1
+TTL_INSTALLATION = 3600
+TTL_HELP = 3600
+MIN_PYTHON = (3, 11)
+LEGACY_PATH_KEYS = frozenset({
+ "system_dir", "resources_dir", "literature_dir",
+ "base_dir", "control_dir", "skill_dir", "command_dir",
+})
+
+SUPPORTED_MODULES = frozenset({"installation", "help"})
+
+
+# ---------------------------------------------------------------------------
+# Envelope builder
+# ---------------------------------------------------------------------------
+
+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,
+) -> 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,
+ }
+
+
+def build_envelope(
+ *,
+ 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,
+ 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,
+ }
+
+
+# ---------------------------------------------------------------------------
+# Config validation helper
+# ---------------------------------------------------------------------------
+
+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:
+ return True
+ return any(k in data for k in LEGACY_PATH_KEYS)
+
+
+# ---------------------------------------------------------------------------
+# 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",
+ ),
+ 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",
+ ),
+ 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",
+ ),
+ 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",
+ 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="",
+ ),
+ 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,
+ )
+
+
+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,
+ )
+
+ 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,
+ )
+
+
+# ---------------------------------------------------------------------------
+# CLI dispatch
+# ---------------------------------------------------------------------------
+
+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 == "help":
+ envelope = probe_help(vault)
+ else:
+ print(f"Error: unsupported probe module '{module}'", file=sys.stderr)
+ return 1
+
+ json_output: bool = getattr(args, "json", False)
+
+ if json_output:
+ print(json.dumps(envelope, indent=2, ensure_ascii=False))
+ else:
+ state = envelope["capability_state"]
+ reason = envelope["reason"]["text"]
+ print(f"[{module}] {state}: {reason}")
+
+ return 0
diff --git a/paperforge/plugin/main.js b/paperforge/plugin/main.js
index 7e5d253d..ec515fe0 100644
--- a/paperforge/plugin/main.js
+++ b/paperforge/plugin/main.js
@@ -1,12 +1,12 @@
-"use strict";var Lt=Object.create;var je=Object.defineProperty;var It=Object.getOwnPropertyDescriptor;var Mt=Object.getOwnPropertyNames;var Nt=Object.getPrototypeOf,jt=Object.prototype.hasOwnProperty;var zt=(d,u)=>()=>(u||d((u={exports:{}}).exports,u),u.exports),$t=(d,u)=>{for(var e in u)je(d,e,{get:u[e],enumerable:!0})},dt=(d,u,e,t)=>{if(u&&typeof u=="object"||typeof u=="function")for(let r of Mt(u))!jt.call(d,r)&&r!==e&&je(d,r,{get:()=>u[r],enumerable:!(t=It(u,r))||t.enumerable});return d};var K=(d,u,e)=>(e=d!=null?Lt(Nt(d)):{},dt(u||!d||!d.__esModule?je(e,"default",{value:d,enumerable:!0}):e,d)),Vt=d=>dt(je({},"__esModule",{value:!0}),d);var Xe=zt((rr,Kt)=>{Kt.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 Yt={};$t(Yt,{default:()=>Ue});module.exports=Vt(Yt);var X=require("obsidian"),Z=K(require("fs")),Fe=K(require("path")),fe=require("child_process");var ke="paperforge-status",De="paperforge",ut='',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"}],Be={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:"",last_seen_version:""};function ht(d,u){if(!u||!u.note_path)return u;let e=d.vault.getAbstractFileByPath(u.note_path);if(!e)return u;let t=d.metadataCache.getFileCache(e),r=t&&t.frontmatter;if(!r)return u;let s={...u};for(let a of["do_ocr","analyze","ocr_status","ocr_redo","deep_reading_status"])Object.prototype.hasOwnProperty.call(r,a)&&(s[a]=r[a]);return s}function qe(d,u){return d&&{...d,...u}}var Je={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_features:"Features",tab_setup:"Installation",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?"},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_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",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...",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"}},Ge=null;function Ht(d){try{let u=d.vault;if(typeof u.getConfig=="function"){let e=u.getConfig("language");if(e&&String(e).startsWith("zh"))return"zh"}}catch(u){}try{if(typeof localStorage!="undefined"){let u=localStorage.getItem("language");if(u&&String(u).startsWith("zh"))return"zh"}}catch(u){}return"en"}function _t(d){Ge=Ht(d)==="zh"?Je.zh:Je.en}function o(d){return Ge&&Ge[d]||Je.en[d]||d}var R=require("obsidian"),V=K(require("fs")),ee=K(require("path")),Ft=K(require("os")),G=require("child_process");var Tt=K(Xe());var he=K(require("fs")),ue=K(require("path")),gt=K(require("os")),ge=require("child_process"),Ye=null,ft=!1;function z(d,u,e,t){let r=e||he,s=t||ge.execFileSync;if(u&&u.python_path&&u.python_path.trim()){let i=u.python_path.trim();if(r.existsSync(i))return{path:i,source:"manual",extraArgs:[]}}let a=[ue.join(d,".paperforge-test-venv","Scripts","python.exe"),ue.join(d,".venv","Scripts","python.exe"),ue.join(d,"venv","Scripts","python.exe")];for(let i of a)try{if(r.existsSync(i))return{path:i,source:"auto-detected",extraArgs:[]}}catch(c){}let n=[{path:"py",extraArgs:["-3"]},{path:"python",extraArgs:[]},{path:"python3",extraArgs:[]}];for(let i of n)try{let c=s(i.path,[...i.extraArgs,"--version"],{encoding:"utf-8",timeout:5e3,windowsHide:!0});if(c&&c.toLowerCase().includes("python"))return{path:i.path,source:"auto-detected",extraArgs:i.extraArgs}}catch(c){}return{path:"python",source:"auto-detected",extraArgs:[]}}function mt(d,u,e,t,r){t===void 0&&(t=1e4);let s=r||ge.execFile;return new Promise(a=>{s(d,["-c","import paperforge; print(paperforge.__version__)"],{cwd:e,timeout:t},(n,i)=>{if(n){a({status:"not-installed",pyVersion:null,pluginVersion:u,error:n.message});return}let c=i&&i.trim()||null;a(c===u?{status:"match",pyVersion:c,pluginVersion:u,error:null}:{status:"mismatch",pyVersion:c,pluginVersion:u,error:null})})})}function Qe(d){let u=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}}[u];return t?{...t}:{type:"unknown",message:String(d),recoverable:!1}}function yt(d,u,e){e===void 0&&(e=[]);let t=`paperforge==${u}`,r=`git+https://github.com/LLLin000/PaperForge.git@${u}`,s=[...e,"-m","pip","install","--upgrade",t],a=[...e,"-m","pip","install","--upgrade",r];return{cmd:d,url:r,args:a,pypiArgs:s,gitArgs:a,timeout:12e4}}function vt(d,u,e,t,r,s){let a=r||ge.spawn;return new Promise(n=>{let i=Date.now(),c={cwd:e,timeout:t,windowsHide:!0};s&&(c.env=s);let l=a(d,u,c),h=[],_=[];l.stdout.on("data",p=>{h.push(p.toString("utf-8"))}),l.stderr.on("data",p=>{_.push(p.toString("utf-8"))}),l.on("close",p=>{n({stdout:h.join(""),stderr:_.join(""),exitCode:p,elapsed:Date.now()-i})}),l.on("error",p=>{n({stdout:h.join(""),stderr:_.join("")+`
-`+p.message,exitCode:-1,elapsed:Date.now()-i})})})}function et(){if(ft)return Ye;ft=!0;try{let d;if(process.platform==="win32"){let u=process.env.ComSpec||"C:\\Windows\\System32\\cmd.exe";d=(0,ge.execFileSync)(u,["/c","where","git"],{timeout:5e3,windowsHide:!0,encoding:"utf-8"})}else d=(0,ge.execFileSync)("which",["git"],{timeout:5e3,encoding:"utf-8"});if(d){let u=d.split(`
-`)[0].trim();u&&(Ye=ue.dirname(u))}}catch(d){}return Ye}function we(){let d={...process.env},u=process.platform,e=gt.homedir(),t=[],r=et();r&&t.push(r),u==="darwin"?t.push("/opt/homebrew/bin","/usr/local/bin","/usr/bin",`${e}/.local/bin`):u==="linux"&&t.push("/usr/local/bin","/usr/bin",`${e}/.local/bin`);let s=d.PATH||"";return d.PATH=[...t,s].filter(Boolean).join(ue.delimiter),d}function bt(d){return String(d).toLowerCase().replace(/[^a-z0-9]/g,"").includes("betterbibtex")}function tt(d){if(!d)return!1;try{if(!he.existsSync(d))return!1;for(let u of he.readdirSync(d))if(bt(u))return!0}catch(u){}return!1}function ze(d){if(!d)return!1;try{if(!he.existsSync(d))return!1;for(let u of he.readdirSync(d)){let e=ue.join(d,u,"extensions");try{if(!he.existsSync(e))continue;for(let t of he.readdirSync(e))if(bt(t))return!0}catch(t){}}}catch(u){}return!1}var Ee=K(require("fs")),W=K(require("path")),rt=require("child_process"),_e=null;function Wt(d,u){let e=u||Ee,t=W.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 s=e.readFileSync(t,"utf-8"),a=JSON.parse(s),n=a.vault_config||{};return{system_dir:n.system_dir||a.system_dir||r.system_dir,resources_dir:n.resources_dir||a.resources_dir||r.resources_dir,literature_dir:n.literature_dir||a.literature_dir||r.literature_dir,base_dir:n.base_dir||a.base_dir||r.base_dir,_warning:null}}catch(s){return console.warn("PaperForge: Failed to read paperforge.json, using defaults",s),{...r,_warning:"paperforge.json invalid; using defaults"}}}function le(d,u){let e=Wt(d,u),t=W.join(d,e.system_dir,"PaperForge");return{vault:d,systemDir:t,indexesDir:W.join(t,"indexes"),logsDir:W.join(t,"logs"),dbPath:W.join(t,"indexes","paperforge.db"),memoryStatePath:W.join(t,"indexes","memory-runtime-state.json"),vectorStatePath:W.join(t,"indexes","vector-runtime-state.json"),healthStatePath:W.join(t,"indexes","runtime-health.json"),buildStatePath:W.join(t,"indexes","vector-build-state.json"),orphanStatePath:W.join(t,"indexes","sync-orphan-state.json"),exportsDir:W.join(t,"exports"),ocrDir:W.join(t,"ocr"),pluginDataPath:W.join(d,".obsidian","plugins","paperforge","data.json"),pfJsonPath:W.join(d,"paperforge.json"),configWarning:e._warning}}function st(d){try{return Ee.existsSync(d)?JSON.parse(Ee.readFileSync(d,"utf-8")):null}catch(u){return null}}function Zt(d){let u=le(d);return st(u.memoryStatePath)}var Se=null;function $e(d){let u=le(d),e=Date.now();if(Se&&Se.vaultPath===d&&e-Se.ts<2e3)return Se.result;let t="",r=[W.join(d,".paperforge-test-venv","Scripts","python.exe"),W.join(d,".venv","Scripts","python.exe"),W.join(d,"venv","Scripts","python.exe")];for(let a=0;a({...a,_selected:!0,_idx:n})),this.vaultPath=r,this.py=s}_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 s of this.orphans){let a=t.createEl("div",{cls:"paperforge-orphan-row"+(s._selected?"":" paperforge-orphan-dimmed")});this._rowEls.push(a);let n=a.createEl("div",{cls:"paperforge-orphan-info"}),i=n.createEl("div",{cls:"paperforge-orphan-header"});i.createEl("span",{cls:"paperforge-orphan-key",text:s.citation_key||s.key});let c=i.createEl("span",{cls:"paperforge-orphan-tags"});c.createEl("span",{cls:"paperforge-tag "+(s.has_pdf?"tag-pdf":"tag-nopdf"),text:s.has_pdf?"PDF":"no PDF"}),s.collection_path&&c.createEl("span",{cls:"paperforge-tag tag-collection",text:s.collection_path}),s.title&&n.createEl("div",{cls:"paperforge-orphan-title",text:s.title});let l=[];s.authors&&l.push(s.authors),s.year&&l.push(s.year),l.length>0&&n.createEl("div",{cls:"paperforge-orphan-meta",text:l.join(" \xB7 ")}),n.createEl("div",{cls:"paperforge-orphan-explain",text:o("orphan_explain")}),a.addEventListener("click",()=>{s._selected=!s._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 s=this.orphans.every(a=>a._selected);for(let a of this.orphans)a._selected=!s;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 s=this.orphans.filter(n=>n._selected);if(s.length===0){new ae.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 ae.Notice("PaperForge: Python not found"),this.close();return}let a=s.map(n=>n.key);(0,Le.execFile)(this.py.path,[...this.py.extraArgs,"-m","paperforge","--vault",this.vaultPath,"prune","--force","--json",...a],{cwd:this.vaultPath,timeout:6e4},(n,i)=>{if(n){new ae.Notice("PaperForge: prune failed"),this.close();return}try{let c=JSON.parse(i),l=c.data&&c.data.deleted||[];new ae.Notice("Deleted "+l.length+" orphan workspace(s)")}catch(c){new ae.Notice("PaperForge: prune done")}this.close()})})}onClose(){this.contentEl.empty()}};function He(d,u,e){console.log("[PF] checkOrphanState called");try{let r=le(e).orphanStatePath;if(!pe.existsSync(r)){console.log("[PF] orphan file NOT FOUND");return}console.log("[PF] orphan file FOUND");let s=pe.readFileSync(r,"utf-8"),n=JSON.parse(s).orphans||[];if(console.log("[PF] orphans count:",n.length),n.length===0)return;let i=me(e,u.settings);console.log("[PF] py.path:",i?i.path:"null"),new at(d,n,e,i).open(),pe.unlinkSync(r),console.log("[PF] orphan file cleaned")}catch(t){console.log("[PF] checkOrphanState exception:",t.message||t)}}var Oe=class extends ae.Modal{constructor(e,t){super(e);this._pendingSave=null;this._showSkipConfirm=!1;this.plugin=t,this._step=1}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,s)=>{let a=s+1,n=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=kt(this._apiKeyValidated,e.zotero_data_dir);if(t.reason==="ocr")return t;let r=(e.zotero_data_dir||"").trim();if(!r)return new ae.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 ae.Notice("Zotero \u6570\u636E\u76EE\u5F55\u8DEF\u5F84\u4E0D\u5B58\u5728"),{blocked:!0,reason:"zotero"};if(!pe.statSync(r).isDirectory())return new ae.Notice("Zotero \u6570\u636E\u76EE\u5F55\u8DEF\u5F84\u4E0D\u662F\u4E00\u4E2A\u76EE\u5F55"),{blocked:!0,reason:"zotero"};let s=wt.join(r,"storage");return!pe.existsSync(s)||!pe.statSync(s).isDirectory()?(new ae.Notice("Zotero \u6570\u636E\u76EE\u5F55\u4E2D\u672A\u627E\u5230 storage/ \u5B50\u76EE\u5F55"),{blocked:!0,reason:"zotero"}):{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,s=e.createEl("div",{cls:"paperforge-dir-tree"}),a=s.createEl("div",{cls:"paperforge-dir-node root"});a.textContent=`\u{1F4C1} Vault (${r})`;let n=s.createEl("div",{cls:"paperforge-dir-children"}),i=n.createEl("div",{cls:"paperforge-dir-node folder"});i.textContent=`\u{1F4C1} ${t.resources_dir||"Resources"}/ \u2014 \u6587\u732E\u5361\u7247\u76EE\u5F55\uFF08Base \u6570\u636E\u6765\u6E90\uFF09`,i.createEl("div",{cls:"paperforge-dir-children"}).createEl("div",{cls:"paperforge-dir-node file",text:`\u{1F4C1} ${t.literature_dir||"Literature"}/ \u2014 \u6587\u732E\u5361\u7247`}),n.createEl("div",{cls:"paperforge-dir-node folder",text:`\u{1F4C1} ${t.base_dir||"Bases"}/ \u2014 \u6570\u636E\u7BA1\u7406\u9762\u677F`}),n.createEl("div",{cls:"paperforge-dir-node folder",text:`\u{1F4C1} ${t.system_dir||"System"}/ \u2014 Zotero \u8F6F\u94FE\u63A5 + PaperForge \u7CFB\u7EDF\u6587\u4EF6\u5939`}),e.createEl("p",{text:o("wizard_preview"),cls:"paperforge-modal-hint"}),e.createEl("p",{text:o("wizard_safety"),cls:"paperforge-modal-hint"});let l=e.createEl("div",{cls:"paperforge-summary"}),h=[{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 _ of h){let p=l.createEl("div",{cls:"paperforge-summary-row"});p.createEl("span",{cls:"paperforge-summary-label",text:_.label}),p.createEl("span",{cls:"paperforge-summary-value",text:_.val})}}_stepDirectories(e){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 s=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 n of a){let i=s.createEl("div",{cls:"paperforge-summary-row"});i.createEl("span",{cls:"paperforge-summary-label",text:n.label}),i.createEl("span",{cls:"paperforge-summary-value",text:n.val})}}_stepKeys(e){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"}],s=e.createEl("div",{cls:"paperforge-modal-field"});s.createEl("label",{cls:"paperforge-modal-label",text:o("label_agent")});let a=s.createEl("select",{cls:"paperforge-modal-select"});for(let _ of r){let p=a.createEl("option",{text:_.name,attr:{value:_.key}});_.key===(t.agent_platform||"opencode")&&(p.selected=!0)}a.addEventListener("change",()=>{t.agent_platform=a.value,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 n=e.createEl("div",{cls:"paperforge-modal-field"});n.createEl("label",{cls:"paperforge-modal-label",text:o("field_paddleocr")});let i=n.createEl("input",{cls:"paperforge-modal-input",attr:{type:"password",placeholder:"API Key"}});i.value=t.paddleocr_api_key||"",this._apiKeyValidated=!1,this._apiKeyStatus=n.createEl("span",{cls:"paperforge-apikey-status",text:""});let c=n.createEl("button",{cls:"paperforge-step-btn",text:"\u9A8C\u8BC1"});c.addEventListener("click",()=>this._validateApiKey(i.value,c)),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 l=e.createEl("div",{cls:"paperforge-modal-field"});l.createEl("label",{cls:"paperforge-modal-label",text:o("field_zotero_data")});let h=l.createEl("input",{cls:"paperforge-modal-input",attr:{type:"text",placeholder:o("field_zotero_placeholder")}});h.value=t.zotero_data_dir||"",h.addEventListener("input",()=>{t.zotero_data_dir=h.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"}),s={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=St.request(s,n=>{t.disabled=!1,t.textContent="\u9A8C\u8BC1";let i="";n.on("data",c=>i+=c),n.on("end",()=>{try{let c=JSON.parse(i);n.statusCode===400&&c.code===10001?(this._apiKeyStatus.textContent="\u2713 \u5BC6\u94A5\u6709\u6548",this._apiKeyStatus.className="paperforge-apikey-status ok",this._apiKeyValidated=!0):n.statusCode===401?(this._apiKeyStatus.textContent="\u9A8C\u8BC1\u5931\u8D25\uFF1A\u5BC6\u94A5\u65E0\u6548\u3002\u53EF\u70B9\u4E0B\u4E00\u6B65\u8DF3\u8FC7\uFF0C\u7B49\u4F1A\u513F\u518D\u914D\u7F6E\u3002",this._apiKeyStatus.className="paperforge-apikey-status error",this._apiKeyValidated=!1):(this._apiKeyStatus.textContent="\u9A8C\u8BC1\u5931\u8D25\uFF1AAPI \u8FD4\u56DE "+n.statusCode+"\u3002\u53EF\u70B9\u4E0B\u4E00\u6B65\u8DF3\u8FC7\uFF0C\u7B49\u4F1A\u513F\u518D\u914D\u7F6E\u3002",this._apiKeyStatus.className="paperforge-apikey-status error",this._apiKeyValidated=!1)}catch(c){this._apiKeyStatus.textContent="\u9A8C\u8BC1\u5931\u8D25\uFF1A\u65E0\u6CD5\u89E3\u6790\u54CD\u5E94\u3002\u53EF\u70B9\u4E0B\u4E00\u6B65\u8DF3\u8FC7\uFF0C\u7B49\u4F1A\u513F\u518D\u914D\u7F6E\u3002",this._apiKeyStatus.className="paperforge-apikey-status error",this._apiKeyValidated=!1}})});a.on("error",n=>{t.disabled=!1,t.textContent="\u9A8C\u8BC1",this._apiKeyStatus.textContent="\u9A8C\u8BC1\u5931\u8D25\uFF1A\u65E0\u6CD5\u8FDE\u63A5 ("+n.message+")\u3002\u53EF\u70B9\u4E0B\u4E00\u6B65\u8DF3\u8FC7\uFF0C\u7B49\u4F1A\u513F\u518D\u914D\u7F6E\u3002",this._apiKeyStatus.className="paperforge-apikey-status error",this._apiKeyValidated=!1}),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,s){let a=e.createEl("div",{cls:"paperforge-modal-field"});a.createEl("label",{cls:"paperforge-modal-label",text:t});let n=a.createEl("input",{cls:"paperforge-modal-input",attr:{type:"text"}});n.value=r,n.disabled=!!s}_modalInput(e,t,r,s,a){let n=e.createEl("div",{cls:"paperforge-modal-field"});n.createEl("label",{cls:"paperforge-modal-label",text:t});let i=n.createEl("input",{cls:"paperforge-modal-input",attr:{type:"text",placeholder:a||""}});i.value=s;let c=this.plugin.settings;i.addEventListener("input",()=>{c[r]=i.value,this._pendingSave&&clearTimeout(this._pendingSave),this._pendingSave=setTimeout(()=>{this.plugin.saveSettings(),this._pendingSave=null},500)})}_modalSecret(e,t,r,s,a){let n=e.createEl("div",{cls:"paperforge-modal-field"});n.createEl("label",{cls:"paperforge-modal-label",text:t});let i=n.createEl("input",{cls:"paperforge-modal-input",attr:{type:"password",placeholder:a||""}});i.value=s;let c=this.plugin.settings;i.addEventListener("input",()=>{c[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 n,i,c,l,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(p=>this._log(" \u2717 "+p)),e.disabled=!1,e.textContent=o("install_btn_retry");return}let s=(p,g={})=>new Promise((y,E)=>{let{path:f,extraArgs:b=[]}=z(t.vault_path.trim(),this.plugin.settings,void 0,void 0),w=(0,Le.spawn)(f,[...b,...p],{cwd:t.vault_path.trim(),env:we(),timeout:12e4,...g}),x="",m="";w.stdout.on("data",v=>{let k=v.toString("utf-8");x+=k,g.logStdout&&this._processSetupOutput(k)}),w.stderr.on("data",v=>{let k=v.toString("utf-8");m+=k,this._log("[stderr] "+k.trim())}),w.on("close",v=>{v===0?y({stdout:x,stderr:m}):E(new Error(m.trim()||x.trim()||`exit code ${v}`))}),w.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 p=!0;try{await s(["-c","import paperforge"])}catch(g){p=!1}if(!p){this._log(o("install_bootstrapping"));let g=this.plugin.manifest.version;this._log(`[install] Trying PyPI: pip install paperforge==${g}`);let y=["-m","pip","install","--upgrade"];process.platform!=="win32"&&y.push("--user"),y.push(`paperforge==${g}`);try{await s(y,{logStdout:!0})}catch(E){this._log(`[install] PyPI failed, falling back to git: git+https://...@v${g}`),console.warn("[PaperForge] PyPI install failed, falling back to git:",(n=E.message)==null?void 0:n.slice(0,200));let f=["-m","pip","install","--upgrade"];process.platform!=="win32"&&f.push("--user"),f.push(`git+https://github.com/LLLin000/PaperForge.git@v${g}`),await s(f,{logStdout:!0})}}await s(a,{logStdout:!0,env:we()}),this._log(o("install_complete")),t.setup_complete=!0,await this.plugin.saveSettings(),setTimeout(()=>{this._step=5,this._render()},800)}catch(p){console.error("PaperForge setup failed:",p.message);let g=this._formatSetupError(p.message);this._log(o("install_failed")+g);let y=(i=this._installLog.parentElement)==null?void 0:i.createEl("button",{cls:"paperforge-copy-diag-btn",text:o("error_copy_diagnostic")||"Copy diagnostic"});if(y){let E=p.message,f=((l=(c=this.plugin)==null?void 0:c.settings)==null?void 0:l.python_path)||"auto",b=((_=(h=this.plugin)==null?void 0:h.manifest)==null?void 0:_.version)||"?",w=process.platform+" "+process.arch,x,m;try{x=et()||"(not found)"}catch(P){x="(error)"}try{m=z(t.vault_path.trim(),this.plugin.settings,void 0,void 0)}catch(P){m=null}let v=(process.env.PATH||"").length,k=(process.env.PATH||"").toLowerCase().includes("git"),F=["[PaperForge Diagnostic]","Category: "+g,"Plugin version: "+b,"Python: "+f,"Resolved Python: "+((m==null?void 0:m.path)||"?"),"OS: "+w,"Vault path: "+(t.vault_path||"?"),"--- Git ---","Git dir (resolved): "+x,"PATH length: "+v+" chars","PATH contains git: "+k,"--- Raw error ---",E.slice(0,2e3)].join(`
-`);y.addEventListener("click",()=>{navigator.clipboard.writeText(F).then(()=>{y.setText(o("error_copied")||"Copied!"),setTimeout(()=>{y.setText(o("error_copy_diagnostic")||"Copy diagnostic")},3e3)}).catch(()=>{new ae.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+`
+"use strict";var qt=Object.create;var Ve=Object.defineProperty;var Zt=Object.getOwnPropertyDescriptor;var Jt=Object.getOwnPropertyNames;var Gt=Object.getPrototypeOf,Yt=Object.prototype.hasOwnProperty;var Xt=(p,d)=>()=>(d||p((d={exports:{}}).exports,d),d.exports),Qt=(p,d)=>{for(var e in d)Ve(p,e,{get:d[e],enumerable:!0})},bt=(p,d,e,t)=>{if(d&&typeof d=="object"||typeof d=="function")for(let r of Jt(d))!Yt.call(p,r)&&r!==e&&Ve(p,r,{get:()=>d[r],enumerable:!(t=Zt(d,r))||t.enumerable});return p};var H=(p,d,e)=>(e=p!=null?qt(Gt(p)):{},bt(d||!p||!p.__esModule?Ve(e,"default",{value:p,enumerable:!0}):e,p)),er=p=>bt(Ve({},"__esModule",{value:!0}),p);var it=Xt((yr,ir)=>{ir.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 fr={};Qt(fr,{default:()=>Xe});module.exports=er(fr);var Y=require("obsidian"),U=H(require("fs")),Ae=H(require("path")),ge=require("child_process");var ke="paperforge-status",Be="paperforge",Et='',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"}],Oe={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 xt(p,d){if(!d||!d.note_path)return d;let e=p.vault.getAbstractFileByPath(d.note_path);if(!e)return d;let t=p.metadataCache.getFileCache(e),r=t&&t.frontmatter;if(!r)return d;let n={...d};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 Qe(p,d){return p&&{...p,...d}}var et=1,Se=["installation","library","ocr","memory","maintenance","help"],tr=["unknown","unavailable","missing_input","needs_action","limited","ready"],rr=["unknown","ok","warning","error"],nr=["idle","running"];function sr(p){if(!p||typeof p!="object"||Array.isArray(p))return!1;let d=p;return!(typeof d.verb!="string"||typeof d.label!="string"||typeof d.destructive!="boolean"||d.destructive_scope!==null&&typeof d.destructive_scope!="string"||d.destructive_effect!==null&&typeof d.destructive_effect!="string"||typeof d.confirmation_required!="boolean"||d.confirmation_prompt!==null&&typeof d.confirmation_prompt!="string"||typeof d.command!="string"||typeof d.scope!="string"||typeof d.scope_count!="number")}function Le(p){return{verb:"probe",label:"Check",destructive:!1,destructive_scope:null,destructive_effect:null,confirmation_required:!1,confirmation_prompt:null,command:`probe ${p}`,scope:p,scope_count:1}}function tt(p,d){if(!p||typeof p!="object")return!1;let e=p;if(e.schema_version!==1||typeof e.module!="string"||!e.module||!Se.includes(e.module)||d!==void 0&&e.module!==d||typeof e.capability_state!="string"||!tr.includes(e.capability_state)||typeof e.severity!="string"||!rr.includes(e.severity)||typeof e.activity_state!="string"||!nr.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&&!sr(r.primary)||typeof e.updated_at!="string"||!e.updated_at||typeof e.ttl_seconds!="number")}function He(p){return{schema_version:et,module:p,capability_state:"unknown",activity_state:"idle",activity_label:null,activity_progress:null,severity:"unknown",reason:{code:`${p}.no_probe`,text:`${p} has not been probed yet.`},action:{primary:Le(p)},notices:[],updated_at:new Date(0).toISOString(),ttl_seconds:0}}function rt(p){return{schema_version:et,module:p,capability_state:"unknown",activity_state:"idle",activity_label:null,activity_progress:null,severity:"unknown",reason:{code:`${p}.stale`,text:`Cached probe data for ${p} is stale.`},action:{primary:Le(p)},notices:[],updated_at:new Date(0).toISOString(),ttl_seconds:0}}function Pe(p){return{schema_version:et,module:p,capability_state:"unknown",activity_state:"idle",activity_label:null,activity_progress:null,severity:"unknown",reason:{code:`${p}.invalid_response`,text:`Probe response for ${p} was invalid.`},action:{primary:Le(p)},notices:[],updated_at:new Date(0).toISOString(),ttl_seconds:0}}function nt(p){if(p.activity_state==="running")return!1;if(p.ttl_seconds<=0)return!0;let d=new Date(p.updated_at).getTime();return isNaN(d)?!0:Date.now()-d>p.ttl_seconds*1e3}function kt(p){return p.capability_state==="ready"&&p.action.primary===null}function wt(p){var r,n,a;let d=(r=p.action)==null?void 0:r.primary,e=(n=d==null?void 0:d.verb)!=null?n:"probe",t=(a=d==null?void 0:d.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 St(p,d){let e={};for(let t of d){let r=p[t];if(!r||typeof r!="object"){e[t]=He(t);continue}if(!tt(r,t)){e[t]=Pe(t);continue}if(nt(r)){e[t]=rt(t);continue}e[t]=r}return e}var st={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_features:"Features",tab_setup:"Installation",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?",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_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",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"}},at=null;function ar(p){try{let d=p.vault;if(typeof d.getConfig=="function"){let e=d.getConfig("language");if(e&&String(e).startsWith("zh"))return"zh"}}catch(d){}try{if(typeof localStorage!="undefined"){let d=localStorage.getItem("language");if(d&&String(d).startsWith("zh"))return"zh"}}catch(d){}return"en"}function Pt(p){at=ar(p)==="zh"?st.zh:st.en}function o(p){return at&&at[p]||st.en[p]||p}var F=require("obsidian"),W=H(require("fs")),te=H(require("path")),zt=H(require("os")),G=require("child_process");var Vt=H(it());var _e=H(require("fs")),ue=H(require("path")),Rt=H(require("os")),me=require("child_process"),ot=null,Ct=!1;function z(p,d,e,t){let r=e||_e,n=t||me.execFileSync;if(d&&d.python_path&&d.python_path.trim()){let i=d.python_path.trim();if(r.existsSync(i))return{path:i,source:"manual",extraArgs:[]}}let a=[ue.join(p,".paperforge-test-venv","Scripts","python.exe"),ue.join(p,".venv","Scripts","python.exe"),ue.join(p,"venv","Scripts","python.exe")];for(let i of a)try{if(r.existsSync(i))return{path:i,source:"auto-detected",extraArgs:[]}}catch(c){}let s=[{path:"py",extraArgs:["-3"]},{path:"python",extraArgs:[]},{path:"python3",extraArgs:[]}];for(let i of s)try{let c=n(i.path,[...i.extraArgs,"--version"],{encoding:"utf-8",timeout:5e3,windowsHide:!0});if(c&&c.toLowerCase().includes("python"))return{path:i.path,source:"auto-detected",extraArgs:i.extraArgs}}catch(c){}return{path:"python",source:"auto-detected",extraArgs:[]}}function Ft(p,d,e,t,r){t===void 0&&(t=1e4);let n=r||me.execFile;return new Promise(a=>{n(p,["-c","import paperforge; print(paperforge.__version__)"],{cwd:e,timeout:t},(s,i)=>{if(s){a({status:"not-installed",pyVersion:null,pluginVersion:d,error:s.message});return}let c=i&&i.trim()||null;a(c===d?{status:"match",pyVersion:c,pluginVersion:d,error:null}:{status:"mismatch",pyVersion:c,pluginVersion:d,error:null})})})}function lt(p){let d=String(p),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}}[d];return t?{...t}:{type:"unknown",message:String(p),recoverable:!1}}function Tt(p,d,e){e===void 0&&(e=[]);let t=`paperforge==${d}`,r=`git+https://github.com/LLLin000/PaperForge.git@${d}`,n=[...e,"-m","pip","install","--upgrade",t],a=[...e,"-m","pip","install","--upgrade",r];return{cmd:p,url:r,args:a,pypiArgs:n,gitArgs:a,timeout:12e4}}function At(p,d,e,t,r,n){let a=r||me.spawn;return new Promise(s=>{let i=Date.now(),c={cwd:e,timeout:t,windowsHide:!0};n&&(c.env=n);let l=a(p,d,c),_=[],f=[];l.stdout.on("data",u=>{_.push(u.toString("utf-8"))}),l.stderr.on("data",u=>{f.push(u.toString("utf-8"))}),l.on("close",u=>{s({stdout:_.join(""),stderr:f.join(""),exitCode:u,elapsed:Date.now()-i})}),l.on("error",u=>{s({stdout:_.join(""),stderr:f.join("")+`
+`+u.message,exitCode:-1,elapsed:Date.now()-i})})})}function ct(){if(Ct)return ot;Ct=!0;try{let p;if(process.platform==="win32"){let d=process.env.ComSpec||"C:\\Windows\\System32\\cmd.exe";p=(0,me.execFileSync)(d,["/c","where","git"],{timeout:5e3,windowsHide:!0,encoding:"utf-8"})}else p=(0,me.execFileSync)("which",["git"],{timeout:5e3,encoding:"utf-8"});if(p){let d=p.split(`
+`)[0].trim();d&&(ot=ue.dirname(d))}}catch(p){}return ot}function we(){let p={...process.env},d=process.platform,e=Rt.homedir(),t=[],r=ct();r&&t.push(r),d==="darwin"?t.push("/opt/homebrew/bin","/usr/local/bin","/usr/bin",`${e}/.local/bin`):d==="linux"&&t.push("/usr/local/bin","/usr/bin",`${e}/.local/bin`);let n=p.PATH||"";return p.PATH=[...t,n].filter(Boolean).join(ue.delimiter),p}function Dt(p){return String(p).toLowerCase().replace(/[^a-z0-9]/g,"").includes("betterbibtex")}function pt(p){if(!p)return!1;try{if(!_e.existsSync(p))return!1;for(let d of _e.readdirSync(p))if(Dt(d))return!0}catch(d){}return!1}function Ke(p){if(!p)return!1;try{if(!_e.existsSync(p))return!1;for(let d of _e.readdirSync(p)){let e=ue.join(p,d,"extensions");try{if(!_e.existsSync(e))continue;for(let t of _e.readdirSync(e))if(Dt(t))return!0}catch(t){}}}catch(d){}return!1}var Ee=H(require("fs")),K=H(require("path")),dt=require("child_process"),fe=null;function or(p,d){let e=d||Ee,t=K.join(p,"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 le(p,d){let e=or(p,d),t=K.join(p,e.system_dir,"PaperForge");return{vault:p,systemDir:t,indexesDir:K.join(t,"indexes"),logsDir:K.join(t,"logs"),dbPath:K.join(t,"indexes","paperforge.db"),memoryStatePath:K.join(t,"indexes","memory-runtime-state.json"),vectorStatePath:K.join(t,"indexes","vector-runtime-state.json"),healthStatePath:K.join(t,"indexes","runtime-health.json"),buildStatePath:K.join(t,"indexes","vector-build-state.json"),orphanStatePath:K.join(t,"indexes","sync-orphan-state.json"),exportsDir:K.join(t,"exports"),ocrDir:K.join(t,"ocr"),pluginDataPath:K.join(p,".obsidian","plugins","paperforge","data.json"),pfJsonPath:K.join(p,"paperforge.json"),configWarning:e._warning}}function ut(p){try{return Ee.existsSync(p)?JSON.parse(Ee.readFileSync(p,"utf-8")):null}catch(d){return null}}function lr(p){let d=le(p);return ut(d.memoryStatePath)}var Ce=null;function We(p){let d=le(p),e=Date.now();if(Ce&&Ce.vaultPath===p&&e-Ce.ts<2e3)return Ce.result;let t="",r=[K.join(p,".paperforge-test-venv","Scripts","python.exe"),K.join(p,".venv","Scripts","python.exe"),K.join(p,"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 c=i.createEl("span",{cls:"paperforge-orphan-tags"});c.createEl("span",{cls:"paperforge-tag "+(n.has_pdf?"tag-pdf":"tag-nopdf"),text:n.has_pdf?"PDF":"no PDF"}),n.collection_path&&c.createEl("span",{cls:"paperforge-tag tag-collection",text:n.collection_path}),n.title&&s.createEl("div",{cls:"paperforge-orphan-title",text:n.title});let l=[];n.authors&&l.push(n.authors),n.year&&l.push(n.year),l.length>0&&s.createEl("div",{cls:"paperforge-orphan-meta",text:l.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 ae.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 ae.Notice("PaperForge: Python not found"),this.close();return}let a=n.map(s=>s.key);(0,Ie.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 ae.Notice("PaperForge: prune failed"),this.close();return}try{let c=JSON.parse(i),l=c.data&&c.data.deleted||[];new ae.Notice("Deleted "+l.length+" orphan workspace(s)")}catch(c){new ae.Notice("PaperForge: prune done")}this.close()})})}onClose(){this.contentEl.empty()}};function Ze(p,d,e){console.log("[PF] checkOrphanState called");try{let r=le(e).orphanStatePath;if(!pe.existsSync(r)){console.log("[PF] orphan file NOT FOUND");return}console.log("[PF] orphan file FOUND");let n=pe.readFileSync(r,"utf-8"),s=JSON.parse(n).orphans||[];if(console.log("[PF] orphans count:",s.length),s.length===0)return;let i=he(e,d.settings);console.log("[PF] py.path:",i?i.path:"null"),new ft(p,s,e,i).open(),pe.unlinkSync(r),console.log("[PF] orphan file cleaned")}catch(t){console.log("[PF] checkOrphanState exception:",t.message||t)}}var qe=class extends ae.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=Lt(this._apiKeyValidated,e.zotero_data_dir);if(t.reason==="ocr")return t;let r=(e.zotero_data_dir||"").trim();if(!r)return new ae.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 ae.Notice("Zotero \u6570\u636E\u76EE\u5F55\u8DEF\u5F84\u4E0D\u5B58\u5728"),{blocked:!0,reason:"zotero"};if(!pe.statSync(r).isDirectory())return new ae.Notice("Zotero \u6570\u636E\u76EE\u5F55\u8DEF\u5F84\u4E0D\u662F\u4E00\u4E2A\u76EE\u5F55"),{blocked:!0,reason:"zotero"};let n=Mt.join(r,"storage");return!pe.existsSync(n)||!pe.statSync(n).isDirectory()?(new ae.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 l=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 f of _){let u=l.createEl("div",{cls:"paperforge-summary-row"});u.createEl("span",{cls:"paperforge-summary-label",text:f.label}),u.createEl("span",{cls:"paperforge-summary-value",text:f.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 f of r){let u=a.createEl("option",{text:f.name,attr:{value:f.key}});f.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 c=s.createEl("button",{cls:"paperforge-step-btn",text:"\u9A8C\u8BC1"});c.addEventListener("click",()=>this._validateApiKey(i.value,c)),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 l=e.createEl("div",{cls:"paperforge-modal-field"});l.createEl("label",{cls:"paperforge-modal-label",text:o("field_zotero_data")});let _=l.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=It.request(n,s=>{t.disabled=!1,t.textContent="\u9A8C\u8BC1";let i="";s.on("data",c=>i+=c),s.on("end",()=>{try{let c=JSON.parse(i);s.statusCode===400&&c.code===10001?(this._apiKeyStatus.textContent="\u2713 \u5BC6\u94A5\u6709\u6548",this._apiKeyStatus.className="paperforge-apikey-status ok",this._apiKeyValidated=!0):s.statusCode===401?(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(c){this._apiKeyStatus.textContent="\u9A8C\u8BC1\u5931\u8D25\uFF1A\u65E0\u6CD5\u89E3\u6790\u54CD\u5E94\u3002\u53EF\u70B9\u4E0B\u4E00\u6B65\u8DF3\u8FC7\uFF0C\u7B49\u4F1A\u513F\u518D\u914D\u7F6E\u3002",this._apiKeyStatus.className="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 c=this.plugin.settings;i.addEventListener("input",()=>{c[r]=i.value,this._pendingSave&&clearTimeout(this._pendingSave),this._pendingSave=setTimeout(()=>{this.plugin.saveSettings(),this._pendingSave=null},500)})}_modalSecret(e,t,r,n,a){let s=e.createEl("div",{cls:"paperforge-modal-field"});s.createEl("label",{cls:"paperforge-modal-label",text:t});let i=s.createEl("input",{cls:"paperforge-modal-input",attr:{type:"password",placeholder:a||""}});i.value=n;let c=this.plugin.settings;i.addEventListener("input",()=>{c[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,c,l,_,f;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,g={})=>new Promise((m,b)=>{let{path:h,extraArgs:v=[]}=z(t.vault_path.trim(),this.plugin.settings,void 0,void 0),x=(0,Ie.spawn)(h,[...v,...u],{cwd:t.vault_path.trim(),env:we(),timeout:12e4,...g}),k="",y="";x.stdout.on("data",E=>{let w=E.toString("utf-8");k+=w,g.logStdout&&this._processSetupOutput(w)}),x.stderr.on("data",E=>{let w=E.toString("utf-8");y+=w,this._log("[stderr] "+w.trim())}),x.on("close",E=>{E===0?m({stdout:k,stderr:y}):b(new Error(y.trim()||k.trim()||`exit code ${E}`))}),x.on("error",E=>b(E))}),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(g){u=!1}if(!u){this._log(o("install_bootstrapping"));let g=this.plugin.manifest.version;this._log(`[install] Trying PyPI: pip install paperforge==${g}`);let m=["-m","pip","install","--upgrade"];process.platform!=="win32"&&m.push("--user"),m.push(`paperforge==${g}`);try{await n(m,{logStdout:!0})}catch(b){this._log(`[install] PyPI failed, falling back to git: git+https://...@v${g}`),console.warn("[PaperForge] PyPI install failed, falling back to git:",(s=b.message)==null?void 0:s.slice(0,200));let h=["-m","pip","install","--upgrade"];process.platform!=="win32"&&h.push("--user"),h.push(`git+https://github.com/LLLin000/PaperForge.git@v${g}`),await n(h,{logStdout:!0})}}await n(a,{logStdout:!0,env:we()}),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 g=this._formatSetupError(u.message);this._log(o("install_failed")+g);let m=(i=this._installLog.parentElement)==null?void 0:i.createEl("button",{cls:"paperforge-copy-diag-btn",text:o("error_copy_diagnostic")||"Copy diagnostic"});if(m){let b=u.message,h=((l=(c=this.plugin)==null?void 0:c.settings)==null?void 0:l.python_path)||"auto",v=((f=(_=this.plugin)==null?void 0:_.manifest)==null?void 0:f.version)||"?",x=process.platform+" "+process.arch,k,y;try{k=ct()||"(not found)"}catch(T){k="(error)"}try{y=z(t.vault_path.trim(),this.plugin.settings,void 0,void 0)}catch(T){y=null}let E=(process.env.PATH||"").length,w=(process.env.PATH||"").toLowerCase().includes("git"),D=["[PaperForge Diagnostic]","Category: "+g,"Plugin version: "+v,"Python: "+h,"Resolved Python: "+((y==null?void 0:y.path)||"?"),"OS: "+x,"Vault path: "+(t.vault_path||"?"),"--- Git ---","Git dir (resolved): "+k,"PATH length: "+E+" chars","PATH contains git: "+w,"--- Raw error ---",b.slice(0,2e3)].join(`
+`);m.addEventListener("click",()=>{navigator.clipboard.writeText(D).then(()=>{m.setText(o("error_copied")||"Copied!"),setTimeout(()=>{m.setText(o("error_copy_diagnostic")||"Copy diagnostic")},3e3)}).catch(()=>{new ae.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(`
-`).filter(Boolean);for(let r of t)if(r.includes("[*]")||r.includes("[OK]")||r.includes("[FAIL]")){let s=r.replace(/^\[\*\].*\d+:?\s*/,"").replace(/^\[OK\]\s*/,"").replace(/^\[FAIL\]\s*/,"");this._log(" "+s)}}_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 s of t)if(s.match.test(e))return s.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,s=this.app.vault.adapter.basePath,a=[{label:o("dir_vault"),val:s},{label:o("dir_resources"),val:`${s}/${r.resources_dir}`},{label:o("dir_notes"),val:`${s}/${r.resources_dir}/${r.literature_dir}`},{label:o("dir_base"),val:`${s}/${r.base_dir}`},{label:o("dir_system"),val:`${s}/${r.system_dir}`},{label:"API Key",val:r.paddleocr_api_key?o("api_key_set"):o("api_key_missing")},{label:o("field_zotero_data"),val:r.zotero_data_dir||o("not_set")}];for(let h of a){let _=t.createEl("div",{cls:"paperforge-summary-row"});_.createEl("span",{cls:"paperforge-summary-label",text:h.label}),_.createEl("span",{cls:"paperforge-summary-value",text:h.val})}let n=t.createEl("div",{cls:"paperforge-summary-row"});n.createEl("span",{cls:"paperforge-summary-label",text:"PaperForge"});let i=n.createEl("span",{cls:"paperforge-summary-value",text:"\u2014"});{let h=s,{path:_,extraArgs:p=[]}=z(h,this.plugin.settings,void 0,void 0);(0,Le.execFile)(_,[...p,"-c","import paperforge; print(paperforge.__version__)"],{cwd:h,timeout:1e4},(g,y)=>{!g&&y&&(i.textContent="v"+y.trim())})}for(let h of a){let _=t.createEl("div",{cls:"paperforge-summary-row"});_.createEl("span",{cls:"paperforge-summary-label",text:h.label}),_.createEl("span",{cls:"paperforge-summary-value",text:h.val})}e.createEl("h3",{text:o("complete_next")});let c=e.createEl("div",{cls:"paperforge-nextsteps"}),l=[[o("complete_step4"),o("complete_step4_desc")],["",`${o("complete_export_path")} ${s}/${r.system_dir}/PaperForge/exports/`],[o("complete_step1"),o("complete_step1_desc")],[o("complete_step2"),o("complete_step2_desc")],[o("complete_step3"),o("complete_step3_desc")]];for(let[h,_]of l){let p=c.createEl("div",{cls:"paperforge-nextstep-item"});h&&p.createEl("strong",{text:h}),p.createEl("span",{text:_})}}};var Ce=K(require("fs")),Ke=K(require("path")),Ct=require("child_process");function it(d){return d.display_action==="rebuild_result"?"rebuild":d.display_action==="retry_ocr"||d.display_action==="upgrade_legacy"?"redo":null}function ot(d){return d==="redo"}function Ut(d,u,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 u)t.papers[r.key]=r;return t}function Rt(d){return Ke.join(d,"System","PaperForge","cache","ocr_maintenance.json")}function We(d){try{let u=Rt(d),e=Ce.readFileSync(u,"utf-8");return JSON.parse(e)}catch(u){return null}}function qt(d,u){let e=Rt(d),t=Ke.dirname(e);Ce.mkdirSync(t,{recursive:!0}),Ce.writeFileSync(e,JSON.stringify(u,null,2),"utf-8")}function Pt(d,u,e){return new Promise((t,r)=>{(0,Ct.execFile)(d,u,e,(s,a)=>{s?r(s):t(a)})})}async function lt(d,u,e,t){let r=await Pt(u,[...e,"-m","paperforge","ocr","list","--manifest"],{cwd:d,timeout:3e4}),s=JSON.parse(r);if(t){let h=Object.keys(t.manifest),_=Object.keys(s);if(h.length===_.length&&h.every(g=>t.manifest[g]===s[g])&&Object.values(t.papers).every(y=>typeof y.needs_derived_rebuild=="boolean"))return{data:Object.values(t.papers),changed:!1}}let a=Object.keys(s),n=await Pt(u,[...e,"-m","paperforge","ocr","list","--json","--keys",...a],{cwd:d,timeout:3e4}),i=JSON.parse(n),c=Ut(s,i,t);return qt(d,c),{data:Object.values(c.papers),changed:!0}}var Jt=["EMBED","OCR_REBUILD","OCR_REDO"];function ct(d,u){var a,n;let t=(u+d).split(`
-`),r=(a=t.pop())!=null?a:"",s=[];for(let i of t)for(let c of Jt){let l=c.length;if(i.startsWith(c+"_START:")){let h=parseInt(i.slice(l+7),10)||0;s.push({prefix:c,event:"START",total:h});break}if(i.startsWith(c+"_PROGRESS:")){let _=i.slice(l+10).split(":");s.push({prefix:c,event:"PROGRESS",current:parseInt(_[0],10)||0,total:parseInt(_[1],10)||0,key:(n=_[2])!=null?n:""});break}if(i===c+"_DONE"||i.startsWith(c+"_DONE:")){s.push({prefix:c,event:"DONE"});break}}return{events:s,buffer:r}}var Ze=class extends R.PluginSettingTab{constructor(e,t){super(e,t);this._saveTimeout=null;this._pfConfig=null;this._lastSyncTime=null;this._memoryStatusText=null;this._vectorDepsOk=null;this._embedStatusText=null;this._skillsCollapsed={user:!0};this._featurePanelsCollapsed={};this._advCollapsed=!0;this._refreshPending=!1;this._pythonInterpDescEl=null;this._customPathDescEl=null;this._checkEl=null;this.activeTab="setup";this._buildState="idle";this._buildProgress={current:0,total:0,key:""};this.plugin=t}_refreshPfConfig(){this._pfConfig=this.plugin.readPaperforgeJson()}display(){let{containerEl:e}=this;if(e.empty(),this._refreshPfConfig(),!document.getElementById("paperforge-tab-styles")){let a=document.createElement("style");a.id="paperforge-tab-styles",a.textContent=`
+`).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 f=t.createEl("div",{cls:"paperforge-summary-row"});f.createEl("span",{cls:"paperforge-summary-label",text:_.label}),f.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:f,extraArgs:u=[]}=z(_,this.plugin.settings,void 0,void 0);(0,Ie.execFile)(f,[...u,"-c","import paperforge; print(paperforge.__version__)"],{cwd:_,timeout:1e4},(g,m)=>{!g&&m&&(i.textContent="v"+m.trim())})}for(let _ of a){let f=t.createEl("div",{cls:"paperforge-summary-row"});f.createEl("span",{cls:"paperforge-summary-label",text:_.label}),f.createEl("span",{cls:"paperforge-summary-value",text:_.val})}e.createEl("h3",{text:o("complete_next")});let c=e.createEl("div",{cls:"paperforge-nextsteps"}),l=[[o("complete_step4"),o("complete_step4_desc")],["",`${o("complete_export_path")} ${n}/${r.system_dir}/PaperForge/exports/`],[o("complete_step1"),o("complete_step1_desc")],[o("complete_step2"),o("complete_step2_desc")],[o("complete_step3"),o("complete_step3_desc")]];for(let[_,f]of l){let u=c.createEl("div",{cls:"paperforge-nextstep-item"});_&&u.createEl("strong",{text:_}),u.createEl("span",{text:f})}}};var Fe=H(require("fs")),Je=H(require("path")),$t=require("child_process");function ht(p){return p.display_action==="rebuild_result"?"rebuild":p.display_action==="retry_ocr"||p.display_action==="upgrade_legacy"?"redo":null}function gt(p){return p==="redo"}function cr(p,d,e){let t={manifest:p,papers:{},cached_at:new Date().toISOString()};if(e!=null&&e.papers)for(let r of Object.keys(p))e.papers[r]&&(t.papers[r]=e.papers[r]);for(let r of d)t.papers[r.key]=r;return t}function jt(p){return Je.join(p,"System","PaperForge","cache","ocr_maintenance.json")}function Ge(p){try{let d=jt(p),e=Fe.readFileSync(d,"utf-8");return JSON.parse(e)}catch(d){return null}}function pr(p,d){let e=jt(p),t=Je.dirname(e);Fe.mkdirSync(t,{recursive:!0}),Fe.writeFileSync(e,JSON.stringify(d,null,2),"utf-8")}function Nt(p,d,e){return new Promise((t,r)=>{(0,$t.execFile)(p,d,e,(n,a)=>{n?r(n):t(a)})})}async function mt(p,d,e,t){let r=await Nt(d,[...e,"-m","paperforge","ocr","list","--manifest"],{cwd:p,timeout:3e4}),n=JSON.parse(r);if(t){let _=Object.keys(t.manifest),f=Object.keys(n);if(_.length===f.length&&_.every(g=>t.manifest[g]===n[g])&&Object.values(t.papers).every(m=>typeof m.needs_derived_rebuild=="boolean"))return{data:Object.values(t.papers),changed:!1}}let a=Object.keys(n),s=await Nt(d,[...e,"-m","paperforge","ocr","list","--json","--keys",...a],{cwd:p,timeout:3e4}),i=JSON.parse(s),c=cr(n,i,t);return pr(p,c),{data:Object.values(c.papers),changed:!0}}var dr=["EMBED","OCR_REBUILD","OCR_REDO"];function yt(p,d){var a,s;let t=(d+p).split(`
+`),r=(a=t.pop())!=null?a:"",n=[];for(let i of t)for(let c of dr){let l=c.length;if(i.startsWith(c+"_START:")){let _=parseInt(i.slice(l+7),10)||0;n.push({prefix:c,event:"START",total:_});break}if(i.startsWith(c+"_PROGRESS:")){let f=i.slice(l+10).split(":");n.push({prefix:c,event:"PROGRESS",current:parseInt(f[0],10)||0,total:parseInt(f[1],10)||0,key:(s=f[2])!=null?s:""});break}if(i===c+"_DONE"||i.startsWith(c+"_DONE:")){n.push({prefix:c,event:"DONE"});break}}return{events:n,buffer:r}}var Ne=class Ne extends F.PluginSettingTab{constructor(e,t){super(e,t);this._saveTimeout=null;this._pfConfig=null;this._lastSyncTime=null;this._memoryStatusText=null;this._vectorDepsOk=null;this._embedStatusText=null;this._skillsCollapsed={user:!0};this._featurePanelsCollapsed={};this._advCollapsed=!0;this._refreshPending=!1;this._pythonInterpDescEl=null;this._customPathDescEl=null;this._checkEl=null;this.activeTab="setup";this._buildState="idle";this._buildProgress={current:0,total:0,key:""};this._capabilityState=null;this._probing=new Set;this._attemptedProbes=new Set;this.plugin=t}_refreshPfConfig(){this._pfConfig=this.plugin.readPaperforgeJson()}display(){let{containerEl:e}=this;if(e.empty(),this._refreshPfConfig(),this._initCapabilityState(),this._applyStaleTolerance(),!document.getElementById("paperforge-tab-styles")){let a=document.createElement("style");a.id="paperforge-tab-styles",a.textContent=`
.paperforge-settings-tabs { display: flex; gap: 4px; margin-bottom: 16px; border-bottom: 1px solid var(--background-modifier-border); }
.paperforge-settings-tab { padding: 6px 16px; border: none; background: none; cursor: pointer; border-bottom: 2px solid transparent; font-size: 14px; color: var(--text-muted); }
.paperforge-settings-tab--active { color: var(--text-accent); border-bottom-color: var(--text-accent); }
@@ -30,26 +30,26 @@
.paperforge-manual-links a { color: var(--text-accent); }
.paperforge-modal-subtitle { color: var(--text-muted); font-size: 13px; margin-bottom: 12px; }
.paperforge-modal-item { font-size: 13px; margin-left: 8px; color: var(--text-muted); }
- `,document.head.appendChild(a)}let t=e.createDiv({cls:"paperforge-settings-tabs"}),r=[{id:"setup",label:o("tab_setup")||"\u5B89\u88C5"},{id:"features",label:o("tab_features")||"\u529F\u80FD"},{id:"maintenance",label:o("tab_maintenance")||"\u7EF4\u62A4"},{id:"release-notes",label:"\u66F4\u65B0\u4E0E\u624B\u518C"}],s={};r.forEach(a=>{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=>{s[a.id]=e.createDiv({cls:"paperforge-tab-content"+(a.id===this.activeTab?" paperforge-tab-content--active":"")})}),this.activeTab==="setup"?this._renderSetupTab(s.setup):this.activeTab==="features"?this._renderFeaturesTab(s.features):this.activeTab==="maintenance"?this._renderMaintenanceTab(s.maintenance):this._renderReleaseNotesTab(s["release-notes"])}_renderSetupTab(e){let t=this.app.vault.adapter.basePath;this.plugin.settings.vault_path||(this.plugin.settings.vault_path=t,this._debouncedSave()),this.plugin.settings.setup_complete&&(V.existsSync(ee.join(t,"paperforge.json"))||(this.plugin.settings.setup_complete=!1,this._debouncedSave())),e.createEl("h2",{text:o("header_title")||"PaperForge"}),e.createEl("p",{text:o("desc"),cls:"paperforge-settings-desc"});let s=e.createEl("div",{cls:"paperforge-setup-bar"}).createEl("span",{cls:"paperforge-setup-label"});this.plugin.settings.setup_complete?(s.setText(o("setup_done")),s.addClass("paperforge-setup-done")):(s.setText(o("setup_pending")),s.addClass("paperforge-setup-pending"));let a=this.app.vault.adapter.basePath,n=z(a,this.plugin.settings,void 0,void 0),i=n.path,c=this.plugin.settings._python_path_stale?"stale":n.source,l=new R.Setting(e).setName(o("field_python_interp")).setDesc(this._getPythonDesc(i,c));this._pythonInterpDescEl=l.descEl;let h=new R.Setting(e).setName(o("field_python_custom")).setDesc("");this._customPathDescEl=h.descEl,h.addText(x=>{x.setPlaceholder("e.g. C:\\Python310\\python.exe").setValue(this.plugin.settings.python_path||"").onChange(m=>{if(this.plugin.settings.python_path=m,this.plugin.saveSettings(),m&&m.trim()){let F=V.existsSync(m.trim());this.plugin.settings._python_path_stale=!F}else this.plugin.settings._python_path_stale=!1;let v=z(this.app.vault.adapter.basePath,this.plugin.settings,void 0,void 0),k=this.plugin.settings._python_path_stale?"stale":v.source;this._pythonInterpDescEl&&(this._pythonInterpDescEl.textContent=this._getPythonDesc(v.path,k))})}),h.addButton(x=>{x.setButtonText(o("btn_validate")).onClick(()=>this._validatePythonOverride())}),e.createEl("h3",{text:o("runtime_health")}),e.createEl("p",{text:o("runtime_health_desc"),cls:"paperforge-settings-desc"});let _=new R.Setting(e).setName("PaperForge").setDesc(o("runtime_health_checking")),p=_.descEl.createEl("span",{cls:"paperforge-runtime-badge"}),g=null;_.addButton(x=>{g=x,x.setButtonText(o("runtime_health_sync")).setDisabled(!0).onClick(()=>this._syncRuntime(x))});{let x=this.app.vault.adapter.basePath,{path:m,extraArgs:v=[]}=z(x,this.plugin.settings,void 0,void 0),k=this.plugin.manifest.version||"?";(0,G.execFile)(m,[...v,"-c","import paperforge; print(paperforge.__version__)"],{cwd:x,timeout:1e4},(F,P)=>{let O=this.plugin.settings.setup_complete,B=!F&&P?P.trim():null,j=B?`${o("runtime_health_plugin_ver").replace("{0}",k)} \u2192 ${o("runtime_health_package_ver").replace("{0}",B)}`:O?`Plugin v${k} \u2192 Python package not installed. Click "Sync Runtime" to install.`:`Plugin v${k} \u2192 Not configured. Please open the setup wizard first.`;_.setDesc(j),B===k?(p.setText(o("runtime_health_match")),p.className="paperforge-runtime-badge match",g&&g.setDisabled(!0)):B?(p.setText(o("runtime_health_mismatch")),p.className="paperforge-runtime-badge mismatch",g&&g.setDisabled(!1)):(p.setText(O?"Not installed":"Setup needed"),p.className="paperforge-runtime-badge missing",g&&g.setDisabled(!1))})}e.createEl("h3",{text:o("section_prep")}),e.createEl("p",{text:o("section_prep_desc"),cls:"paperforge-settings-desc"});let y=e.createEl("div",{cls:"paperforge-guide"}),E=[["prep_python","prep_python_desc"],["prep_zotero","prep_zotero_desc"],["prep_bbt","prep_bbt_desc"],["prep_key","prep_key_desc"]];for(let[x,m]of E){let v=y.createEl("div",{cls:"paperforge-guide-item"});v.createEl("strong",{text:o(x)}),v.createEl("span",{text:" \u2014 "+o(m)})}this._checkEl=e.createEl("div",{cls:"paperforge-message"});let f=!this.plugin.settings.setup_complete;new R.Setting(e).setName(o(f?"btn_install":"btn_reconfig")).setDesc(o(f?"btn_install_desc":"btn_reconfig_desc")).addButton(x=>{x.setButtonText(o(f?"btn_install":"btn_reconfig")).setCta().onClick(()=>{f?this._preCheck(()=>{new Oe(this.app,this.plugin).open()}):new Oe(this.app,this.plugin).open()})}),e.createEl("h3",{text:o("section_guide")});let b=e.createEl("div",{cls:"paperforge-guide"}),w=[["guide_open","guide_open_desc"],["guide_sync","guide_sync_desc"],["guide_ocr","guide_ocr_desc"]];for(let[x,m]of w){let v=b.createEl("div",{cls:"paperforge-guide-item"});v.createEl("strong",{text:o(x)}),v.createEl("span",{text:" \u2014 "+o(m)})}if(this.plugin.settings.setup_complete){e.createEl("h3",{text:o("section_config")});let x=e.createEl("div",{cls:"paperforge-summary"}),m=this.plugin.settings,v=this._pfConfig,k=[{label:o("dir_vault"),val:t},{label:o("dir_resources"),val:`${t}/${v==null?void 0:v.resources_dir}`},{label:" "+o("dir_notes"),val:`${t}/${v==null?void 0:v.resources_dir}/${v==null?void 0:v.literature_dir}`},{label:o("dir_base"),val:`${t}/${v==null?void 0:v.base_dir}`},{label:o("dir_system"),val:`${t}/${v==null?void 0:v.system_dir}`},{label:"API Key",val:m.paddleocr_api_key?o("api_key_set"):o("api_key_missing")},{label:o("field_zotero_data"),val:m.zotero_data_dir||o("not_set")}];for(let F of k){let P=x.createEl("div",{cls:"paperforge-summary-row"});P.createEl("span",{cls:"paperforge-summary-label",text:F.label}),P.createEl("span",{cls:"paperforge-summary-value",text:F.val})}}}_execMemoryStatus(e,t,r){(0,G.exec)(`"${e}" -m paperforge --vault "${t}" memory status --json`,{encoding:"utf-8",timeout:15e3},(s,a)=>{if(s){r("Status unavailable");return}try{let n=JSON.parse(a);if(n.ok){let i=n.data,c=i.fresh?"fresh":"stale";r(`Papers: ${i.paper_count_db} | ${c}${i.needs_rebuild?" - needs rebuild":""}`)}else r("DB not found. Run paperforge memory build.")}catch(n){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},(s,a)=>{if(s){r("Status unavailable");return}try{let n=JSON.parse(a);n.ok?r(`Chunks: ${n.data.chunk_count} | ${n.data.model} | ${n.data.mode}`):r("Could not parse status.")}catch(n){r("Could not parse status.")}})}_callPython(e,t){let r=this.app.vault.adapter.basePath,s=me(r,this.plugin.settings),a=[...s.extraArgs,"-m","paperforge","--vault",r,...e];if(t&&t.stream){let n=(0,G.spawn)(s.path,a,{cwd:r,env:t.env||process.env,windowsHide:!0});return t.onData&&n.stdout.on("data",t.onData),t.onStderr&&n.stderr.on("data",t.onStderr),t.onError&&n.on("error",t.onError),n.on("close",t.onClose),n}return(0,G.execFile)(s.path,a,{cwd:r,timeout:t&&t.timeout||6e4},(n,i,c)=>{t&&t.onClose&&t.onClose(n?1:0,i,c)}),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 s=e.createEl("button",{cls:"paperforge-rebuild-btn",text:o("feat_memory_rebuild_btn")});s.title="Rebuild memory database",s.onclick=()=>{let n=this.app.vault.adapter.basePath,i=me(n,this.plugin.settings);if(!i.path){new R.Notice(o("feat_no_python"));return}console.log("[PaperForge] Rebuilding memory:",i.path),s.setText(o("feat_memory_rebuilding")),s.setAttr("disabled",""),this._callPython(["memory","build"],{timeout:6e4,onClose:(c,l,h)=>{console.log("[PaperForge] memory build exit:",c?"FAIL:"+c:"OK",(l||"").slice(0,200),(h||"").slice(0,200)),s.setText(o("feat_memory_rebuild_btn")),s.removeAttribute("disabled"),c===0?new R.Notice(o("feat_memory_rebuild_done")):new R.Notice(o("feat_memory_rebuild_failed")+(h?" "+h.slice(0,80):"")),this._memoryStatusText=Ve(n),this._refreshSnapshots(n)}})};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(!me(e,this.plugin.settings).path)return;let r=document.querySelector(".paperforge-memory-status");r&&this._renderMemoryStatusText(r,"Checking...","syncing"),this.plugin._autoSyncRunning=!0,this._callPython(["sync"],{timeout:12e4,onClose:s=>{this.plugin._autoSyncRunning=!1,this._memoryStatusText=null,s===0&&(this._lastSyncTime=new Date().toLocaleTimeString(),this.plugin._lastSyncTime=this._lastSyncTime),this.display(),this._refreshSnapshots(e),He(this.app,this.plugin,e)}})}_refreshSnapshots(e){let t=me(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},(s,a,n)=>{this._refreshPending=!1,this._memoryStatusText=Ve(e),this._embedStatusText=Pe(e),this.display()})}_renderFeaturesTab(e){e.createEl("h3",{text:"Skills"});let t=e.createEl("div",{cls:"paperforge-desc-box"});t.setText(o("feat_skills_desc")),t.createEl("br"),t.createEl("span",{text:o("feat_skills_system")});let r={opencode:"OpenCode",claude:"Claude Code",codex:"Codex",cursor:"Cursor",windsurf:"Windsurf",github_copilot:"GitHub Copilot",gemini:"Gemini CLI"},s={opencode:".opencode/skills",claude:".claude/skills",codex:".codex/skills",cursor:".cursor/skills",windsurf:".windsurf/skills",github_copilot:".github/skills",gemini:".gemini/skills"},a=this.app.vault.adapter.basePath,n=this.plugin.settings.selected_skill_platform||"opencode";new R.Setting(e).setName(o("feat_agent_platform")).setDesc(o("feat_agent_platform_desc")).addDropdown(m=>{Object.entries(r).forEach(([v,k])=>m.addOption(v,k)),m.setValue(n).onChange(v=>{this.plugin.settings.selected_skill_platform=v,this.plugin.saveSettings(),this.display()})}).addExtraButton(m=>{m.setIcon("folder").setTooltip("Open skills folder").onClick(()=>{let v=s[n]||".opencode/skills",k=ee.join(a,v);V.existsSync(k)?(0,G.exec)(`start "" "${k}"`):new R.Notice(`Skills folder not found: ${v}`)})});let i=ee.join(a,s[n]),c=[],l=[];V.existsSync(i)&&V.readdirSync(i,{withFileTypes:!0}).forEach(m=>{if(!m.isDirectory())return;let v=ee.join(i,m.name,"SKILL.md");if(!V.existsSync(v))return;let k=V.readFileSync(v,"utf-8"),F=k.match(/^name:\s*(.+)$/m),P=k.split(`
-`),O=P.findIndex(N=>/^description:/.test(N)),B="";if(O>=0){let N=P[O].match(/^description:\s*(.+)$/);if(N&&N[1]&&N[1]!==">"&&N[1]!=="|-"&&N[1]!=="|")B=N[1].trim();else{for(let U=O+1;U{if(v.length===0)return;let F=h.createEl("div",{cls:"paperforge-skills-group"}),P=F.createEl("div",{cls:"paperforge-skills-collapse-header"}),O=F.createEl("div",{cls:"paperforge-skills-collapse-content"}),B=P.createEl("span",{text:"\u25BC",cls:"paperforge-skills-arrow"});P.createEl("h4",{text:`${m} (${v.length})`,cls:"paperforge-skills-subheader"}),v.forEach(A=>{let S=A.name+(A.version?" v"+A.version:""),N=k?" [system]":" [user]",U=A.desc||"",$=new R.Setting(O).setName(S+N).setDesc(U);$.settingEl.style.opacity=A.disabled?"0.4":"1",$.addToggle(L=>{L.setValue(!A.disabled).onChange(ne=>{let Q=!ne,te=A.content.match(/^disable-model-invocation:\s*(.+)$/m)?A.content.replace(/^disable-model-invocation:\s*.+$/m,`disable-model-invocation: ${Q}`):A.content.replace(/^(---\r?\n)/,`$1disable-model-invocation: ${Q}
-`);V.writeFileSync(A.path,te,"utf-8"),A.disabled=Q,A.content=te,$.settingEl.style.opacity=A.disabled?"0.4":"1"})})});let j=k?"system":"user";(this._skillsCollapsed[j]||!1)&&(O.style.display="none",B.style.transform="rotate(-90deg)"),P.addEventListener("click",()=>{O.style.display!=="none"?(O.style.display="none",B.style.transform="rotate(-90deg)"):(O.style.display="",B.style.transform="rotate(0deg)"),this._skillsCollapsed[j]=O.style.display==="none"})};_("System Skills",c,!0),_("User Skills",l,!1),c.length===0&&l.length===0&&h.createEl("p",{text:`No skills found in ${s[n]}. Run setup to deploy skills.`,cls:"setting-item-description"}),this._advCollapsed===void 0&&(this._advCollapsed=!0);let p=e.createEl("div",{cls:"paperforge-collapsible-header"}),g=p.createEl("span",{text:"\u25B6",cls:"paperforge-collapsible-arrow"});g.style.transform=this._advCollapsed?"rotate(0deg)":"rotate(90deg)";let y=p.createEl("span",{cls:"paperforge-collapsible-title",text:"Advanced"}),E=p.createEl("span",{cls:"paperforge-collapsible-sub",text:"Memory + Vector DB + Embedding"}),f=e.createEl("div",{cls:"paperforge-collapsible-content"});f.style.display=this._advCollapsed?"none":"",p.addEventListener("click",()=>{this._advCollapsed=!this._advCollapsed,f.style.display=this._advCollapsed?"none":"",g.style.transform=this._advCollapsed?"rotate(0deg)":"rotate(90deg)"}),f.createEl("h4",{text:"Memory Layer"}),f.createEl("div",{cls:"paperforge-desc-box"}).setText(o("feat_memory_desc"));let w=f.createEl("div",{cls:"paperforge-memory-status"}),x=this.app.vault.adapter.basePath;this.plugin._lastSyncTime&&!this._lastSyncTime&&(this._lastSyncTime=this.plugin._lastSyncTime),this._memoryStatusText===null&&(this._memoryStatusText=Ve(x)),this._renderMemoryStatusText(w,this._memoryStatusText,this._lastSyncTime),this._renderVectorSection(f)}_renderVectorSection(e){var c;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 R.Setting(e).setName(o("feat_vector_enable")).setDesc(o("feat_vector_enable_desc")).addToggle(l=>{l.setValue(!!this.plugin.settings.features.vector_db).onChange(h=>{this.plugin.settings.features.vector_db=h,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,s=e.createEl("div",{cls:"paperforge-vec-header"}),a=s.createEl("span",{text:"\u25BC",cls:"paperforge-skills-arrow"});s.createEl("span",{cls:"paperforge-vec-header-label",text:o("feat_vector_config_label")});let n=e.createEl("div",{cls:"paperforge-vector-config"}),i=l=>{n.style.display=l?"none":"",a.style.transform=l?"rotate(-90deg)":"rotate(0deg)"};if(i(nt(this._featurePanelsCollapsed,"vectorConfig",!1)),s.addEventListener("click",()=>{let l=xt(this._featurePanelsCollapsed,"vectorConfig",!1);i(l)}),this._vectorDepsOk===!0){this._renderVectorReady(n,r);return}if(this._vectorDepsOk===!1){this._renderVectorNoDeps(n);return}if(this._vectorDepsOk===null){let l=$e(r);this._vectorDepsOk=l&&(c=l.deps_installed)!=null?c:!1,this._vectorDepsOk&&(this._embedStatusText=Pe(r)),this.display()}}_renderApiConfig(e){new R.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 R.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 R.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 R.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 s=this.app.vault.adapter.basePath,a=me(s,this.plugin.settings);if(!a.path){new R.Notice(o("feat_no_python"));return}r.setButtonText(o("feat_installing")),r.setDisabled(!0);let n="chromadb openai",i=new R.Notice(o("feat_installing_pkgs").replace("{pkgs}",n),0);try{let c=Object.assign({},process.env,{PYTHONIOENCODING:"utf-8",PYTHONUTF8:"1"}),l=n.split(" ");await new Promise((h,_)=>{(0,G.execFile)(a.path,[...a.extraArgs,"-m","pip","install",...l],{cwd:s,timeout:3e5,env:c,windowsHide:!0},p=>{p?_(p):h()})}),i.hide(),new R.Notice(o("feat_install_done")),this._vectorDepsOk=!0,this._embedStatusText=Pe(s),this.display()}catch(c){i.hide(),new R.Notice(o("feat_install_failed")+(c.stderr||c.message||c)),r.setButtonText(o("feat_retry_btn")),r.setDisabled(!1)}})})}_renderVectorReady(e,t){e.createEl("div",{cls:"paperforge-desc-box"}).setText(Pe(t)),this._renderApiConfig(e);let s=e.createEl("div",{cls:"paperforge-embed-section"});s.createEl("div",{cls:"paperforge-embed-header"}).createEl("span",{text:o("retrieval_rebuild_vectors"),cls:"setting-item-name"});let n=s.createEl("div",{cls:"paperforge-embed-controls"}),i=s.createEl("div",{cls:"paperforge-embed-status-text",attr:{"aria-live":"polite"}});(()=>{n.empty(),i.empty();let l=$e(t),h=l==null?void 0:l.build_state,_=h&&typeof h=="object"&&!Array.isArray(h)?h:{};this.plugin._embedProgress=this.plugin._embedProgress||{current:0,total:0,key:""},!this.plugin._embedProcess&&_.status==="running"&&(this.plugin._embedProgress={current:typeof _.current=="number"?_.current:0,total:typeof _.total=="number"?_.total:1,key:typeof _.paper_id=="string"?_.paper_id:""});let{current:p,total:g,key:y}=this.plugin._embedProgress,E=typeof(l==null?void 0:l.body_chunk_count)=="number"?l.body_chunk_count:0,f=typeof(l==null?void 0:l.object_chunk_count)=="number"?l.object_chunk_count:0,w=(typeof(l==null?void 0:l.chunk_count)=="number"?l.chunk_count:0)+E+f,x=w>0,m=l!==null&&typeof l.corrupted=="boolean"&&l.corrupted,v=!!this.plugin._embedProcess,k=!this.plugin._embedProcess&&_.status==="running",F=(l==null?void 0:l.deps_installed)!==void 0?!!l.deps_installed:!0,P=typeof _.status=="string"?_.status:"",O=typeof _.message=="string"?_.message:"",B=S=>{var $;if(S==="--resume"&&x&&!m){let L=o("retrieval_rebuild_warning").replace("{n}",String(w));if(!confirm(L))return}if(S==="--force"&&x&&!m){let L="Force rebuild will replace "+w+" existing chunk(s). Continue?";if(!confirm(L))return}if(!me(t,this.plugin.settings).path){new R.Notice(o("retrieval_no_python"));return}let U=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",S],{stream:!0,env:U,onData:L=>{var te;let ne=typeof L=="string"?L:Buffer.isBuffer(L)?L.toString("utf-8"):String(L),{events:Q,buffer:ce}=ct(ne,(te=this.plugin._embedBuffer)!=null?te:"");this.plugin._embedBuffer=ce;for(let q of Q)q.event==="START"?this.plugin._embedProgress.total=q.total||0:q.event==="PROGRESS"?(this.plugin._embedProgress.current=q.current||0,this.plugin._embedProgress.key=q.key||""):q.event==="DONE"&&(this.plugin._embedProcess=null,this.plugin._embedProgress.current=this.plugin._embedProgress.total);this.display()},onStderr:L=>{this.plugin._embedStderr||(this.plugin._embedStderr=""),this.plugin._embedStderr+=String(L)},onError:L=>{this.plugin._embedProcess=null,new R.Notice(o("feat_build_failed")+": "+(L.message||L)),this.display()},onClose:L=>{var ne;if(clearInterval((ne=this.plugin._embedPollInterval)!=null?ne: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=Pe(t),new R.Notice(o("feat_build_complete"));else{this._embedStatusText=null;let Q=(this.plugin._embedStderr||"").slice(0,200);new R.Notice(o("feat_build_failed")+(Q?": "+Q:""),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,ne)=>{var Q;if(this.plugin._embedPolling=!1,L===0&&ne)try{let te=JSON.parse(ne).data;if(te&&te.build_state){let q=te.build_state;(q.status==="stopping"||q.status==="idle")&&this.plugin._embedProcess&&(this.plugin._embedProcess=null,clearInterval((Q=this.plugin._embedPollInterval)!=null?Q:void 0),this.plugin._embedPollInterval=null,this.display()),q.current!==void 0&&q.total!==void 0&&(this.plugin._embedProgress.current=q.current,this.plugin._embedProgress.total=q.total||1,this.plugin._embedProgress.key=q.paper_id||"")}}catch(ce){}}}))},2e3),this.display()},j=Ae(t),H=!1;j&&typeof j.summary=="object"&&j.summary!==null&&"status"in j.summary&&(H=j.summary.status==="version_mismatch");let A;switch(F?H?A="runtime-mismatch":P==="stopping"?A="stopping":v&&P==="running"?A="building":P==="failed"?A="failed":P==="stopped"?A="stopped":k?A="stale":m?A="corrupted":x?A="ready":A="idle":A="deps-missing",A){case"building":{let S=n.createEl("div",{cls:"paperforge-progress-track"});S.style.cssText="flex:1;";let N=g>0?(p/g*100).toFixed(1):"0",U=S.createEl("div",{cls:"paperforge-progress-seg done"});if(U.style.cssText=`width:${N}%; min-width:${p>0?"2px":"0"};`,p{this._callPython(["embed","stop","--json"],{timeout:8e3}),this.display()});break}case"stopping":{let S=n.createEl("div",{cls:"paperforge-progress-track"});S.style.cssText="flex:1; opacity:0.5;";let N=g>0?(p/g*100).toFixed(1):"0",U=S.createEl("div",{cls:"paperforge-progress-seg done"});if(U.style.cssText=`width:${N}%; min-width:${p>0?"2px":"0"};`,pB("--resume"));let N=n.createEl("button");N.setText(o("retrieval_force_rebuild")),N.style.marginLeft="6px",N.addEventListener("click",()=>B("--force"));break}case"stopped":{i.setText(o("retrieval_build_stopped"));let S=n.createEl("button");S.setText(o("retrieval_retry")),S.className="mod-cta",S.addEventListener("click",()=>B("--resume"));break}case"corrupted":{i.createEl("div",{cls:"paperforge-desc-box",text:o("feat_vector_corrupted"),attr:{style:"background:var(--background-modifier-warning);"}});let S=n.createEl("button");S.setText(o("retrieval_force_rebuild")),S.className="mod-cta",S.addEventListener("click",()=>B("--force"));break}case"stale":{i.createEl("div",{cls:"paperforge-desc-box",text:o("retrieval_build_stale"),attr:{style:"color:var(--text-warning);"}});let S=n.createEl("button");S.setText(o("retrieval_rebuild_vectors")),S.className="mod-cta",S.addEventListener("click",()=>B("--resume"));break}case"ready":{n.createEl("span",{text:w+" chunks embedded",cls:"setting-item-description"});let S=n.createEl("button");S.setText(o("retrieval_rebuild_vectors")),S.className="mod-cta",S.addEventListener("click",()=>B("--resume"));let N=n.createEl("button");N.setText(o("retrieval_force_rebuild")),N.style.marginLeft="6px",N.addEventListener("click",()=>B("--force"));break}case"deps-missing":{i.setText(o("retrieval_build_deps_missing"));let S=n.createEl("a");S.setText(o("feat_install_deps")),S.style.cssText="cursor:pointer; text-decoration:underline;",S.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 S=n.createEl("a");S.setText(o("runtime_health_sync")),S.style.cssText="cursor:pointer; text-decoration:underline;",S.addEventListener("click",()=>{this.display()});break}case"idle":default:{i.setText(o("retrieval_build_idle"));let S=n.createEl("button");S.setText(o("feat_build_btn")),S.className="mod-cta",S.addEventListener("click",()=>B("--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 s=r.match(/^\s*([^:]+):\s*(.*)/);s&&(t[s[1].trim()]=s[2].trim())}),t.db_exists!==void 0&&(t.db_exists=t.db_exists==="True"),t.chunk_count!==void 0&&(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 R.Notice(r);return}if(!V.existsSync(e)){let r="\u8DEF\u5F84\u4E0D\u5B58\u5728 / Path does not exist";t&&(t.innerHTML=`\u2717 ${r}`),new R.Notice(r,4e3);return}try{V.accessSync(e,V.constants.X_OK)}catch(r){let s="\u4E0D\u53EF\u6267\u884C / Not executable";t&&(t.innerHTML=`\u2717 ${s}`),new R.Notice(s,4e3);return}(0,G.execFile)(e,["--version"],{timeout:8e3},(r,s)=>{if(r||!s){let c="\u65E0\u6CD5\u8FD0\u884C / Cannot run";t&&(t.innerHTML=`\u2717 ${c}`),new R.Notice(c,4e3);return}let a=s.match(/Python (\d+)\.(\d+)/);if(!a){let c="\u65E0\u6CD5\u89E3\u6790\u7248\u672C / Cannot parse version";t&&(t.innerHTML=`\u2717 ${c}`),new R.Notice(c,4e3);return}let n=parseInt(a[1],10),i=parseInt(a[2],10);if(n<3||n===3&&i<10){let c="Python \u7248\u672C\u8FC7\u4F4E\uFF0C\u9700\u8981 3.10+ / Python version too low, need 3.10+";t&&(t.innerHTML=`\u2717 ${c}`),new R.Notice(c,4e3);return}(0,G.execFile)(e,["-m","pip","--version"],{timeout:8e3},c=>{if(c){let l=`\u2713 Python ${n}.${i} \u6709\u6548\uFF0C\u4F46\u672A\u68C0\u6D4B\u5230 pip / Valid, but pip not found`;t&&(t.innerHTML=`\u26A0 ${l}`),new R.Notice(l,4e3)}else{let l=`\u2713 Python ${n}.${i} \u6709\u6548 / Valid`;t&&(t.innerHTML=`${l}`),new R.Notice(l,4e3)}})})}_syncRuntime(e){let t=this.app.vault.adapter.basePath,{path:r,extraArgs:s=[]}=z(t,this.plugin.settings,void 0,void 0),a=this.plugin.manifest.version,n=yt(r,a,s);e.setDisabled(!0),e.setButtonText(o("runtime_health_syncing"));let i=(l,h)=>(console.log(`[PaperForge] Sync Runtime: trying ${h}`),vt(n.cmd,l,t,n.timeout,void 0,we())),c=()=>{let l="opencode";try{let g=V.readFileSync(ee.join(t,"paperforge.json"),"utf-8"),y=JSON.parse(g);y.agent_key&&(l=y.agent_key)}catch(g){}let h=[...s,"-c",'from paperforge.services.skill_deploy import deploy_skills; from pathlib import Path; r=deploy_skills(vault=Path(r"'+t.replace(/\\/g,"\\\\")+'"), agent_key="'+l+'", overwrite=True); print("skills deployed" if r["skill_deployed"] else "skills skipped", flush=True)'],_=(0,G.spawn)(r,h,{cwd:t,timeout:3e4,windowsHide:!0}),p="";_.stdout.on("data",g=>{p+=g.toString("utf-8")}),_.on("close",g=>{console.log(`[PaperForge] Skill deploy: ${p.trim()} (exit ${g})`)})};i(n.pypiArgs,"PyPI").then(l=>{if(l.exitCode===0){console.log("[PaperForge] Sync Runtime: installed via PyPI"),c(),new R.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(n.gitArgs,"git").then(h=>{h.exitCode===0?(console.log("[PaperForge] Sync Runtime: installed via git"),c(),new R.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:",h.stderr),new R.Notice(o("runtime_health_sync_fail").replace("{0}","pip exit code "+h.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:s=[]}=z(t,(a=this.plugin)==null?void 0:a.settings,void 0,void 0);(0,G.execFile)(r,[...s,"--version"],{timeout:8e3},(n,i)=>{let c=[];c.push({label:"Python",ok:!n,detail:n?o("check_python_fail"):i.trim()});let l=!1,h=process.env.HOME||process.env.USERPROFILE||Ft.homedir()||"";if(process.platform==="darwin")l=["/Applications/Zotero.app",ee.join(h,"Applications","Zotero.app")].some(b=>{try{return V.existsSync(b)}catch(w){return!1}});else if(process.platform==="win32"){let f=process.env.ProgramFiles||"",b=process.env.LOCALAPPDATA||"";l=[ee.join(f,"Zotero"),ee.join(f,"(x86)","Zotero"),ee.join(b,"Programs","Zotero"),ee.join(b,"Zotero"),ee.join(h,"AppData","Local","Programs","Zotero")].filter(Boolean).some(x=>{try{return V.existsSync(x)}catch(m){return!1}})}else l=[ee.join(h,".local","share","zotero","zotero"),"/usr/bin/zotero","/usr/local/bin/zotero"].some(b=>{try{return V.existsSync(b)}catch(w){return!1}});let _=this.plugin.settings.zotero_data_dir;if(!l&&_)try{l=V.existsSync(_)}catch(f){}c.push({label:"Zotero",ok:l,detail:l?o("check_zotero_ok"):o("check_zotero_fail")});let p=!1,g=process.env.APPDATA||"";process.platform==="win32"&&g&&(p=ze(ee.join(g,"Zotero","Zotero","Profiles"))),!p&&process.platform==="darwin"&&h&&(p=ze(ee.join(h,"Library","Application Support","Zotero","Profiles"))),!p&&process.platform!=="win32"&&process.platform!=="darwin"&&h&&(p=ze(ee.join(h,".zotero","zotero","Profiles"))),!p&&_&&String(_).trim()&&(p=tt(_.trim())),!p&&h&&(p=tt(ee.join(h,"Zotero"))),c.push({label:"Better BibTeX",ok:p,detail:p?o("check_bbt_ok"):o("check_bbt_fail")});let y={true:"\u2713",false:"\u2717"};if(this._checkEl){this._checkEl.setText(c.map(b=>`${y[String(b.ok)]} ${b.label}: ${b.detail}`).join(`
-`));let f=c.some(b=>!b.ok);this._checkEl.className=`paperforge-message msg-${f?"error":"ok"}`}let E=c.filter(f=>!f.ok);E.length>0&&new R.Notice(`[!!] \u672A\u901A\u8FC7: ${E.map(f=>f.label).join(", ")}`,6e3),e()})}_renderMaintenanceTab(e){var h;e.createEl("h2",{text:o("tab_maintenance")||"\u7EF4\u62A4"});let r=(h=this.app.vault.adapter.basePath)!=null?h:"",s=e.createEl("div"),a={active:"all"},n=null;try{n=We(r)}catch(_){}let i=z(r,this.plugin.settings,V,G.execFileSync);if(!i.path){s.createEl("p",{text:"\u26A0 Python \u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u5728\u300C\u5B89\u88C5\u300D\u6807\u7B7E\u9875\u914D\u7F6E\u3002",cls:"setting-item-description"});return}let c=()=>!!this.plugin._ocrProcess,l=_=>{s.empty();let p=_,g=s.createEl("div",{cls:"pf-maint-filters"}),y=g.createEl("button",{cls:"pf-maint-filter"+(a.active==="all"?" active":""),text:o("maintenance_filter_all")||"All"});y.addEventListener("click",()=>{a.active="all",l(_)});let E=g.createEl("button",{cls:"pf-maint-filter"+(a.active==="recommended"?" active":""),text:o("maintenance_filter_recommended")||"Recommended"});E.addEventListener("click",()=>{a.active="recommended",l(_)});let f=a.active==="recommended"?p.filter(b=>b.needs_derived_rebuild===!0):p;if(f.length===0)s.createEl("p",{text:"\u5F53\u524D\u7B5B\u9009\u6761\u4EF6\u4E0B\u65E0\u6570\u636E",cls:"setting-item-description"});else{let b=i.path,w=i.extraArgs||[],x=s.createEl("div",{cls:"pf-maint-progress"});x.style.display="none";let m=x.createEl("div",{cls:"paperforge-progress-track"});m.style.cssText="flex:1;";let v=m.createEl("div",{cls:"paperforge-progress-seg done"}),k=m.createEl("div",{cls:"paperforge-progress-seg pending"}),F=x.createEl("span",{cls:"pf-maint-progress-text"}),P=x.createEl("span",{cls:"pf-maint-progress-key"}),O=x.createEl("button",{text:o("maintenance_stop")||"Stop"});O.className="mod-warning",O.addEventListener("click",()=>{let C=this.plugin._ocrProcess;C&&(C.stdin&&typeof C.stdin.write=="function"?C.stdin.write(`PAPERFORGE_STOP
-`):typeof C.kill=="function"&&C.kill("SIGINT")),this.plugin._ocrWasStopped=!0,O.disabled=!0,O.textContent=(o("maintenance_stop")||"Stop")+"\u2026"});let B=()=>{let C=this.plugin._ocrProgress;if(!C||C.total===0||!this.plugin._ocrProcess){x.style.display="none";return}x.style.display="flex";let Y=C.total>0?(C.current/C.total*100).toFixed(1):"0";v.style.width=`${Y}%`,v.style.minWidth=C.current>0?"2px":"0",C.current{let Y=document.createElement("th");Y.textContent=C,U.appendChild(Y)});let $=c();for(let C of f){let Y=N.insertRow(),T=Y.insertCell();T.style.cssText="padding:3px 4px;text-align:center;width:24px;";let I=document.createElement("input");I.type="checkbox",I.className="pf-maint-sel",I.checked=j.get(C.key)||!1,I.addEventListener("change",()=>{j.set(C.key,I.checked),Q()}),T.appendChild(I);let re=Y.insertCell();re.style.cssText="padding:3px 4px;";let D=re.createEl("div",{cls:"pf-maint-paper-info"});D.createEl("div",{cls:"pf-maint-paper-title",text:C.title||C.key}),D.createEl("div",{cls:"pf-maint-paper-key",text:C.key});let se=Y.insertCell();se.style.cssText="padding:3px 4px;",se.createEl("div",{cls:"pf-maint-reason",text:C.display_reason||""});let de=Y.insertCell();de.style.cssText="padding:3px 4px;white-space:nowrap;";let be=de.createEl("div",{cls:"pf-maint-actions"}),Te=it(C);if(Te==="rebuild"){let oe=be.createEl("button",{cls:"pf-maint-action-btn rebuild",text:o("maintenance_btn_rebuild")||"Rebuild"});$&&(oe.disabled=!0),oe.addEventListener("click",()=>{(0,G.execFile)(b,[...w,"-m","paperforge","ocr","rebuild",C.key],{cwd:r,timeout:12e4,windowsHide:!0},()=>{new R.Notice((o("maintenance_btn_rebuild")||"Rebuild")+" \u2014 "+C.key)})})}else if(Te==="redo"){let oe=be.createEl("button",{cls:"pf-maint-action-btn redo",text:o("ocr_maint_redo_btn")||"Redo"});$&&(oe.disabled=!0),oe.addEventListener("click",()=>{ot("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,[...w,"-m","paperforge","ocr","redo",C.key],{cwd:r,timeout:3e5,windowsHide:!0},()=>{new R.Notice((o("ocr_maint_redo_btn")||"Redo OCR")+" \u2014 "+C.key)})})}}let L=s.createEl("div",{cls:"pf-maint-batch-bar"}),ne=L.createEl("span",{cls:"pf-maint-batch-label",text:"0 selected"}),Q=()=>{let C=f.filter(Y=>j.get(Y.key)).length;ne.textContent=C+" selected"},ce=L.createEl("button",{cls:"mod-cta",text:o("maintenance_batch_rebuild")||"\u25B6 Rebuild selected"});ce.disabled=$;let te=L.createEl("button",{cls:"mod-cta",text:o("maintenance_batch_redo")||"\u25B6 Full OCR redo selected"});te.disabled=$;let q=C=>{let Y=f.filter(D=>j.get(D.key)&&it(D)===C);if(Y.length===0){let D=C==="rebuild"?o("maintenance_btn_rebuild")||"Rebuild":o("ocr_maint_redo_btn")||"Redo";new R.Notice("Selected papers are not eligible for "+D+". Uncheck ineligible rows and try again.",6e3);return}if(ot(C)&&!confirm((o("ocr_maint_redo_confirm")||"Rerun OCR for {n} paper(s)? Existing derived OCR artifacts will be replaced.").replace("{n}",String(Y.length))))return;let T=Y.map(D=>D.key);this.plugin._ocrProgress={current:0,total:T.length,key:""},this.plugin._ocrBuffer="",this.plugin._ocrWasStopped=!1;let I=C==="rebuild"?"OCR_REBUILD":"OCR_REDO";ce.disabled=!0,te.disabled=!0,Array.from(H.querySelectorAll(".pf-maint-action-btn")).forEach(D=>{D.disabled=!0}),Array.from(H.querySelectorAll(".pf-maint-sel")).forEach(D=>{D.disabled=!0}),y.disabled=!0,E.disabled=!0,O.disabled=!1,O.textContent=o("maintenance_stop")||"Stop";let re=this._callPython(["ocr",C,...T],{stream:!0,onData:D=>{var Te;let se=typeof D=="string"?D:Buffer.isBuffer(D)?D.toString("utf-8"):String(D),{events:de,buffer:be}=ct(se,(Te=this.plugin._ocrBuffer)!=null?Te:"");this.plugin._ocrBuffer=be;for(let oe of de)oe.event==="START"?this.plugin._ocrProgress&&(this.plugin._ocrProgress.total=oe.total||T.length):oe.event==="PROGRESS"&&(this.plugin._ocrProgress={current:oe.current||0,total:oe.total||T.length,key:oe.key||""});B()},onError:D=>{this.plugin._ocrProcess=null,new R.Notice("Batch error: "+(D.message||D)),l(_)},onClose:D=>{this.plugin._ocrWasStopped||D===130?(this.plugin._ocrWasStopped=!1,this.plugin._ocrProcess=null,B(),new R.Notice("OCR batch stopped by user.")):D===0?(this.plugin._ocrProgress&&(this.plugin._ocrProgress.current=this.plugin._ocrProgress.total),this.plugin._ocrProcess=null,B(),new R.Notice((o("maintenance_batch_complete")||"Batch operation complete \u2014 {n} papers processed.").replace("{n}",String(T.length)))):(this.plugin._ocrProcess=null,B(),new R.Notice("Batch operation finished with exit code "+D+".",8e3)),lt(r,b,w,n).then(se=>{n=We(r),l(se.data)}).catch(()=>{l(p)})}});this.plugin._ocrProcess=re,B()};ce.addEventListener("click",()=>q("rebuild")),te.addEventListener("click",()=>q("redo")),Q()}};if(n){let _=Object.values(n.papers);l(_)}else s.createEl("p",{text:"\u6B63\u5728\u52A0\u8F7D OCR \u7EF4\u62A4\u6570\u636E\u2026"});lt(r,i.path,i.extraArgs||[],n||null).then(_=>{n=We(r),(_.changed||!n)&&l(_.data)}).catch(()=>{n||(s.empty(),s.createEl("p",{text:"\u65E0\u6CD5\u52A0\u8F7D OCR \u6570\u636E\u3002\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5 paperforge \u5E76\u8FD0\u884C\u8FC7 OCR\u3002",cls:"setting-item-description"}))})}_renderReleaseNotesTab(e){e.createEl("h2",{text:"\u66F4\u65B0\u4E0E\u624B\u518C"}),e.createEl("h3",{text:"\u7248\u672C\u66F4\u65B0\u8BB0\u5F55"});let t=Tt.default.versions||[];for(let a of t){let n=e.createEl("div",{cls:"paperforge-release-card"}),i=n.createEl("div",{cls:"paperforge-release-header"});if(i.createEl("strong",{text:`v${a.version} \u2014 ${a.title}`}),i.createEl("span",{cls:"paperforge-release-date",text:` (${a.date})`}),a.breaking_or_migration&&a.breaking_or_migration.length>0){let c=n.createEl("div",{cls:"paperforge-release-section"});c.createEl("div",{cls:"paperforge-release-label",text:"\u884C\u4E3A\u53D8\u66F4 / \u8FC1\u79FB\u6CE8\u610F"});for(let l of a.breaking_or_migration)c.createEl("div",{cls:"paperforge-release-item",text:`\u2022 ${l}`})}if(a.new_features&&a.new_features.length>0){let c=n.createEl("div",{cls:"paperforge-release-section"});c.createEl("div",{cls:"paperforge-release-label",text:"\u65B0\u529F\u80FD"});for(let l of a.new_features)c.createEl("div",{cls:"paperforge-release-item",text:`\u2022 ${l}`})}if(a.fixes&&a.fixes.length>0){let c=n.createEl("div",{cls:"paperforge-release-section"});c.createEl("div",{cls:"paperforge-release-label",text:"\u4FEE\u590D"});for(let l of a.fixes)c.createEl("div",{cls:"paperforge-release-item",text:`\u2022 ${l}`})}if(a.recommended_actions&&a.recommended_actions.length>0){let c=n.createEl("div",{cls:"paperforge-release-section paperforge-release-recommended"});c.createEl("div",{cls:"paperforge-release-label",text:"\u5EFA\u8BAE\u64CD\u4F5C"});for(let l of a.recommended_actions)c.createEl("div",{cls:"paperforge-release-item paperforge-release-item-bold",text:`\u2022 ${l}`})}}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")}};var M=require("obsidian"),ye=K(require("fs")),Ne=K(require("path")),ve=require("child_process");var Ie=K(require("path"));function Dt(d){if(!d)return null;let u=Ie.dirname(d);for(;;){let e=Ie.basename(u);if(!e||e===".")break;let t=e.match(/^([A-Z0-9]{8})(?:\s*-\s*.*)?$/i);if(t)return t[1];let r=Ie.dirname(u);if(r===u)break;u=r}return null}var J=K(require("fs")),xe=K(require("path"));function Me(d){return le(d).ocrDir}function Gt(d,u){let e=xe.join(Me(d),u,"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 s=r,a=s.versions,n=s.current;if(Array.isArray(a)&&n&&typeof n=="object"&&"label"in n)return r}return null}catch(t){return null}}function Xt(d){let u=Me(d);try{return J.existsSync(u)?J.readdirSync(u,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name):[]}catch(e){return[]}}function pt(d){let u=Xt(d),e=[];for(let t of u){let r=Gt(d,t);if(!r)continue;let s=r.versions.map(n=>n.label),a=0;for(let n of s){let i=xe.join(Me(d),t,"versions",n,"fulltext.md");try{J.existsSync(i)&&(a+=J.statSync(i).size)}catch(c){}}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 At(d,u,e){let t=Me(d),r=xe.join(t,u,"versions",e,"fulltext.md"),s=xe.join(t,u,"render"),a=xe.join(s,"fulltext.md");try{return J.existsSync(r)?(J.existsSync(s)||J.mkdirSync(s,{recursive:!0}),J.copyFileSync(r,a),!0):!1}catch(n){return!1}}function Ot(d,u,e,t){var p;let r=Me(d),s=xe.join(r,u,"versions",e,"fulltext.md"),a=xe.join(r,u,"versions",t,"fulltext.md"),n="",i="";try{J.existsSync(s)&&(n=J.readFileSync(s,"utf-8"))}catch(g){}try{J.existsSync(a)&&(i=J.readFileSync(a,"utf-8"))}catch(g){}let c=Bt(n),l=Bt(i),h=Math.max(c.length,l.length),_=[];for(let g=0;g0)e.push(t.join(`
-`).trim()),t=[r];else if(r.trim()===""&&t.length>0){let s=t.join(`
-`).trim();s&&(e.push(s),t=[])}else t.push(r);if(t.length>0){let r=t.join(`
-`).trim();r&&e.push(r)}return e}var Re=class extends M.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 ke}getDisplayText(){return"PaperForge"}getIcon(){return De}async onOpen(){this._buildPanel(),this._modeSubscribers=[],this._leafChangeTimer=null,this._setupEventSubscriptions(),this._fetchVersion(),this._detectAndSwitch(),this._onKeyDown=e=>{var t,r,s;if(e.key==="/"&&!e.ctrlKey&&!e.metaKey&&!e.altKey){let a=(r=(t=e.target)==null?void 0:t.tagName)==null?void 0:r.toLowerCase();a!=="input"&&a!=="textarea"&&(e.preventDefault(),(s=this._searchInput)==null||s.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 s=t.createEl("button",{cls:"paperforge-header-refresh",attr:{"aria-label":"Refresh"}});s.innerHTML="\u21BB",s.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 n,i;let e=this.app.vault.adapter.basePath,t=this.app.plugins.plugins.paperforge,r=((n=t==null?void 0:t.manifest)==null?void 0:n.version)||"?",{path:s,extraArgs:a=[]}=z(e,(i=t==null?void 0:t.settings)!=null?i:null,void 0,void 0);mt(s,r,e,1e4,void 0).then(c=>{if(c.status==="not-installed")return;let l=c.pyVersion||"";this._paperforgeVersion=l.startsWith("v")?l:"v"+l,this._versionBadge&&this._versionBadge.setText(this._paperforgeVersion),this._driftBannerEl&&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 n;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:s,extraArgs:a=[]}=z(t,(n=r==null?void 0:r.settings)!=null?n:null,void 0,void 0);(0,ve.execFile)(s,[...a,"-m","paperforge","dashboard","--json"],{cwd:t,timeout:3e4},(i,c)=>{if(!i)try{let l=JSON.parse(c);if(l.ok&&l.data){let h=this._normalizeDashboardData(l.data);this._cachedStats=h,this._metricsEl.empty(),this._renderStats(h),this._renderOcr(h),this._dashboardPermissions=l.data.permissions||{};return}}catch(l){}this._fallbackFetchStats(e,t,r)})}_normalizeDashboardData(e){let t=e.stats||{},r=t.ocr_health||{},s=t.pdf_health||{},a=e.ocr_version_state||{},n=(r.done||0)+(r.pending||0)+(r.failed||0);return{total_papers:t.papers||0,formal_notes:t.papers||0,exports:0,bases:0,ocr:{total:n,pending:r.pending||0,processing:0,done:r.done||0,failed:r.failed||0},path_errors:(s.broken||0)+(s.missing||0),ocr_version_state:{total_papers:a.total_papers||0,derived_stale_count:a.derived_stale_count||0,raw_upgradable_count:a.raw_upgradable_count||0}}}_fallbackFetchStats(e,t,r){var n,i,c;let s=((n=r==null?void 0:r.settings)==null?void 0:n.system_dir)||"System",a=Ne.join(t,s,"PaperForge","indexes","formal-library.json");try{let l=ye.readFileSync(a,"utf-8"),h=JSON.parse(l),_=h.items||[],p={},g={pdf_health:{healthy:0,unhealthy:0},ocr_health:{healthy:0,unhealthy:0},note_health:{healthy:0,unhealthy:0},asset_health:{healthy:0,unhealthy:0}},y=0,E=0,f=0,b=0,w=0,x=0;for(let m of _){m.note_path&&x++;let v=m.lifecycle||"pdf_ready";p[v]=(p[v]||0)+1;let k=m.health||{};for(let P of["pdf_health","ocr_health","note_health","asset_health"])(k[P]||"healthy")==="healthy"?g[P].healthy++:g[P].unhealthy++;let F=m.ocr_status||"";y++,F==="done"?E++:F==="pending"?f++:F==="processing"||F==="queued"||F==="running"?b++:w++}this._cachedStats={version:h.paperforge_version||((i=this._cachedStats)==null?void 0:i.version)||"\u2014",total_papers:_.length,formal_notes:x,exports:0,bases:0,ocr:{total:y,pending:f,processing:b,done:E,failed:w},path_errors:0,lifecycle_level_counts:p,health_aggregate:g},this._metricsEl.empty(),this._renderStats(this._cachedStats),this._renderOcr(this._cachedStats)}catch(l){!e&&!this._cachedStats&&this._metricsEl.createEl("div",{cls:"paperforge-status-loading",text:"No index \u2014 trying CLI..."});let{path:h,extraArgs:_=[]}=z(t,(c=r==null?void 0:r.settings)!=null?c:null,void 0,void 0);(0,ve.execFile)(h,[..._,"-m","paperforge","status","--json"],{cwd:t,timeout:3e4},(p,g)=>{if(p){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 y=JSON.parse(g);this._cachedStats=y,this._metricsEl.empty(),this._renderStats(y),this._renderOcr(y)}catch(y){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 s=Math.min(100,t/r*100);e.createEl("div",{cls:"paperforge-metric-progress"}).createEl("div",{cls:"paperforge-metric-progress-fill",attr:{style:`width:${s.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",s=Ne.join(e,r,"PaperForge","indexes","formal-library.json");try{let n=ye.readFileSync(s,"utf-8");return JSON.parse(n)}catch(n){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 ht(this.app,t)}_patchCachedEntry(e,t){if(!e||!this._cachedItems)return;let r=this._cachedItems.findIndex(s=>s.zotero_key===e);r!==-1&&(this._cachedItems[r]=qe(this._cachedItems[r],t))}_filterByDomain(e){return e?this._getCachedIndex().filter(t=>t.domain===e):[]}_renderStats(e){var n;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,s=[{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 s){let c=this._metricsEl.createEl("div",{cls:"paperforge-metric-card"});c.style.setProperty("--metric-color",i.color),c.createEl("div",{cls:"paperforge-metric-value",text:((n=i.value)==null?void 0:n.toString())||"\u2014"}),c.createEl("div",{cls:"paperforge-metric-label",text:i.label}),i.barMax>0&&this._buildMetricBar(c,i.value,i.barMax)}let a=e.ocr_version_state||{};if(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 c=this._metricsEl.createEl("div",{cls:"paperforge-metric-card"});c.style.setProperty("--metric-color","var(--color-yellow)"),c.createEl("div",{cls:"paperforge-metric-value",text:i.join(", ")}),c.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 s=t.done||0,a=t.pending||0,n=t.processing||0,i=t.failed||0;if(this._ocrBadge&&(this._ocrBadge.removeClass("active","idle"),n>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(),n>0?this._ocrTrack.addClass("paperforge-processing"):this._ocrTrack.removeClass("paperforge-processing");let c=[{cls:"pending",count:a},{cls:"active",count:n},{cls:"done",count:s},{cls:"failed",count:i}];for(let l of c)if(l.count>0){let h=(l.count/r*100).toFixed(1);this._ocrTrack.createEl("div",{cls:`paperforge-progress-seg ${l.cls}`,attr:{style:`width:${h}%`}})}}if(this._ocrCounts){this._ocrCounts.empty();let c=[{cls:"pending",value:a,label:"Pending"},{cls:"active",value:n,label:"Processing"},{cls:"done",value:s,label:"Done"},{cls:"failed",value:i,label:"Failed"}];for(let l of c){let h=this._ocrCounts.createEl("div",{cls:"paperforge-ocr-count"});h.createEl("div",{cls:"paperforge-ocr-count-value",text:l.value.toString()}),h.createEl("div",{cls:"paperforge-ocr-count-label",text:l.label})}}}_renderLifecycleStepper(e,t,r){if(!t||!r){this._renderSkeleton(e);return}let s=[{key:"indexed",label:"Indexed"},{key:"pdf_ready",label:"PDF Ready"},{key:"fulltext_ready",label:"Fulltext Ready"},{key:"deep_read_done",label:"Deep Read"}],a=e.createEl("div",{cls:"paperforge-lifecycle-stepper"}),n=!1;for(let i of s){let c=a.createEl("div",{cls:"step"});c.createEl("div",{cls:"step-indicator"}),c.createEl("div",{cls:"step-label",text:i.label}),i.key===r?(c.addClass("current"),n=!0):n?c.addClass("pending"):c.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"}],s=e.createEl("div",{cls:"paperforge-health-matrix"});for(let a of r){let n=t[a.key]||"healthy",i=s.createEl("div",{cls:"paperforge-health-cell"}),c,l,h;n==="healthy"||n==="ok"?(c=a.iconOk,l="ok",h=`${a.label}: OK`):n==="warn"||n==="warning"||n==="degraded"?(c=a.iconWarn,l="warn",h=`${a.label}: Needs Attention`):(c=a.iconFail,l="fail",h=`${a.label}: Failed`),i.addClass(l),i.setAttribute("title",h),i.createEl("div",{cls:"paperforge-health-cell-icon",text:c}),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 s=e.createEl("div",{cls:"paperforge-maturity-gauge"}),a=s.createEl("div",{cls:"gauge-track"}),n=4,i=Math.max(1,Math.min(n,Math.round(t)));for(let c=1;c<=n;c++){let l=a.createEl("div",{cls:"gauge-segment"});c<=i&&(l.addClass("filled"),l.addClass(`level-${c}`))}if(s.createEl("div",{cls:"gauge-level",text:`Level ${i} / ${n}`}),i0){let l=s.createEl("ul",{cls:"gauge-blockers"});for(let h of c)l.createEl("li",{text:h})}}}_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"}],s=e.createEl("div",{cls:"paperforge-bar-chart"}),a=Math.max(1,...r.map(n=>t[n.key]||0));for(let n of r){let i=t[n.key]||0,c=i/a*100,l=s.createEl("div",{cls:"bar-row"});l.createEl("div",{cls:"bar-label",text:n.label}),l.createEl("div",{cls:"bar-track"}).createEl("div",{cls:`bar-fill ${n.cls}`,attr:{style:`width:${c.toFixed(1)}%`}}),l.createEl("div",{cls:"bar-count",text:i.toString()})}}_invalidateIndex(){this._cachedItems=null}_extractZoteroKeyFromPath(e){return Dt(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),n=a&&a.frontmatter&&a.frontmatter.zotero_key;if(n)return{mode:"paper",filePath:r,key:n,domain:null}}if(t==="pdf"){let a=this._getCachedIndex();for(let n of a){let i=(n.pdf_path||"").match(/\[\[([^\]]+)\]\]/);if((i?i[1]:n.pdf_path)===r)return{mode:"paper",filePath:r,key:n.zotero_key,domain:null}}}let s=this._extractZoteroKeyFromPath(r);return s?{mode:"paper",filePath:r,key:s,domain:null}:{mode:"global",filePath:r,key:null,domain:null}}_detectAndSwitch(){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 ne,Q,ce,te,q,C,Y;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,s=0,a=0,n=0;for(let T of t)T.has_pdf&&s++,T.ocr_status==="done"&&a++,T.deep_reading_status==="done"&&n++;let i=e.createEl("div",{cls:"paperforge-library-snapshot"});i.createEl("div",{cls:"paperforge-section-label",text:"Library Snapshot"});let c=i.createEl("div",{cls:"paperforge-snapshot-pills"}),l=[{value:r,label:"papers"},{value:s,label:"PDFs ready"},{value:a,label:"OCR done"},{value:n,label:"deep-read done"}];for(let T of l){let I=c.createEl("div",{cls:"paperforge-snapshot-pill"});I.createEl("span",{cls:"paperforge-snapshot-value",text:String(T.value)}),I.createEl("span",{cls:"paperforge-snapshot-label",text:" "+T.label})}let h=e.createEl("div",{cls:"paperforge-system-status"});h.createEl("div",{cls:"paperforge-section-label",text:"System Status"});let _=h.createEl("div",{cls:"paperforge-status-grid"}),p=this.app.plugins.plugins.paperforge,g=((ne=p==null?void 0:p.manifest)==null?void 0:ne.version)||"?",y=this._paperforgeVersion;if(!y)try{let T=this.app.vault.adapter.basePath,{path:I,extraArgs:re=[]}=z(T,(Q=p==null?void 0:p.settings)!=null?Q:null,void 0,void 0),D=(0,ve.execFileSync)(I,[...re,"-c","import paperforge; print(paperforge.__version__)"],{cwd:T,timeout:5e3,encoding:"utf-8",windowsHide:!0}).trim();D&&(y=D.startsWith("v")?D:"v"+D,this._paperforgeVersion=y)}catch(T){}y=y||"\u2014";let E=y==="v"+g;this._renderSystemStatusRow(_,"Runtime",E?"healthy":"mismatch",E?"v"+g:"plugin v"+g+" \u2260 CLI "+y);let f=this._loadIndex(),b=f&&f.items&&f.items.length>0;this._renderSystemStatusRow(_,"Index",b?"healthy":"missing",b?f.items.length+" entries":"formal-library.json not found");let w=((ce=p==null?void 0:p.settings)==null?void 0:ce.system_dir)||"System",x=this.app.vault.adapter.basePath,m=!1,v="No exports found";try{let T=Ne.join(x,w,"PaperForge","exports");if(ye.existsSync(T)){let I=ye.readdirSync(T).filter(re=>re.endsWith(".json"));m=I.length>0,v=m?I.length+" export(s)":"No JSON exports"}}catch(T){}this._renderSystemStatusRow(_,"Zotero Export",m?"healthy":"missing",v);let k=!!((te=p==null?void 0:p.settings)!=null&&te.paddleocr_api_key);if(!k)try{let T=((q=p==null?void 0:p.settings)==null?void 0:q.system_dir)||"System",I=Ne.join(x,T,"PaperForge",".env");if(ye.existsSync(I)){let D=ye.readFileSync(I,"utf-8").match(/^PADDLEOCR_API_TOKEN\s*=\s*(.+)$/m);k=!!(D&&D[1]&&D[1].trim())}}catch(T){}k||(k=!!(process.env.PADDLEOCR_API_TOKEN||process.env.PADDLEOCR_API_KEY||process.env.OCR_TOKEN)),this._renderSystemStatusRow(_,"OCR Token",k?"configured":"missing",k?"Configured":"Not set");let F=!1,P="",O=this.app.vault.adapter.basePath,B=Ae(O);F=Et(O),P=B&&((C=B.summary)==null?void 0:C.reason)||B&&((Y=B.summary)==null?void 0:Y.status)||"Unknown",this._renderSystemStatusRow(_,"Memory Layer",F?"healthy":"fail",P);let j=!E&&y!=="\u2014";if(j||!b||!m||!k){let T=e.createEl("div",{cls:"paperforge-issue-summary"});T.createEl("div",{cls:"paperforge-section-label",text:"\u9700\u8981\u5904\u7406"});let I=T.createEl("div",{cls:"paperforge-issue-list"});j&&I.createEl("div",{cls:"paperforge-issue-item",text:"Runtime version mismatch"}),b||I.createEl("div",{cls:"paperforge-issue-item",text:"Index missing or corrupted"}),m||I.createEl("div",{cls:"paperforge-issue-item",text:"No Zotero export found"}),k||I.createEl("div",{cls:"paperforge-issue-item",text:"PaddleOCR API key not configured"});let re=T.createEl("div",{cls:"paperforge-issue-actions"}),D=re.createEl("button",{cls:"paperforge-contextual-btn"});D.createEl("span",{text:"Run Doctor"}),D.addEventListener("click",()=>{let de=ie.find(be=>be.id==="paperforge-doctor");de&&this._runAction(de,D)});let se=re.createEl("button",{cls:"paperforge-contextual-btn"});se.createEl("span",{text:"Repair Issues"}),se.addEventListener("click",()=>{let de=ie.find(be=>be.id==="paperforge-repair");de&&this._runAction(de,se)})}let A=e.createEl("div",{cls:"paperforge-global-actions"});A.createEl("div",{cls:"paperforge-section-label",text:"Start Working"});let S=A.createEl("div",{cls:"paperforge-global-actions-row"}),N=S.createEl("button",{cls:"paperforge-contextual-btn primary"});N.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u{1F4C1}"}),N.createEl("span",{text:"Open Literature Hub"}),N.addEventListener("click",()=>{var re;let T=((re=p==null?void 0:p.settings)==null?void 0:re.base_dir)||"Bases",I=this.app.vault.getAbstractFileByPath(T);if(I){let D=null;if(I.children&&(D=I.children.find(se=>se.extension==="base")),D){let se=this.app.workspace.getLeaf(!1);se&&se.openFile(D)}else new M.Notice("[!!] No .base file found in "+T,6e3)}else new M.Notice("[!!] Base directory not found: "+T,6e3)});let U=S.createEl("button",{cls:"paperforge-contextual-btn"});U.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u21BB"}),U.createEl("span",{text:"Sync Library"}),U.addEventListener("click",()=>{let T=ie.find(I=>I.id==="paperforge-sync");T&&this._runAction(T,U)});let $=S.createEl("button",{cls:"paperforge-contextual-btn"});$.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u229E"}),$.createEl("span",{text:"Run OCR"}),$.addEventListener("click",()=>{let T=ie.find(I=>I.id==="paperforge-ocr");T&&this._runAction(T,$)});let L=S.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 T=ie.find(I=>I.id==="paperforge-ocr-redo");T&&this._runAction(T,L)})}_renderSystemStatusRow(e,t,r,s){let a=e.createEl("div",{cls:"paperforge-status-row"});a.createEl("span",{cls:"paperforge-status-dot"}).addClass(r==="healthy"||r==="configured"?"ok":"fail"),a.createEl("span",{cls:"paperforge-status-label",text:t}),a.createEl("span",{cls:"paperforge-status-detail",text:s||""})}_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"}),s=r.createEl("div",{cls:"paperforge-paper-header"});s.createEl("div",{cls:"paperforge-paper-title pf-copy",text:e.title||"Untitled"}).addEventListener("click",()=>{navigator.clipboard.writeText(e.title||""),new M.Notice("Title copied")});let n=s.createEl("div",{cls:"paperforge-paper-meta"});e.authors&&e.authors.length>0&&n.createEl("span",{cls:"paperforge-paper-authors",text:e.authors.join(", ")}),e.year&&n.createEl("span",{cls:"paperforge-paper-year",text:String(e.year)});let i=r.createEl("div",{cls:"paperforge-status-strip"}),c=i.createEl("div",{cls:"paperforge-status-strip-left"}),l=i.createEl("div",{cls:"paperforge-status-strip-right"}),h=[{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 p of h){let g=c.createEl("span",{cls:"paperforge-status-pill"}),y="pending";p.ok?y="ok":p.fail?y="fail":p.pending&&(y="pending"),g.addClass(y);let E=p.ok?"\u2713":p.fail?"\u2717":"\u25CB";g.createEl("span",{cls:"paperforge-status-pill-icon",text:E}),g.createEl("span",{text:" "+p.label})}if(e.pdf_path){let p=l.createEl("button",{cls:"paperforge-contextual-btn"});p.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u{1F4C4}"}),p.createEl("span",{text:"\u6253\u5F00 PDF"}),p.addEventListener("click",()=>{let g=e.pdf_path.match(/\[\[([^\]]+)\]\]/),y=g?g[1]:e.pdf_path;this.app.vault.getAbstractFileByPath(y)?this.app.workspace.openLinkText(y,""):new M.Notice("[!!] PDF not found: "+y,6e3)})}if(e.fulltext_path){let p=l.createEl("button",{cls:"paperforge-contextual-btn"});p.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u{1F4DD}"}),p.createEl("span",{text:"\u6253\u5F00\u5168\u6587"}),p.addEventListener("click",()=>this._openFulltext(e.fulltext_path))}let _=l.createEl("button",{cls:"paperforge-contextual-btn"});if(_.createEl("span",{text:o("version_panel_title")}),_.addEventListener("click",()=>{this._switchToVersionMode(t)}),this._renderPaperOverviewCard(r,e),e.next_step==="ready"&&e.deep_reading_status==="done"){let p=r.createEl("div",{cls:"paperforge-complete-row"});p.createEl("span",{text:"\u2713"}),p.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"}),n=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(c=>{let l=this._extractOverviewFromNote(c);if(l){let h=l.length>200?l.slice(0,200)+"...":l;if(n.setText(h),l.length>200){let _=a.createEl("div",{cls:"paperforge-expand-container"}),p=_.createEl("button",{cls:"paperforge-expand-icon",title:"\u5C55\u5F00/\u6536\u8D77"});p.innerHTML='';let g=!1;_.addEventListener("click",()=>{n.setText(g?h:l),p.innerHTML=g?'':'',g=!g})}}else n.setText("\u5C1A\u672A\u751F\u6210\u6587\u7AE0\u6982\u89C8\u3002\u8FD0\u884C /pf-deep \u5F00\u59CB\u7CBE\u8BFB\u3002")}).catch(()=>{n.setText("\u65E0\u6CD5\u8BFB\u53D6\u7B14\u8BB0\u5185\u5BB9")}):n.setText("\u7B14\u8BB0\u6587\u4EF6\u4E0D\u5B58\u5728")}else n.setText("\u5C1A\u672A\u751F\u6210\u6587\u7AE0\u6982\u89C8")}_extractOverviewFromNote(e){if(!e)return null;let t=e.indexOf("## \u{1F50D} \u7CBE\u8BFB");if(t===-1)return null;let r=e.slice(t),s=["**\u4E00\u53E5\u8BDD\u603B\u89C8:**","**\u4E00\u53E5\u8BDD\u603B\u89C8**","**\u6587\u7AE0\u6458\u8981:**","**\u6587\u7AE0\u6458\u8981**"];for(let i of s){let c=r.indexOf(i);if(c!==-1){let l=r.slice(c+i.length),h=["**5 Cs","**Figure","**\u8BC1\u636E","### Pass 2","## "],_=l.length;for(let y of h){let E=l.indexOf(y);E!==-1&&E<_&&(_=E)}let p=l.indexOf(`
+ `,document.head.appendChild(a)}let t=e.createDiv({cls:"paperforge-settings-tabs"}),r=[{id:"setup",label:o("tab_setup")||"\u5B89\u88C5"},{id:"features",label:o("tab_features")||"\u529F\u80FD"},{id:"maintenance",label:o("tab_maintenance")||"\u7EF4\u62A4"},{id:"release-notes",label:"\u66F4\u65B0\u4E0E\u624B\u518C"}],n={};r.forEach(a=>{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==="setup"?this._renderSetupTab(n.setup):this.activeTab==="features"?this._renderFeaturesTab(n.features):this.activeTab==="maintenance"?this._renderMaintenanceTab(n.maintenance):this._renderReleaseNotesTab(n["release-notes"])}_renderSetupTab(e){var r;let t=this.app.vault.adapter.basePath;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);for(let n of Se){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))}}_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,c=i.fresh?"fresh":"stale";r(`Papers: ${i.paper_count_db} | ${c}${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=he(r,this.plugin.settings),a=[...n.extraArgs,"-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,c)=>{t&&t.onClose&&t.onClose(s?1:0,i,c)}),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=he(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:(c,l,_)=>{console.log("[PaperForge] memory build exit:",c?"FAIL:"+c:"OK",(l||"").slice(0,200),(_||"").slice(0,200)),n.setText(o("feat_memory_rebuild_btn")),n.removeAttribute("disabled"),c===0?new F.Notice(o("feat_memory_rebuild_done")):new F.Notice(o("feat_memory_rebuild_failed")+(_?" "+_.slice(0,80):"")),this._memoryStatusText=Ue(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(!he(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),Ze(this.app,this.plugin,e)}})}_refreshSnapshots(e){let t=he(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=Ue(e),this._embedStatusText=Re(e),this.display()})}_renderFeaturesTab(e){e.createEl("h3",{text:"Skills"});let t=e.createEl("div",{cls:"paperforge-desc-box"});t.setText(o("feat_skills_desc")),t.createEl("br"),t.createEl("span",{text:o("feat_skills_system")});let r={opencode:"OpenCode",claude:"Claude Code",codex:"Codex",cursor:"Cursor",windsurf:"Windsurf",github_copilot:"GitHub Copilot",gemini:"Gemini CLI"},n={opencode:".opencode/skills",claude:".claude/skills",codex:".codex/skills",cursor:".cursor/skills",windsurf:".windsurf/skills",github_copilot:".github/skills",gemini:".gemini/skills"},a=this.app.vault.adapter.basePath,s=this.plugin.settings.selected_skill_platform||"opencode";new F.Setting(e).setName(o("feat_agent_platform")).setDesc(o("feat_agent_platform_desc")).addDropdown(y=>{Object.entries(r).forEach(([E,w])=>y.addOption(E,w)),y.setValue(s).onChange(E=>{this.plugin.settings.selected_skill_platform=E,this.plugin.saveSettings(),this.display()})}).addExtraButton(y=>{y.setIcon("folder").setTooltip("Open skills folder").onClick(()=>{let E=n[s]||".opencode/skills",w=te.join(a,E);W.existsSync(w)?(0,G.exec)(`start "" "${w}"`):new F.Notice(`Skills folder not found: ${E}`)})});let i=te.join(a,n[s]),c=[],l=[];W.existsSync(i)&&W.readdirSync(i,{withFileTypes:!0}).forEach(y=>{if(!y.isDirectory())return;let E=te.join(i,y.name,"SKILL.md");if(!W.existsSync(E))return;let w=W.readFileSync(E,"utf-8"),D=w.match(/^name:\s*(.+)$/m),T=w.split(`
+`),I=T.findIndex(N=>/^description:/.test(N)),B="";if(I>=0){let N=T[I].match(/^description:\s*(.+)$/);if(N&&N[1]&&N[1]!==">"&&N[1]!=="|-"&&N[1]!=="|")B=N[1].trim();else{for(let q=I+1;q{if(E.length===0)return;let D=_.createEl("div",{cls:"paperforge-skills-group"}),T=D.createEl("div",{cls:"paperforge-skills-collapse-header"}),I=D.createEl("div",{cls:"paperforge-skills-collapse-content"}),B=T.createEl("span",{text:"\u25BC",cls:"paperforge-skills-arrow"});T.createEl("h4",{text:`${y} (${E.length})`,cls:"paperforge-skills-subheader"}),E.forEach(A=>{let S=A.name+(A.version?" v"+A.version:""),N=w?" [system]":" [user]",q=A.desc||"",j=new F.Setting(I).setName(S+N).setDesc(q);j.settingEl.style.opacity=A.disabled?"0.4":"1",j.addToggle(O=>{O.setValue(!A.disabled).onChange(se=>{let Q=!se,ee=A.content.match(/^disable-model-invocation:\s*(.+)$/m)?A.content.replace(/^disable-model-invocation:\s*.+$/m,`disable-model-invocation: ${Q}`):A.content.replace(/^(---\r?\n)/,`$1disable-model-invocation: ${Q}
+`);W.writeFileSync(A.path,ee,"utf-8"),A.disabled=Q,A.content=ee,j.settingEl.style.opacity=A.disabled?"0.4":"1"})})});let $=w?"system":"user";(this._skillsCollapsed[$]||!1)&&(I.style.display="none",B.style.transform="rotate(-90deg)"),T.addEventListener("click",()=>{I.style.display!=="none"?(I.style.display="none",B.style.transform="rotate(-90deg)"):(I.style.display="",B.style.transform="rotate(0deg)"),this._skillsCollapsed[$]=I.style.display==="none"})};f("System Skills",c,!0),f("User Skills",l,!1),c.length===0&&l.length===0&&_.createEl("p",{text:`No skills found in ${n[s]}. Run setup to deploy skills.`,cls:"setting-item-description"}),this._advCollapsed===void 0&&(this._advCollapsed=!0);let u=e.createEl("div",{cls:"paperforge-collapsible-header"}),g=u.createEl("span",{text:"\u25B6",cls:"paperforge-collapsible-arrow"});g.style.transform=this._advCollapsed?"rotate(0deg)":"rotate(90deg)";let m=u.createEl("span",{cls:"paperforge-collapsible-title",text:"Advanced"}),b=u.createEl("span",{cls:"paperforge-collapsible-sub",text:"Memory + Vector DB + Embedding"}),h=e.createEl("div",{cls:"paperforge-collapsible-content"});h.style.display=this._advCollapsed?"none":"",u.addEventListener("click",()=>{this._advCollapsed=!this._advCollapsed,h.style.display=this._advCollapsed?"none":"",g.style.transform=this._advCollapsed?"rotate(0deg)":"rotate(90deg)"}),h.createEl("h4",{text:"Memory Layer"}),h.createEl("div",{cls:"paperforge-desc-box"}).setText(o("feat_memory_desc"));let x=h.createEl("div",{cls:"paperforge-memory-status"}),k=this.app.vault.adapter.basePath;this.plugin._lastSyncTime&&!this._lastSyncTime&&(this._lastSyncTime=this.plugin._lastSyncTime),this._memoryStatusText===null&&(this._memoryStatusText=Ue(k)),this._renderMemoryStatusText(x,this._memoryStatusText,this._lastSyncTime),this._renderVectorSection(h)}_renderVectorSection(e){var c;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(l=>{l.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=l=>{s.style.display=l?"none":"",a.style.transform=l?"rotate(-90deg)":"rotate(0deg)"};if(i(_t(this._featurePanelsCollapsed,"vectorConfig",!1)),n.addEventListener("click",()=>{let l=Ot(this._featurePanelsCollapsed,"vectorConfig",!1);i(l)}),this._vectorDepsOk===!0){this._renderVectorReady(s,r);return}if(this._vectorDepsOk===!1){this._renderVectorNoDeps(s);return}if(this._vectorDepsOk===null){let l=We(r);this._vectorDepsOk=l&&(c=l.deps_installed)!=null?c:!1,this._vectorDepsOk&&(this._embedStatusText=Re(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=he(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 c=Object.assign({},process.env,{PYTHONIOENCODING:"utf-8",PYTHONUTF8:"1"}),l=s.split(" ");await new Promise((_,f)=>{(0,G.execFile)(a.path,[...a.extraArgs,"-m","pip","install",...l],{cwd:n,timeout:3e5,env:c,windowsHide:!0},u=>{u?f(u):_()})}),i.hide(),new F.Notice(o("feat_install_done")),this._vectorDepsOk=!0,this._embedStatusText=Re(n),this.display()}catch(c){i.hide(),new F.Notice(o("feat_install_failed")+(c.stderr||c.message||c)),r.setButtonText(o("feat_retry_btn")),r.setDisabled(!1)}})})}_renderVectorReady(e,t){e.createEl("div",{cls:"paperforge-desc-box"}).setText(Re(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 l=We(t),_=l==null?void 0:l.build_state,f=_&&typeof _=="object"&&!Array.isArray(_)?_:{};this.plugin._embedProgress=this.plugin._embedProgress||{current:0,total:0,key:""},!this.plugin._embedProcess&&f.status==="running"&&(this.plugin._embedProgress={current:typeof f.current=="number"?f.current:0,total:typeof f.total=="number"?f.total:1,key:typeof f.paper_id=="string"?f.paper_id:""});let{current:u,total:g,key:m}=this.plugin._embedProgress,b=typeof(l==null?void 0:l.body_chunk_count)=="number"?l.body_chunk_count:0,h=typeof(l==null?void 0:l.object_chunk_count)=="number"?l.object_chunk_count:0,x=(typeof(l==null?void 0:l.chunk_count)=="number"?l.chunk_count:0)+b+h,k=x>0,y=l!==null&&typeof l.corrupted=="boolean"&&l.corrupted,E=!!this.plugin._embedProcess,w=!this.plugin._embedProcess&&f.status==="running",D=(l==null?void 0:l.deps_installed)!==void 0?!!l.deps_installed:!0,T=typeof f.status=="string"?f.status:"",I=typeof f.message=="string"?f.message:"",B=S=>{var j;if(S==="--resume"&&k&&!y){let O=o("retrieval_rebuild_warning").replace("{n}",String(x));if(!confirm(O))return}if(S==="--force"&&k&&!y){let O="Force rebuild will replace "+x+" existing chunk(s). Continue?";if(!confirm(O))return}if(!he(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",S],{stream:!0,env:q,onData:O=>{var ee;let se=typeof O=="string"?O:Buffer.isBuffer(O)?O.toString("utf-8"):String(O),{events:Q,buffer:ce}=yt(se,(ee=this.plugin._embedBuffer)!=null?ee:"");this.plugin._embedBuffer=ce;for(let Z of Q)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:O=>{this.plugin._embedStderr||(this.plugin._embedStderr=""),this.plugin._embedStderr+=String(O)},onError:O=>{this.plugin._embedProcess=null,new F.Notice(o("feat_build_failed")+": "+(O.message||O)),this.display()},onClose:O=>{var se;if(clearInterval((se=this.plugin._embedPollInterval)!=null?se:void 0),this.plugin._embedPollInterval=null,this.plugin._embedProcess=null,O===0)this.plugin._embedProgress.current=this.plugin._embedProgress.total,this.plugin.saveSettings(),this._embedStatusText=Re(t),new F.Notice(o("feat_build_complete"));else{this._embedStatusText=null;let Q=(this.plugin._embedStderr||"").slice(0,200);new F.Notice(o("feat_build_failed")+(Q?": "+Q:""),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:(O,se)=>{var Q;if(this.plugin._embedPolling=!1,O===0&&se)try{let ee=JSON.parse(se).data;if(ee&&ee.build_state){let Z=ee.build_state;(Z.status==="stopping"||Z.status==="idle")&&this.plugin._embedProcess&&(this.plugin._embedProcess=null,clearInterval((Q=this.plugin._embedPollInterval)!=null?Q: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(ce){}}}))},2e3),this.display()},$=Me(t),V=!1;$&&typeof $.summary=="object"&&$.summary!==null&&"status"in $.summary&&(V=$.summary.status==="version_mismatch");let A;switch(D?V?A="runtime-mismatch":T==="stopping"?A="stopping":E&&T==="running"?A="building":T==="failed"?A="failed":T==="stopped"?A="stopped":w?A="stale":y?A="corrupted":k?A="ready":A="idle":A="deps-missing",A){case"building":{let S=s.createEl("div",{cls:"paperforge-progress-track"});S.style.cssText="flex:1;";let N=g>0?(u/g*100).toFixed(1):"0",q=S.createEl("div",{cls:"paperforge-progress-seg done"});if(q.style.cssText=`width:${N}%; min-width:${u>0?"2px":"0"};`,u{this._callPython(["embed","stop","--json"],{timeout:8e3}),this.display()});break}case"stopping":{let S=s.createEl("div",{cls:"paperforge-progress-track"});S.style.cssText="flex:1; opacity:0.5;";let N=g>0?(u/g*100).toFixed(1):"0",q=S.createEl("div",{cls:"paperforge-progress-seg done"});if(q.style.cssText=`width:${N}%; min-width:${u>0?"2px":"0"};`,uB("--resume"));let N=s.createEl("button");N.setText(o("retrieval_force_rebuild")),N.style.marginLeft="6px",N.addEventListener("click",()=>B("--force"));break}case"stopped":{i.setText(o("retrieval_build_stopped"));let S=s.createEl("button");S.setText(o("retrieval_retry")),S.className="mod-cta",S.addEventListener("click",()=>B("--resume"));break}case"corrupted":{i.createEl("div",{cls:"paperforge-desc-box",text:o("feat_vector_corrupted"),attr:{style:"background:var(--background-modifier-warning);"}});let S=s.createEl("button");S.setText(o("retrieval_force_rebuild")),S.className="mod-cta",S.addEventListener("click",()=>B("--force"));break}case"stale":{i.createEl("div",{cls:"paperforge-desc-box",text:o("retrieval_build_stale"),attr:{style:"color:var(--text-warning);"}});let S=s.createEl("button");S.setText(o("retrieval_rebuild_vectors")),S.className="mod-cta",S.addEventListener("click",()=>B("--resume"));break}case"ready":{s.createEl("span",{text:x+" chunks embedded",cls:"setting-item-description"});let S=s.createEl("button");S.setText(o("retrieval_rebuild_vectors")),S.className="mod-cta",S.addEventListener("click",()=>B("--resume"));let N=s.createEl("button");N.setText(o("retrieval_force_rebuild")),N.style.marginLeft="6px",N.addEventListener("click",()=>B("--force"));break}case"deps-missing":{i.setText(o("retrieval_build_deps_missing"));let S=s.createEl("a");S.setText(o("feat_install_deps")),S.style.cssText="cursor:pointer; text-decoration:underline;",S.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 S=s.createEl("a");S.setText(o("runtime_health_sync")),S.style.cssText="cursor:pointer; text-decoration:underline;",S.addEventListener("click",()=>{this.display()});break}case"idle":default:{i.setText(o("retrieval_build_idle"));let S=s.createEl("button");S.setText(o("feat_build_btn")),S.className="mod-cta",S.addEventListener("click",()=>B("--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 c="\u65E0\u6CD5\u8FD0\u884C / Cannot run";t&&(t.innerHTML=`\u2717 ${c}`),new F.Notice(c,4e3);return}let a=n.match(/Python (\d+)\.(\d+)/);if(!a){let c="\u65E0\u6CD5\u89E3\u6790\u7248\u672C / Cannot parse version";t&&(t.innerHTML=`\u2717 ${c}`),new F.Notice(c,4e3);return}let s=parseInt(a[1],10),i=parseInt(a[2],10);if(s<3||s===3&&i<10){let c="Python \u7248\u672C\u8FC7\u4F4E\uFF0C\u9700\u8981 3.10+ / Python version too low, need 3.10+";t&&(t.innerHTML=`\u2717 ${c}`),new F.Notice(c,4e3);return}(0,G.execFile)(e,["-m","pip","--version"],{timeout:8e3},c=>{if(c){let l=`\u2713 Python ${s}.${i} \u6709\u6548\uFF0C\u4F46\u672A\u68C0\u6D4B\u5230 pip / Valid, but pip not found`;t&&(t.innerHTML=`\u26A0 ${l}`),new F.Notice(l,4e3)}else{let l=`\u2713 Python ${s}.${i} \u6709\u6548 / Valid`;t&&(t.innerHTML=`${l}`),new F.Notice(l,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=Tt(r,a,n);e.setDisabled(!0),e.setButtonText(o("runtime_health_syncing"));let i=(l,_)=>(console.log(`[PaperForge] Sync Runtime: trying ${_}`),At(s.cmd,l,t,s.timeout,void 0,we())),c=()=>{let l="opencode";try{let g=W.readFileSync(te.join(t,"paperforge.json"),"utf-8"),m=JSON.parse(g);m.agent_key&&(l=m.agent_key)}catch(g){}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="'+l+'", overwrite=True); print("skills deployed" if r["skill_deployed"] else "skills skipped", flush=True)'],f=(0,G.spawn)(r,_,{cwd:t,timeout:3e4,windowsHide:!0}),u="";f.stdout.on("data",g=>{u+=g.toString("utf-8")}),f.on("close",g=>{console.log(`[PaperForge] Skill deploy: ${u.trim()} (exit ${g})`)})};i(s.pypiArgs,"PyPI").then(l=>{if(l.exitCode===0){console.log("[PaperForge] Sync Runtime: installed via PyPI"),c(),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"),c(),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 c=[];c.push({label:"Python",ok:!s,detail:s?o("check_python_fail"):i.trim()});let l=!1,_=process.env.HOME||process.env.USERPROFILE||zt.homedir()||"";if(process.platform==="darwin")l=["/Applications/Zotero.app",te.join(_,"Applications","Zotero.app")].some(v=>{try{return W.existsSync(v)}catch(x){return!1}});else if(process.platform==="win32"){let h=process.env.ProgramFiles||"",v=process.env.LOCALAPPDATA||"";l=[te.join(h,"Zotero"),te.join(h,"(x86)","Zotero"),te.join(v,"Programs","Zotero"),te.join(v,"Zotero"),te.join(_,"AppData","Local","Programs","Zotero")].filter(Boolean).some(k=>{try{return W.existsSync(k)}catch(y){return!1}})}else l=[te.join(_,".local","share","zotero","zotero"),"/usr/bin/zotero","/usr/local/bin/zotero"].some(v=>{try{return W.existsSync(v)}catch(x){return!1}});let f=this.plugin.settings.zotero_data_dir;if(!l&&f)try{l=W.existsSync(f)}catch(h){}c.push({label:"Zotero",ok:l,detail:l?o("check_zotero_ok"):o("check_zotero_fail")});let u=!1,g=process.env.APPDATA||"";process.platform==="win32"&&g&&(u=Ke(te.join(g,"Zotero","Zotero","Profiles"))),!u&&process.platform==="darwin"&&_&&(u=Ke(te.join(_,"Library","Application Support","Zotero","Profiles"))),!u&&process.platform!=="win32"&&process.platform!=="darwin"&&_&&(u=Ke(te.join(_,".zotero","zotero","Profiles"))),!u&&f&&String(f).trim()&&(u=pt(f.trim())),!u&&_&&(u=pt(te.join(_,"Zotero"))),c.push({label:"Better BibTeX",ok:u,detail:u?o("check_bbt_ok"):o("check_bbt_fail")});let m={true:"\u2713",false:"\u2717"};if(this._checkEl){this._checkEl.setText(c.map(v=>`${m[String(v.ok)]} ${v.label}: ${v.detail}`).join(`
+`));let h=c.some(v=>!v.ok);this._checkEl.className=`paperforge-message msg-${h?"error":"ok"}`}let b=c.filter(h=>!h.ok);b.length>0&&new F.Notice(`[!!] \u672A\u901A\u8FC7: ${b.map(h=>h.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=Ge(r)}catch(f){}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 c=()=>!!this.plugin._ocrProcess,l=f=>{n.empty();let u=f,g=n.createEl("div",{cls:"pf-maint-filters"}),m=g.createEl("button",{cls:"pf-maint-filter"+(a.active==="all"?" active":""),text:o("maintenance_filter_all")||"All"});m.addEventListener("click",()=>{a.active="all",l(f)});let b=g.createEl("button",{cls:"pf-maint-filter"+(a.active==="recommended"?" active":""),text:o("maintenance_filter_recommended")||"Recommended"});b.addEventListener("click",()=>{a.active="recommended",l(f)});let h=a.active==="recommended"?u.filter(v=>v.needs_derived_rebuild===!0):u;if(h.length===0)n.createEl("p",{text:"\u5F53\u524D\u7B5B\u9009\u6761\u4EF6\u4E0B\u65E0\u6570\u636E",cls:"setting-item-description"});else{let v=i.path,x=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 E=y.createEl("div",{cls:"paperforge-progress-seg done"}),w=y.createEl("div",{cls:"paperforge-progress-seg pending"}),D=k.createEl("span",{cls:"pf-maint-progress-text"}),T=k.createEl("span",{cls:"pf-maint-progress-key"}),I=k.createEl("button",{text:o("maintenance_stop")||"Stop"});I.className="mod-warning",I.addEventListener("click",()=>{let P=this.plugin._ocrProcess;P&&(P.stdin&&typeof P.stdin.write=="function"?P.stdin.write(`PAPERFORGE_STOP
+`):typeof P.kill=="function"&&P.kill("SIGINT")),this.plugin._ocrWasStopped=!0,I.disabled=!0,I.textContent=(o("maintenance_stop")||"Stop")+"\u2026"});let B=()=>{let P=this.plugin._ocrProgress;if(!P||P.total===0||!this.plugin._ocrProcess){k.style.display="none";return}k.style.display="flex";let X=P.total>0?(P.current/P.total*100).toFixed(1):"0";E.style.width=`${X}%`,E.style.minWidth=P.current>0?"2px":"0",P.current{let X=document.createElement("th");X.textContent=P,q.appendChild(X)});let j=c();for(let P of h){let X=N.insertRow(),C=X.insertCell();C.style.cssText="padding:3px 4px;text-align:center;width:24px;";let L=document.createElement("input");L.type="checkbox",L.className="pf-maint-sel",L.checked=$.get(P.key)||!1,L.addEventListener("change",()=>{$.set(P.key,L.checked),Q()}),C.appendChild(L);let re=X.insertCell();re.style.cssText="padding:3px 4px;";let R=re.createEl("div",{cls:"pf-maint-paper-info"});R.createEl("div",{cls:"pf-maint-paper-title",text:P.title||P.key}),R.createEl("div",{cls:"pf-maint-paper-key",text:P.key});let ne=X.insertCell();ne.style.cssText="padding:3px 4px;",ne.createEl("div",{cls:"pf-maint-reason",text:P.display_reason||""});let de=X.insertCell();de.style.cssText="padding:3px 4px;white-space:nowrap;";let be=de.createEl("div",{cls:"pf-maint-actions"}),De=ht(P);if(De==="rebuild"){let oe=be.createEl("button",{cls:"pf-maint-action-btn rebuild",text:o("maintenance_btn_rebuild")||"Rebuild"});j&&(oe.disabled=!0),oe.addEventListener("click",()=>{(0,G.execFile)(v,[...x,"-m","paperforge","ocr","rebuild",P.key],{cwd:r,timeout:12e4,windowsHide:!0},()=>{new F.Notice((o("maintenance_btn_rebuild")||"Rebuild")+" \u2014 "+P.key)})})}else if(De==="redo"){let oe=be.createEl("button",{cls:"pf-maint-action-btn redo",text:o("ocr_maint_redo_btn")||"Redo"});j&&(oe.disabled=!0),oe.addEventListener("click",()=>{gt("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)(v,[...x,"-m","paperforge","ocr","redo",P.key],{cwd:r,timeout:3e5,windowsHide:!0},()=>{new F.Notice((o("ocr_maint_redo_btn")||"Redo OCR")+" \u2014 "+P.key)})})}}let O=n.createEl("div",{cls:"pf-maint-batch-bar"}),se=O.createEl("span",{cls:"pf-maint-batch-label",text:"0 selected"}),Q=()=>{let P=h.filter(X=>$.get(X.key)).length;se.textContent=P+" selected"},ce=O.createEl("button",{cls:"mod-cta",text:o("maintenance_batch_rebuild")||"\u25B6 Rebuild selected"});ce.disabled=j;let ee=O.createEl("button",{cls:"mod-cta",text:o("maintenance_batch_redo")||"\u25B6 Full OCR redo selected"});ee.disabled=j;let Z=P=>{let X=h.filter(R=>$.get(R.key)&&ht(R)===P);if(X.length===0){let R=P==="rebuild"?o("maintenance_btn_rebuild")||"Rebuild":o("ocr_maint_redo_btn")||"Redo";new F.Notice("Selected papers are not eligible for "+R+". Uncheck ineligible rows and try again.",6e3);return}if(gt(P)&&!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 C=X.map(R=>R.key);this.plugin._ocrProgress={current:0,total:C.length,key:""},this.plugin._ocrBuffer="",this.plugin._ocrWasStopped=!1;let L=P==="rebuild"?"OCR_REBUILD":"OCR_REDO";ce.disabled=!0,ee.disabled=!0,Array.from(V.querySelectorAll(".pf-maint-action-btn")).forEach(R=>{R.disabled=!0}),Array.from(V.querySelectorAll(".pf-maint-sel")).forEach(R=>{R.disabled=!0}),m.disabled=!0,b.disabled=!0,I.disabled=!1,I.textContent=o("maintenance_stop")||"Stop";let re=this._callPython(["ocr",P,...C],{stream:!0,onData:R=>{var De;let ne=typeof R=="string"?R:Buffer.isBuffer(R)?R.toString("utf-8"):String(R),{events:de,buffer:be}=yt(ne,(De=this.plugin._ocrBuffer)!=null?De:"");this.plugin._ocrBuffer=be;for(let oe of de)oe.event==="START"?this.plugin._ocrProgress&&(this.plugin._ocrProgress.total=oe.total||C.length):oe.event==="PROGRESS"&&(this.plugin._ocrProgress={current:oe.current||0,total:oe.total||C.length,key:oe.key||""});B()},onError:R=>{this.plugin._ocrProcess=null,new F.Notice("Batch error: "+(R.message||R)),l(f)},onClose:R=>{this.plugin._ocrWasStopped||R===130?(this.plugin._ocrWasStopped=!1,this.plugin._ocrProcess=null,B(),new F.Notice("OCR batch stopped by user.")):R===0?(this.plugin._ocrProgress&&(this.plugin._ocrProgress.current=this.plugin._ocrProgress.total),this.plugin._ocrProcess=null,B(),new F.Notice((o("maintenance_batch_complete")||"Batch operation complete \u2014 {n} papers processed.").replace("{n}",String(C.length)))):(this.plugin._ocrProcess=null,B(),new F.Notice("Batch operation finished with exit code "+R+".",8e3)),mt(r,v,x,s).then(ne=>{s=Ge(r),l(ne.data)}).catch(()=>{l(u)})}});this.plugin._ocrProcess=re,B()};ce.addEventListener("click",()=>Z("rebuild")),ee.addEventListener("click",()=>Z("redo")),Q()}};if(s){let f=Object.values(s.papers);l(f)}else n.createEl("p",{text:"\u6B63\u5728\u52A0\u8F7D OCR \u7EF4\u62A4\u6570\u636E\u2026"});mt(r,i.path,i.extraArgs||[],s||null).then(f=>{s=Ge(r),(f.changed||!s)&&l(f.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=Vt.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 c=s.createEl("div",{cls:"paperforge-release-section"});c.createEl("div",{cls:"paperforge-release-label",text:"\u884C\u4E3A\u53D8\u66F4 / \u8FC1\u79FB\u6CE8\u610F"});for(let l of a.breaking_or_migration)c.createEl("div",{cls:"paperforge-release-item",text:`\u2022 ${l}`})}if(a.new_features&&a.new_features.length>0){let c=s.createEl("div",{cls:"paperforge-release-section"});c.createEl("div",{cls:"paperforge-release-label",text:"\u65B0\u529F\u80FD"});for(let l of a.new_features)c.createEl("div",{cls:"paperforge-release-item",text:`\u2022 ${l}`})}if(a.fixes&&a.fixes.length>0){let c=s.createEl("div",{cls:"paperforge-release-section"});c.createEl("div",{cls:"paperforge-release-label",text:"\u4FEE\u590D"});for(let l of a.fixes)c.createEl("div",{cls:"paperforge-release-item",text:`\u2022 ${l}`})}if(a.recommended_actions&&a.recommended_actions.length>0){let c=s.createEl("div",{cls:"paperforge-release-section paperforge-release-recommended"});c.createEl("div",{cls:"paperforge-release-label",text:"\u5EFA\u8BAE\u64CD\u4F5C"});for(let l of a.recommended_actions)c.createEl("div",{cls:"paperforge-release-item paperforge-release-item-bold",text:`\u2022 ${l}`})}}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=St(e!=null?e:{},Se),this._persistCapabilityState()}_persistCapabilityState(){this._capabilityState&&(this.plugin.settings.capabilityState=this._capabilityState,this.plugin.saveSettings())}_probeModule(e){var i,c,l,_;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:(c=t==null?void 0:t.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:Le(e)},notices:(l=t==null?void 0:t.notices)!=null?l:[],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=he(n,this.plugin.settings);if(!a.path){this._probing.delete(e),this._updateCapabilityEnvelope(e,Pe(e));return}let s=[...a.extraArgs,"-m","paperforge","--vault",n,"probe",e,"--json"];(0,G.execFile)(a.path,s,{cwd:n,timeout:15e3},(f,u,g)=>{if(this._probing.delete(e),f){console.warn(`[PaperForge] Probe ${e} failed:`,f.message),this._updateCapabilityEnvelope(e,Pe(e));return}try{let m=JSON.parse(u);tt(m,e)?this._updateCapabilityEnvelope(e,m):(console.warn(`[PaperForge] Probe ${e}: invalid envelope schema`,u==null?void 0:u.slice(0,200)),this._updateCapabilityEnvelope(e,Pe(e)))}catch(m){console.warn(`[PaperForge] Probe ${e}: unparseable JSON`,u==null?void 0:u.slice(0,200)),this._updateCapabilityEnvelope(e,Pe(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=Ne._REAL_PROBE.has(t),i=e.createEl("div",{cls:"pf-cc-card",attr:{role:"listitem",tabindex:"0","aria-label":`${o("cc_module_"+t)} \u2014 ${o(this._ccBadgeKey(n,t))}`}}),c=i.createEl("div",{cls:"pf-cc-card-header"});c.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 l;if(!s)l=o("cc_reason_placeholder").replace("{module}",o("cc_module_"+t));else{let k=this._localizeReason(n.reason.code,t);l=k!=null?k:n.reason.text}if(i.createEl("div",{cls:"pf-cc-card-reason",text:l}),n.activity_state==="running"&&n.activity_label){let k=i.createEl("div",{cls:"pf-cc-card-activity",attr:{"aria-live":"polite"}});if(k.createEl("span",{text:n.activity_label}),n.activity_progress&&n.activity_progress.total>0){let y=Math.round(n.activity_progress.current/n.activity_progress.total*100),w=k.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"});w.style.width=y+"%"}}let _=i.createEl("div",{cls:"pf-cc-card-footer"});if(s&&n.action.primary&&!kt(n)){let k=wt(n),E=k.kind==="setup"?"pf-cc-card-action pf-cc-card-action--primary":"pf-cc-card-action";_.createEl("button",{cls:E,text:k.label,attr:{"aria-label":k.label}}).addEventListener("click",()=>{k.kind==="setup"?new qe(this.app,this.plugin,()=>{this._probeModule("installation"),this._probeModule("help")}).open():this._probeModule(t)})}let f=i.createEl("details",{cls:"pf-cc-card-diagnostic"});f.createEl("summary",{text:o("cc_diagnostic_toggle")});let u=f.createEl("div",{cls:"pf-cc-card-diagnostic-body"}),g=o("cc_state_"+n.capability_state)||n.capability_state,m=o("cc_severity_"+n.severity)||n.severity,b=o("cc_activity_"+n.activity_state)||n.activity_state,h;try{h=new Date(n.updated_at).toLocaleString()}catch(k){h=n.updated_at}u.createEl("div",{text:`${o("cc_diag_module")}: ${n.module}`}),u.createEl("div",{text:`${o("cc_diag_state")}: ${g}`}),u.createEl("div",{text:`${o("cc_diag_severity")}: ${m}`}),u.createEl("div",{text:`${o("cc_diag_activity")}: ${b}`});let v=u.createEl("div");v.appendText(o("cc_diag_reason")+": "+l+" ");let x=v.createEl("code",{text:n.reason.code});u.createEl("div",{text:`${o("cc_diag_ttl")}: ${String(n.ttl_seconds)}s`}),u.createEl("div",{text:`${o("cc_diag_updated")}: ${h}`})}_renderControlCenter(e){var g,m,b;let t=e.createEl("div",{cls:"pf-control-center"}),r=Se,n=(g=this._capabilityState)!=null?g:{},a=0,s=0,i=0;for(let h of r){let v=(m=n[h])!=null?m:He(h);v.severity==="ok"&&v.capability_state==="ready"&&v.action.primary===null?a++:Ne._REAL_PROBE.has(h)?(v.severity==="error"||v.severity==="warning"||v.severity==="unknown")&&s++:i++}let c=t.createEl("div",{cls:"pf-cc-summary"});c.createEl("div",{cls:"pf-cc-summary-eyebrow",text:o("cc_title")});let l,_;s>0?(l=o("cc_summary_attention"),_=o("cc_summary_attention_body")):a===r.length?(l=o("cc_summary_ok"),_=o("cc_summary_ok_body")):a>0&&i>0&&s===0?(l=o("cc_summary_core_ok").replace("{n}",String(i)),_=o("cc_summary_core_ok_body")):(l=o("cc_summary_core_ok").replace("{n}",String(r.length-a)),_=o("cc_desc")),c.createEl("div",{cls:"pf-cc-summary-title",text:l}),c.createEl("div",{cls:"pf-cc-summary-body",text:_});let f=c.createEl("div",{cls:"pf-cc-summary-counts"});f.createEl("div",{cls:"pf-cc-summary-count",text:o("cc_n_ready").replace("{n}",String(a))}),i>0&&f.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 h of r){let v=(b=n[h])!=null?b:He(h);this._renderCard(u,h,v)}}_applyStaleTolerance(){if(!this._capabilityState)return;let e=!1;for(let t of Se){let r=this._capabilityState[t];r&&nt(r)&&(this._capabilityState[t]=rt(t),e=!0)}e&&this._persistCapabilityState()}};Ne._REAL_PROBE=new Set(["installation","help"]);var Ye=Ne;var M=require("obsidian"),ye=H(require("fs")),ze=H(require("path")),ve=require("child_process");var $e=H(require("path"));function Ht(p){if(!p)return null;let d=$e.dirname(p);for(;;){let e=$e.basename(d);if(!e||e===".")break;let t=e.match(/^([A-Z0-9]{8})(?:\s*-\s*.*)?$/i);if(t)return t[1];let r=$e.dirname(d);if(r===d)break;d=r}return null}var J=H(require("fs")),xe=H(require("path"));function je(p){return le(p).ocrDir}function ur(p,d){let e=xe.join(je(p),d,"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 _r(p){let d=je(p);try{return J.existsSync(d)?J.readdirSync(d,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name):[]}catch(e){return[]}}function vt(p){let d=_r(p),e=[];for(let t of d){let r=ur(p,t);if(!r)continue;let n=r.versions.map(s=>s.label),a=0;for(let s of n){let i=xe.join(je(p),t,"versions",s,"fulltext.md");try{J.existsSync(i)&&(a+=J.statSync(i).size)}catch(c){}}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 Wt(p,d,e){let t=je(p),r=xe.join(t,d,"versions",e,"fulltext.md"),n=xe.join(t,d,"render"),a=xe.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 Ut(p,d,e,t){var u;let r=je(p),n=xe.join(r,d,"versions",e,"fulltext.md"),a=xe.join(r,d,"versions",t,"fulltext.md"),s="",i="";try{J.existsSync(n)&&(s=J.readFileSync(n,"utf-8"))}catch(g){}try{J.existsSync(a)&&(i=J.readFileSync(a,"utf-8"))}catch(g){}let c=Kt(s),l=Kt(i),_=Math.max(c.length,l.length),f=[];for(let g=0;g<_;g++){let m=g0)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 Te=class extends M.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 ke}getDisplayText(){return"PaperForge"}getIcon(){return Be}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);Ft(n,r,e,1e4,void 0).then(c=>{if(c.status==="not-installed")return;let l=c.pyVersion||"";this._paperforgeVersion=l.startsWith("v")?l:"v"+l,this._versionBadge&&this._versionBadge.setText(this._paperforgeVersion),this._driftBannerEl&&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,ve.execFile)(n,[...a,"-m","paperforge","dashboard","--json"],{cwd:t,timeout:3e4},(i,c)=>{if(!i)try{let l=JSON.parse(c);if(l.ok&&l.data){let _=this._normalizeDashboardData(l.data);this._cachedStats=_,this._metricsEl.empty(),this._renderStats(_),this._renderOcr(_),this._dashboardPermissions=l.data.permissions||{};return}}catch(l){}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,c;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 l=ye.readFileSync(a,"utf-8"),_=JSON.parse(l),f=_.items||[],u={},g={pdf_health:{healthy:0,unhealthy:0},ocr_health:{healthy:0,unhealthy:0},note_health:{healthy:0,unhealthy:0},asset_health:{healthy:0,unhealthy:0}},m=0,b=0,h=0,v=0,x=0,k=0;for(let y of f){y.note_path&&k++;let E=y.lifecycle||"pdf_ready";u[E]=(u[E]||0)+1;let w=y.health||{};for(let T of["pdf_health","ocr_health","note_health","asset_health"])(w[T]||"healthy")==="healthy"?g[T].healthy++:g[T].unhealthy++;let D=y.ocr_status||"";m++,D==="done"?b++:D==="pending"?h++:D==="processing"||D==="queued"||D==="running"?v++:x++}this._cachedStats={version:_.paperforge_version||((i=this._cachedStats)==null?void 0:i.version)||"\u2014",total_papers:f.length,formal_notes:k,exports:0,bases:0,ocr:{total:m,pending:h,processing:v,done:b,failed:x},path_errors:0,lifecycle_level_counts:u,health_aggregate:g},this._metricsEl.empty(),this._renderStats(this._cachedStats),this._renderOcr(this._cachedStats)}catch(l){!e&&!this._cachedStats&&this._metricsEl.createEl("div",{cls:"paperforge-status-loading",text:"No index \u2014 trying CLI..."});let{path:_,extraArgs:f=[]}=z(t,(c=r==null?void 0:r.settings)!=null?c:null,void 0,void 0);(0,ve.execFile)(_,[...f,"-m","paperforge","status","--json"],{cwd:t,timeout:3e4},(u,g)=>{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 m=JSON.parse(g);this._cachedStats=m,this._metricsEl.empty(),this._renderStats(m),this._renderOcr(m)}catch(m){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=ye.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 xt(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]=Qe(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 c=this._metricsEl.createEl("div",{cls:"paperforge-metric-card"});c.style.setProperty("--metric-color",i.color),c.createEl("div",{cls:"paperforge-metric-value",text:((s=i.value)==null?void 0:s.toString())||"\u2014"}),c.createEl("div",{cls:"paperforge-metric-label",text:i.label}),i.barMax>0&&this._buildMetricBar(c,i.value,i.barMax)}let a=e.ocr_version_state||{};if(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 c=this._metricsEl.createEl("div",{cls:"paperforge-metric-card"});c.style.setProperty("--metric-color","var(--color-yellow)"),c.createEl("div",{cls:"paperforge-metric-value",text:i.join(", ")}),c.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 c=[{cls:"pending",count:a},{cls:"active",count:s},{cls:"done",count:n},{cls:"failed",count:i}];for(let l of c)if(l.count>0){let _=(l.count/r*100).toFixed(1);this._ocrTrack.createEl("div",{cls:`paperforge-progress-seg ${l.cls}`,attr:{style:`width:${_}%`}})}}if(this._ocrCounts){this._ocrCounts.empty();let c=[{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 l of c){let _=this._ocrCounts.createEl("div",{cls:"paperforge-ocr-count"});_.createEl("div",{cls:"paperforge-ocr-count-value",text:l.value.toString()}),_.createEl("div",{cls:"paperforge-ocr-count-label",text:l.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 c=a.createEl("div",{cls:"step"});c.createEl("div",{cls:"step-indicator"}),c.createEl("div",{cls:"step-label",text:i.label}),i.key===r?(c.addClass("current"),s=!0):s?c.addClass("pending"):c.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"}),c,l,_;s==="healthy"||s==="ok"?(c=a.iconOk,l="ok",_=`${a.label}: OK`):s==="warn"||s==="warning"||s==="degraded"?(c=a.iconWarn,l="warn",_=`${a.label}: Needs Attention`):(c=a.iconFail,l="fail",_=`${a.label}: Failed`),i.addClass(l),i.setAttribute("title",_),i.createEl("div",{cls:"paperforge-health-cell-icon",text:c}),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 c=1;c<=s;c++){let l=a.createEl("div",{cls:"gauge-segment"});c<=i&&(l.addClass("filled"),l.addClass(`level-${c}`))}if(n.createEl("div",{cls:"gauge-level",text:`Level ${i} / ${s}`}),i0){let l=n.createEl("ul",{cls:"gauge-blockers"});for(let _ of c)l.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,c=i/a*100,l=n.createEl("div",{cls:"bar-row"});l.createEl("div",{cls:"bar-label",text:s.label}),l.createEl("div",{cls:"bar-track"}).createEl("div",{cls:`bar-fill ${s.cls}`,attr:{style:`width:${c.toFixed(1)}%`}}),l.createEl("div",{cls:"bar-count",text:i.toString()})}}_invalidateIndex(){this._cachedItems=null}_extractZoteroKeyFromPath(e){return Ht(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 se,Q,ce,ee,Z,P,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 C of t)C.has_pdf&&n++,C.ocr_status==="done"&&a++,C.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 c=i.createEl("div",{cls:"paperforge-snapshot-pills"}),l=[{value:r,label:"papers"},{value:n,label:"PDFs ready"},{value:a,label:"OCR done"},{value:s,label:"deep-read done"}];for(let C of l){let L=c.createEl("div",{cls:"paperforge-snapshot-pill"});L.createEl("span",{cls:"paperforge-snapshot-value",text:String(C.value)}),L.createEl("span",{cls:"paperforge-snapshot-label",text:" "+C.label})}let _=e.createEl("div",{cls:"paperforge-system-status"});_.createEl("div",{cls:"paperforge-section-label",text:"System Status"});let f=_.createEl("div",{cls:"paperforge-status-grid"}),u=this.app.plugins.plugins.paperforge,g=((se=u==null?void 0:u.manifest)==null?void 0:se.version)||"?",m=this._paperforgeVersion;if(!m)try{let C=this.app.vault.adapter.basePath,{path:L,extraArgs:re=[]}=z(C,(Q=u==null?void 0:u.settings)!=null?Q:null,void 0,void 0),R=(0,ve.execFileSync)(L,[...re,"-c","import paperforge; print(paperforge.__version__)"],{cwd:C,timeout:5e3,encoding:"utf-8",windowsHide:!0}).trim();R&&(m=R.startsWith("v")?R:"v"+R,this._paperforgeVersion=m)}catch(C){}m=m||"\u2014";let b=m==="v"+g;this._renderSystemStatusRow(f,"Runtime",b?"healthy":"mismatch",b?"v"+g:"plugin v"+g+" \u2260 CLI "+m);let h=this._loadIndex(),v=h&&h.items&&h.items.length>0;this._renderSystemStatusRow(f,"Index",v?"healthy":"missing",v?h.items.length+" entries":"formal-library.json not found");let x=((ce=u==null?void 0:u.settings)==null?void 0:ce.system_dir)||"System",k=this.app.vault.adapter.basePath,y=!1,E="No exports found";try{let C=ze.join(k,x,"PaperForge","exports");if(ye.existsSync(C)){let L=ye.readdirSync(C).filter(re=>re.endsWith(".json"));y=L.length>0,E=y?L.length+" export(s)":"No JSON exports"}}catch(C){}this._renderSystemStatusRow(f,"Zotero Export",y?"healthy":"missing",E);let w=!!((ee=u==null?void 0:u.settings)!=null&&ee.paddleocr_api_key);if(!w)try{let C=((Z=u==null?void 0:u.settings)==null?void 0:Z.system_dir)||"System",L=ze.join(k,C,"PaperForge",".env");if(ye.existsSync(L)){let R=ye.readFileSync(L,"utf-8").match(/^PADDLEOCR_API_TOKEN\s*=\s*(.+)$/m);w=!!(R&&R[1]&&R[1].trim())}}catch(C){}w||(w=!!(process.env.PADDLEOCR_API_TOKEN||process.env.PADDLEOCR_API_KEY||process.env.OCR_TOKEN)),this._renderSystemStatusRow(f,"OCR Token",w?"configured":"missing",w?"Configured":"Not set");let D=!1,T="",I=this.app.vault.adapter.basePath,B=Me(I);D=Bt(I),T=B&&((P=B.summary)==null?void 0:P.reason)||B&&((X=B.summary)==null?void 0:X.status)||"Unknown",this._renderSystemStatusRow(f,"Memory Layer",D?"healthy":"fail",T);let $=!b&&m!=="\u2014";if($||!v||!y||!w){let C=e.createEl("div",{cls:"paperforge-issue-summary"});C.createEl("div",{cls:"paperforge-section-label",text:"\u9700\u8981\u5904\u7406"});let L=C.createEl("div",{cls:"paperforge-issue-list"});$&&L.createEl("div",{cls:"paperforge-issue-item",text:"Runtime version mismatch"}),v||L.createEl("div",{cls:"paperforge-issue-item",text:"Index missing or corrupted"}),y||L.createEl("div",{cls:"paperforge-issue-item",text:"No Zotero export found"}),w||L.createEl("div",{cls:"paperforge-issue-item",text:"PaddleOCR API key not configured"});let re=C.createEl("div",{cls:"paperforge-issue-actions"}),R=re.createEl("button",{cls:"paperforge-contextual-btn"});R.createEl("span",{text:"Run Doctor"}),R.addEventListener("click",()=>{let de=ie.find(be=>be.id==="paperforge-doctor");de&&this._runAction(de,R)});let ne=re.createEl("button",{cls:"paperforge-contextual-btn"});ne.createEl("span",{text:"Repair Issues"}),ne.addEventListener("click",()=>{let de=ie.find(be=>be.id==="paperforge-repair");de&&this._runAction(de,ne)})}let A=e.createEl("div",{cls:"paperforge-global-actions"});A.createEl("div",{cls:"paperforge-section-label",text:"Start Working"});let S=A.createEl("div",{cls:"paperforge-global-actions-row"}),N=S.createEl("button",{cls:"paperforge-contextual-btn primary"});N.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u{1F4C1}"}),N.createEl("span",{text:"Open Literature Hub"}),N.addEventListener("click",()=>{var re;let C=((re=u==null?void 0:u.settings)==null?void 0:re.base_dir)||"Bases",L=this.app.vault.getAbstractFileByPath(C);if(L){let R=null;if(L.children&&(R=L.children.find(ne=>ne.extension==="base")),R){let ne=this.app.workspace.getLeaf(!1);ne&&ne.openFile(R)}else new M.Notice("[!!] No .base file found in "+C,6e3)}else new M.Notice("[!!] Base directory not found: "+C,6e3)});let q=S.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 C=ie.find(L=>L.id==="paperforge-sync");C&&this._runAction(C,q)});let j=S.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 C=ie.find(L=>L.id==="paperforge-ocr");C&&this._runAction(C,j)});let O=S.createEl("button",{cls:"paperforge-contextual-btn warn"});O.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u21BA"}),O.createEl("span",{text:"Redo OCR"}),O.addEventListener("click",()=>{let C=ie.find(L=>L.id==="paperforge-ocr-redo");C&&this._runAction(C,O)})}_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 M.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"}),c=i.createEl("div",{cls:"paperforge-status-strip-left"}),l=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 g=c.createEl("span",{cls:"paperforge-status-pill"}),m="pending";u.ok?m="ok":u.fail?m="fail":u.pending&&(m="pending"),g.addClass(m);let b=u.ok?"\u2713":u.fail?"\u2717":"\u25CB";g.createEl("span",{cls:"paperforge-status-pill-icon",text:b}),g.createEl("span",{text:" "+u.label})}if(e.pdf_path){let u=l.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 g=e.pdf_path.match(/\[\[([^\]]+)\]\]/),m=g?g[1]:e.pdf_path;this.app.vault.getAbstractFileByPath(m)?this.app.workspace.openLinkText(m,""):new M.Notice("[!!] PDF not found: "+m,6e3)})}if(e.fulltext_path){let u=l.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 f=l.createEl("button",{cls:"paperforge-contextual-btn"});if(f.createEl("span",{text:o("version_panel_title")}),f.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(c=>{let l=this._extractOverviewFromNote(c);if(l){let _=l.length>200?l.slice(0,200)+"...":l;if(s.setText(_),l.length>200){let f=a.createEl("div",{cls:"paperforge-expand-container"}),u=f.createEl("button",{cls:"paperforge-expand-icon",title:"\u5C55\u5F00/\u6536\u8D77"});u.innerHTML='';let g=!1;f.addEventListener("click",()=>{s.setText(g?_:l),u.innerHTML=g?'':'',g=!g})}}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 c=r.indexOf(i);if(c!==-1){let l=r.slice(c+i.length),_=["**5 Cs","**Figure","**\u8BC1\u636E","### Pass 2","## "],f=l.length;for(let m of _){let b=l.indexOf(m);b!==-1&&b300?n.slice(0,300)+"...":n}_renderRecentDiscussionCard(e,t){let r=e.createEl("div",{cls:"paperforge-discussion-card"});if(r.style.display="none",!t.note_path)return;let s=t.note_path.lastIndexOf("/"),n=(s!==-1?t.note_path.substring(0,s):".")+"/ai/discussion.md";this.app.vault.adapter.exists(n).then(i=>{if(i)return this.app.vault.adapter.read(n)}).then(async i=>{if(!i)return;let c=this._parseDiscussionMD(i);if(!c||c.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 _ of c){let p=r.createEl("div",{cls:"paperforge-discussion-item"}),g=p.createEl("div",{cls:"paperforge-discussion-q"});g.createEl("span",{cls:"paperforge-discussion-q-label",text:"\u63D0\u95EE\uFF1A"}),g.createEl("span",{cls:"paperforge-discussion-q-text",text:_.question});let y=p.createEl("div",{cls:"paperforge-discussion-a"}),E=!1;if(_.answer&&_.answer.length>500&&(E=!0,y.classList.add("paperforge-discussion-a-collapsed")),await M.MarkdownRenderer.render(this.app,_.answer||"",y,n,this),E){let f=!1;p.style.cursor="pointer",p.addEventListener("click",()=>{f=!f,y.classList.toggle("paperforge-discussion-a-collapsed",!f),y.classList.toggle("paperforge-discussion-a-expanded",f)})}}r.createEl("a",{cls:"paperforge-discussion-viewall",text:"\u67E5\u770B\u5168\u90E8\u8BA8\u8BBA \u2192"}).addEventListener("click",_=>{_.preventDefault(),this.app.vault.getAbstractFileByPath(n)?this.app.workspace.openLinkText(n,""):new M.Notice("\u8BA8\u8BBA\u6587\u4EF6\u5C1A\u672A\u751F\u6210")})}).catch(i=>{console.error("PaperForge: discussion.md read error",n,i.message)})}_parseDiscussionMD(e){let t=e.split(/\n## /).slice(1);if(t.length===0)return null;let r=t[t.length-1],s=[],a=r.split(/\*\*\u95EE\u9898:\*\*/).slice(1);for(let n of a){let i=n.match(/\*\*\u89E3\u7B54:\*\*/);if(!i)continue;let c=n.substring(0,i.index).trim(),l=n.substring(i.index+3+4).trim();s.push({question:c,answer:l})}return s.slice(-3)}_renderPaperTechnicalDetails(e,t){let r=this._currentPaperKey,s=e.createEl("div",{cls:"paperforge-technical-details"}),a=s.createEl("button",{cls:"paperforge-technical-details-toggle"}),n=s.createEl("div",{cls:"paperforge-technical-details-body"});n.style.display="none",this._techDetailsExpanded?(n.style.display="block",a.setText("\u6280\u672F\u8BE6\u60C5 \u25BE")):a.setText("\u6280\u672F\u8BE6\u60C5 \u25B8"),a.addEventListener("click",()=>{let p=n.style.display!=="none";n.style.display=p?"none":"block",a.setText(p?"\u6280\u672F\u8BE6\u60C5 \u25B8":"\u6280\u672F\u8BE6\u60C5 \u25BE"),this._techDetailsExpanded=!p});let i=n.createEl("div",{cls:"paperforge-workflow-toggles"}),c=[{key:"do_ocr",label:"OCR",hint:"\u52A0\u5165 OCR"},{key:"analyze",label:"\u7CBE\u8BFB",hint:"\u6807\u8BB0\u7CBE\u8BFB"}];for(let p of c){let g=i.createEl("label",{cls:"paperforge-workflow-toggle"}),y=g.createEl("input",{type:"checkbox",cls:"paperforge-workflow-checkbox"});y.checked=t[p.key]===!0,g.createEl("span",{cls:"paperforge-workflow-toggle-label",text:p.label}),g.createEl("span",{cls:"paperforge-workflow-toggle-hint",text:p.hint}),y.addEventListener("change",async()=>{let E=t.note_path?this.app.vault.getAbstractFileByPath(t.note_path):null;if(!E){new M.Notice("[!!] Note file not found",6e3);return}let f=y.checked;await this.app.fileManager.processFrontMatter(E,b=>{b[p.key]=f}),this._patchCachedEntry(r,{[p.key]:f}),this._currentPaperEntry=qe(this._currentPaperEntry,{[p.key]:f})})}let l=t.health||{},h=[["PDF Health",l.pdf_health||"\u2014"],["OCR Status",t.ocr_status||"\u2014"],["Asset Health",l.asset_health||"\u2014"],["Note Path",t.note_path||"\u2014"],["Fulltext Path",t.fulltext_path||"\u2014"]],_=new Set(["Note Path","Fulltext Path","Key"]);for(let[p,g]of h){let y=n.createEl("div",{cls:"paperforge-technical-row"});y.createEl("span",{cls:"paperforge-technical-label",text:p});let E=y.createEl("span",{cls:"paperforge-technical-value",text:String(g)});_.has(p)&&g&&g!=="\u2014"&&(E.addClass("pf-copy"),E.addEventListener("click",()=>{navigator.clipboard.writeText(g),new M.Notice(p+" copied")}))}}_renderNextStepCard(e,t,r){var c,l;let s=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"}},n=a[s]||a.ready,i=e.createEl("div",{cls:"paperforge-next-step-card"});if(s==="ready"&&i.addClass("ready"),i.createEl("div",{cls:"paperforge-next-step-label",text:"Recommended Next Step"}),i.createEl("div",{cls:"paperforge-next-step-text",text:n.text}),n.cmd&&n.cmd!=="ready"){let h=i.createEl("button",{cls:"paperforge-next-step-trigger"});h.createEl("span",{text:n.icon+" "+n.label}),h.addEventListener("click",()=>{let _=ie.find(p=>p.cmd===n.cmd);_&&this._runAction(_,h)})}else if(s==="/pf-deep"){let h=i.createEl("button",{cls:"paperforge-next-step-trigger"});h.createEl("span",{text:"\u{1F4CB} "+o("copy_pf_deep_cmd")}),h.addEventListener("click",()=>{let E="/pf-deep "+r;navigator.clipboard.writeText(E).then(()=>{h.setText("\u2713 "+o("copied")),new M.Notice(E+" copied")}).catch(()=>{new M.Notice("[!!] Clipboard write failed",6e3)})});let _=((l=(c=this.app.plugins.plugins.paperforge)==null?void 0:c.settings)==null?void 0:l.agent_platform)||"opencode",g={opencode:"OpenCode",claude:"Claude Code",cursor:"Cursor",github_copilot:"GitHub Copilot",windsurf:"Windsurf",codex:"Codex",gemini:"Gemini CLI",cline:"Cline"}[_]||_;i.createEl("div",{cls:"paperforge-agent-platform-label"}).setText(o("run_in_agent").replace("{0}",g))}else s==="ready"&&i.createEl("button",{cls:"paperforge-next-step-trigger"}).createEl("span",{text:"\u2713 "+n.label})}_openFulltext(e){if(!e){new M.Notice("[!!] No fulltext path available for this paper",6e3);return}let t=this.app.vault.getAbstractFileByPath(e);t?this.app.workspace.openLinkText(t.path,""):new M.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"}),s=t.length,a=0,n=0,i=0,c=0,l=0,h=0,_=0;for(let m of t){m.has_pdf&&a++,m.ocr_status==="done"&&n++,m.ocr_status==="done"&&m.analyze===!0&&i++,m.deep_reading_status==="done"&&c++;let v=m.ocr_status||"";v==="pending"||v==="queued"?l++:v==="processing"?h++:(v==="failed"||v==="blocked"||v==="done_incomplete"||v==="nopdf")&&_++}r.createEl("div",{cls:"paperforge-collection-header"}).createEl("div",{cls:"paperforge-collection-title",text:e});let g=r.createEl("div",{cls:"paperforge-workflow-overview"});g.createEl("div",{cls:"paperforge-section-label",text:"Workflow Overview"});let y=g.createEl("div",{cls:"paperforge-workflow-funnel"}),E=[{value:s,label:"Total"},{value:a,label:"PDF Ready"},{value:n,label:"OCR Done"},{value:c,label:"Deep Read"}];for(let m=0;m0){let m=r.createEl("div",{cls:"paperforge-ocr-section"}),v=m.createEl("div",{cls:"paperforge-collection-ocr-header"});v.createEl("h4",{cls:"paperforge-ocr-title",text:"OCR Pipeline"});let k=v.createEl("span",{cls:"paperforge-ocr-badge idle"});h>0?(k.addClass("active"),k.setText("Processing")):l>0?k.setText("Pending"):(k.addClass("idle"),k.setText("Idle"));let F=m.createEl("div",{cls:"paperforge-progress-track"});h>0&&F.addClass("paperforge-processing");let P=l+h+n+_,O=[{cls:"pending",count:l},{cls:"active",count:h},{cls:"done",count:n},{cls:"failed",count:_}];for(let H of O)if(H.count>0){let A=(H.count/P*100).toFixed(1);F.createEl("div",{cls:`paperforge-progress-seg ${H.cls}`,attr:{style:`width:${A}%`}})}let B=m.createEl("div",{cls:"paperforge-ocr-counts"}),j=[{cls:"pending",value:l,label:"Pending"},{cls:"active",value:h,label:"Processing"},{cls:"done",value:n,label:"Done"},{cls:"failed",value:_,label:"Attention"}];for(let H of j){let A=B.createEl("div",{cls:"paperforge-ocr-count"});A.createEl("div",{cls:"paperforge-ocr-count-value",text:H.value.toString()}),A.createEl("div",{cls:"paperforge-ocr-count-label",text:H.label})}}let f=r.createEl("div",{cls:"paperforge-collection-actions"}),b=f.createEl("button",{cls:"paperforge-contextual-btn primary"});b.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u229E"}),b.createEl("span",{text:"Run OCR"}),b.addEventListener("click",()=>{let m=ie.find(v=>v.id==="paperforge-ocr");m&&this._runAction(m,b)});let w=f.createEl("button",{cls:"paperforge-contextual-btn"});w.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u21BB"}),w.createEl("span",{text:"Sync Library"}),w.addEventListener("click",()=>{let m=ie.find(v=>v.id==="paperforge-sync");m&&this._runAction(m,w)});let x=f.createEl("button",{cls:"paperforge-contextual-btn warn"});x.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u21BA"}),x.createEl("span",{text:"Redo OCR"}),x.addEventListener("click",()=>{let m=ie.find(v=>v.id==="paperforge-ocr-redo");m&&this._runAction(m,x)}),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,s=typeof r=="string"?r:"";if(!s){new M.Notice("Cannot determine vault path");return}this._versionPapers=pt(s),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,s=typeof r=="string"?r:"";if(!s){e.createEl("div",{cls:"paperforge-status-error",text:"Could not determine vault path"});return}(!this._versionPapers||this._versionPapers.length===0)&&(this._versionPapers=pt(s));let a=e.createEl("div",{cls:"paperforge-version-left"}),n=e.createEl("div",{cls:"paperforge-version-right"}),i=a.createEl("input",{cls:"paperforge-version-filter",attr:{type:"text",placeholder:o("version_filter_placeholder")}});i.value=this._versionFilter;let c=a.createEl("div",{cls:"paperforge-version-paper-list"}),l=()=>{c.empty();let b=this._versionFilter.toLowerCase(),w=this._versionPapers?this._versionPapers.filter(m=>!b||m.key.toLowerCase().includes(b)||m.title.toLowerCase().includes(b)):[];if(w.length===0){c.createEl("div",{cls:"paperforge-meta",text:o("version_no_backups")});return}let x=c.createEl("div",{cls:"paperforge-meta",text:o("version_papers_count").replace("{n}",String(w.length))});for(let m of w){let v=c.createEl("div",{cls:"paperforge-version-paper-item"}),k=v.createEl("span",{cls:"paperforge-version-paper-title",text:m.title}),F=v.createEl("span",{cls:"paperforge-version-paper-versions",text:m.versions.map(P=>P.label).join(" ")});v.addEventListener("click",()=>{c.querySelectorAll(".paperforge-version-paper-item.selected").forEach(P=>P.removeClass("selected")),v.addClass("selected"),_(m)})}};i.addEventListener("input",()=>{this._versionFilter=i.value,l()});let h=n.createEl("div",{cls:"paperforge-version-timeline-area"}),_=b=>{if(h.empty(),h.createEl("div",{cls:"paperforge-version-timeline-header"}).createEl("span",{cls:"pf-title",text:b.title}),b.versions.length===0){h.createEl("div",{cls:"paperforge-meta",text:o("version_no_backups")});return}let x=h.createEl("div",{cls:"paperforge-version-timeline"});for(let m of b.versions){let v=m.label===b.currentLabel,k=x.createEl("div",{cls:"paperforge-version-entry"+(v?" paperforge-version-current":"")}),F=k.createEl("div",{cls:"paperforge-version-dot"}),P=k.createEl("div",{cls:"paperforge-version-content"}),O=P.createEl("div",{cls:"paperforge-version-label-row"});O.createEl("span",{cls:"paperforge-version-label",text:m.label}),v&&O.createEl("span",{cls:"paperforge-version-current-tag",text:o("version_current")});let B=m.created_at?m.created_at.slice(0,10):"";P.createEl("div",{cls:"paperforge-meta",text:B+" \u2014 "+m.source});let j=m.fulltext_size?m.fulltext_size>1024?(m.fulltext_size/1024).toFixed(0)+"KB":m.fulltext_size+"B":"";j&&P.createEl("div",{cls:"paperforge-meta",text:j});let H=P.createEl("div",{cls:"paperforge-version-actions"});H.createEl("button",{cls:"pf-btn-primary",text:o("version_restore_btn")}).addEventListener("click",()=>{At(s,b.key,m.label)?new M.Notice(o("version_restore_done").replace("{label}",m.label)):new M.Notice("Restore failed",6e3)}),b.versions.length>1&&!v&&H.createEl("button",{cls:"pf-btn-secondary",text:o("version_compare_btn")}).addEventListener("click",()=>{g(b,m.label,b.currentLabel)})}},p=n.createEl("div",{cls:"paperforge-version-compare"});p.style.display="none";let g=(b,w,x)=>{let m=Ot(s,b.key,w,x);p.style.display="block",p.empty();let v=p.createEl("div",{cls:"paperforge-version-compare-header"});if(v.createEl("span",{cls:"pf-title",text:o("version_compare_title").replace("{vA}",w).replace("{vB}",x)}),v.createEl("span",{cls:"paperforge-meta",text:o("version_compare_paragraphs").replace("{n}",String(m.length))}),m.length===0){p.createEl("div",{cls:"paperforge-meta",text:"No changes"});return}let k=p.createEl("div",{cls:"paperforge-version-diff-list"});for(let F of m){let P=k.createEl("div",{cls:"paperforge-version-diff-row"}),O=F.type==="added"?"[+]":F.type==="removed"?"[-]":"[~]",B=F.heading||"paragraph "+(F.paragraphIndex+1);P.createEl("span",{cls:"paperforge-version-diff-label",text:O+" "+B}),F.oldText&&P.createEl("pre",{cls:"paperforge-version-diff-old",text:F.oldText.slice(0,200)}),F.newText&&P.createEl("pre",{cls:"paperforge-version-diff-new",text:F.newText.slice(0,200)})}},y=e.createEl("div",{cls:"paperforge-version-actions-bar"}),E=y.createEl("button",{cls:"pf-btn-primary",text:o("version_restore_selected")}),f=y.createEl("button",{cls:"pf-btn-secondary",text:o("version_clear_old").replace("{size}","")});l()}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"}),s=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 n;let a=((n=this._searchInput)==null?void 0:n.value)||"";if(a.startsWith("@")&&!a.startsWith("@ ")?(this._searchMode="@",s.setText("@"),s.addClass("deep"),this._searchInput&&(this._searchInput.placeholder=o("retrieval_search_placeholder_deep"))):(this._searchMode="M",s.setText("M"),s.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 n,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"||!((n=this._searchResults)!=null&&n.length))return;a.preventDefault();let c=this._searchResults.length;a.key==="ArrowDown"?this._searchActiveIndex=Math.min(this._searchActiveIndex+1,c-1):this._searchActiveIndex=Math.max(this._searchActiveIndex-1,-1);let l=(i=this._searchResultsEl)==null?void 0:i.querySelectorAll(".paperforge-search-result-card");l&&l.forEach((h,_)=>{_===this._searchActiveIndex?(h.setAttribute("aria-selected","true"),h.classList.add("active")):(h.setAttribute("aria-selected","false"),h.classList.remove("active"))});return}if(a.key==="Enter"&&a.ctrlKey){a.preventDefault(),this._searchTimer&&(clearTimeout(this._searchTimer),this._searchTimer=void 0);let c=this._searchMode;this._searchMode="@",this.executeSearch(),this._searchMode=c;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 s=this.app.setting;if(s&&typeof s=="object"){let a=s.openTab;typeof a=="function"&&a.call(s,"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"}),s=r.createEl("button",{cls:"pf-btn-primary",text:o("retrieval_run_doctor")});s.addEventListener("click",()=>{let n=this.app.vault.adapter.basePath;if(typeof n=="string"){let{path:i,extraArgs:c=[]}=z(n,null,void 0,void 0);(0,ve.spawn)(i,[...c,"-m","paperforge","doctor"],{cwd:n,stdio:"inherit"})}}),r.createEl("button",{cls:"pf-btn-secondary",text:o("retrieval_retry")}).addEventListener("click",()=>{this.executeSearch()}),setTimeout(()=>{s.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 s=this.app.setting;if(s&&typeof s=="object"){let a=s.openTab;typeof a=="function"&&a.call(s,"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 s=t?"retrieve":"search";this._searchState="searching",this._searchResults=null,this._searchActiveIndex=-1,this._renderSearchState();let a=this.app.vault.adapter,n="";if(a&&typeof a=="object"&&"basePath"in a){let E=a.basePath;n=typeof E=="string"?E:""}if(!n){this._searchState="backend-unavailable",this._renderSearchState();return}let i=null,l=this.app.plugins;if(l&&typeof l=="object"&&"plugins"in l){let E=l.plugins;if(E&&typeof E=="object"&&"paperforge"in E){let f=E.paperforge;f&&typeof f=="object"&&"settings"in f&&(i=f.settings)}}let{path:h,extraArgs:_=[]}=z(n,i,void 0,void 0),p=s==="retrieve"?["--deep"]:[],g=(0,ve.spawn)(h,[..._,"-m","paperforge","--vault",n,s,r,...p,"--json"],{cwd:n,timeout:3e4}),y=[];g.stdout.on("data",E=>{y.push(E.toString("utf-8"))}),g.stderr.on("data",()=>{}),g.on("close",E=>{if(E!==0){let m=Qe(String(E));this._searchState=this._mapErrorToSearchState(m.type),this._renderSearchState();return}let f=y.join(""),b=f.indexOf("{"),w=f.lastIndexOf("}"),x="";if(b!==-1&&w>b)x=f.slice(b,w+1);else{let m=f.indexOf("["),v=f.lastIndexOf("]");m!==-1&&v>m&&(x=f.slice(m,v+1))}if(!x){this._searchState="internal-error",this._renderSearchState();return}try{let m=JSON.parse(x),v=[];if(m&&typeof m=="object"&&"data"in m){let k=m.data;if(k&&typeof k=="object"){let F=k;"matches"in F&&Array.isArray(F.matches)&&(v=F.matches)}}this._searchResults=v,this._searchState=v.length>0?"results":"empty",this._renderSearchState()}catch(m){let v=m instanceof Error?m.message:String(m);this._searchState="internal-error",this._renderSearchState()}}),g.on("error",E=>{let f=E.code;if(typeof f=="string"){let b=Qe(f);this._searchState=this._mapErrorToSearchState(b.type)}else this._searchState="backend-unavailable";this._renderSearchState()})}_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;aw!==null&&typeof w=="object"&&"zotero_key"in w&&w.zotero_key===_);if(b&&typeof b=="object"){let w=b;y=typeof w.main_note_path=="string"&&w.main_note_path?w.main_note_path:typeof w.note_path=="string"&&w.note_path?w.note_path:null}}y?l.addEventListener("click",f=>{let b=f.ctrlKey||f.metaKey;this.app.workspace.openLinkText(y,"",b)}):l.addEventListener("click",()=>{new M.Notice("[!!] Note not found: "+(_||"unknown"),6e3)}),l.addEventListener("keydown",f=>{if(f.key==="Enter"&&y){f.preventDefault();let b=f.ctrlKey||f.metaKey;this.app.workspace.openLinkText(y,"",b)}});let E=l.createEl("div",{cls:"paperforge-search-result-meta"});if(typeof i.first_author=="string"&&i.first_author&&E.createEl("span",{cls:"paperforge-search-result-author",text:i.first_author}),typeof i.journal=="string"&&i.journal&&E.createEl("span",{cls:"paperforge-search-result-journal",text:i.journal}),i.score!==void 0){let f=i.score,b=typeof f=="number"?f.toFixed(3):String(f);E.createEl("span",{cls:"paperforge-search-result-score",text:"Score: "+b})}if(typeof i.domain=="string"&&i.domain&&l.createEl("span",{cls:"paperforge-search-result-tag",text:i.domain}),typeof i.abstract=="string"&&i.abstract){let f=i.abstract;l.createEl("div",{cls:"paperforge-search-result-abstract",text:f.length>200?f.slice(0,200)+"...":f})}if(t&&typeof i.text=="string"&&i.text){let f=i.text;l.createEl("div",{cls:"paperforge-search-result-source",text:f.length>300?f.slice(0,300)+"...":f})}}}_renderSearchError(e){this._searchResultsEl&&(this._searchResultsEl.empty(),this._searchResultsEl.createEl("div",{cls:"paperforge-search-error",text:e}))}_runAction(e,t){var p,g;if(e.disabled){new M.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 s=Array.isArray(e.args)?[...e.args]:[];if(e.needsKey){let y=this.app.workspace.getActiveFile(),E=null;if(y){let f=this.app.metadataCache.getFileCache(y);if(f&&f.frontmatter&&f.frontmatter.zotero_key?E=f.frontmatter.zotero_key:E=this._extractZoteroKeyFromPath(y.path),E)s=[...s,E];else if(f&&f.frontmatter){this._showMessage("[!!] No zotero_key in active note frontmatter","error"),new M.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 M.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 M.Notice("[!!] Open a paper note with a zotero_key in its frontmatter first",6e3),t.removeClass("running");return}}e.needsFilter&&(s=[...s,"--all"]);let a=e.needsFilter?6e4:e.needsKey?3e4:6e5,{path:n,extraArgs:i=[]}=z(r,(g=(p=this.app.plugins.plugins.paperforge)==null?void 0:p.settings)!=null?g:null,void 0,void 0),c=(0,ve.spawn)(n,[...i,"-m","paperforge",e.cmd,...s],{cwd:r,timeout:a}),l=[],h=Date.now(),_=setInterval(()=>this._fetchStats(!0),4e3);c.stdout.on("data",y=>{let E=y.toString("utf-8").split(`
-`).filter(Boolean);for(let f of E){let b=f.trim();b&&(l.push(b),this._showMessage(l.slice(-8).join(`
-`),"running"))}}),c.stderr.on("data",y=>{let E=y.toString("utf-8").split(`
-`).filter(Boolean);for(let f of E){if(f.includes("\r")||f.includes("%")||f.includes("\u2588"))continue;let b=f.trim();b&&!b.match(/^\d+%|^\|/)&&(l.push(b),this._showMessage(l.slice(-8).join(`
-`),"running"))}}),c.on("close",y=>{clearInterval(_),t.removeClass("running");let E=((Date.now()-h)/1e3).toFixed(1);if(y!==0){let f=l.slice(-3).join(" | ")||"exit code "+y;(e.cmd==="repair"||e.cmd==="ocr")&&y===1?(this._showMessage("[WARN] "+f,"running"),new M.Notice("[WARN] "+e.cmd+" partial: "+f,8e3),this._fetchStats(!0)):(this._showMessage("[!!] "+f,"error"),new M.Notice("[!!] "+e.cmd+" failed: "+f,8e3))}else if(e.needsKey||e.needsFilter){let f=l.join(`
-`);if(f.trim())try{JSON.parse(f),navigator.clipboard.writeText(f).then(()=>{let b=`${E}s \u2014 ${f.length} chars copied`;this._showMessage("[OK] "+e.title+": "+b,"ok"),new M.Notice("[OK] "+e.okMsg+" \u2014 "+f.length+" chars")}).catch(b=>{this._showMessage("[!!] Clipboard write failed: "+b.message,"error"),new M.Notice("[!!] Clipboard error",6e3)})}catch(b){this._showMessage("[!!] Invalid JSON from "+e.title,"error"),new M.Notice("[!!] "+e.title+" returned invalid JSON: "+b.message.slice(0,100),8e3)}else this._showMessage("[!!] No output from context command","error"),new M.Notice("[!!] Context command returned empty output",8e3);this._fetchStats(!0)}else{let b=l.filter(x=>x.match(/updated \d+/)).pop()||l[l.length-1]||"",w=`${E}s \u2014 ${b}`;this._showMessage("[OK] "+e.title+": "+w,"ok"),new M.Notice("[OK] "+e.okMsg),this._contentEl&&this._contentEl.removeClass("switching"),this._cachedStats=null;try{this._fetchStats(!1)}catch(x){console.log("[PF] fetchStats error:",x)}console.log("[PF] close cmd="+e.cmd+" id="+e.id),e.cmd==="sync"&&He(this.app,this.app.plugins.plugins.paperforge,r)}}),c.on("error",y=>{t.removeClass("running"),this._contentEl&&this._contentEl.removeClass("switching"),this._showMessage("[!!] "+y.message,"error"),new M.Notice("[!!] Cannot start: "+y.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()),s=r.mode,a=r.filePath;this._currentMode===s&&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(ke);if(t.length>0){e.app.workspace.revealLeaf(t[0]);return}let r=e.app.workspace.getRightLeaf(!1);r&&(await r.setViewState({type:ke,active:!0}),e.app.workspace.revealLeaf(r))}};var Ue=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(),_t(this.app),this.registerView(ke,t=>new Re(t));try{(0,X.addIcon)(De,ut)}catch(t){}this.addRibbonIcon(De,"PaperForge Dashboard",()=>Re.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:s}=z(t,this.settings,void 0,void 0);(0,fe.execFile)(r,[...s,"-m","paperforge","ocr","redo"],{cwd:t,timeout:6e5},(a,n,i)=>{if(a){new X.Notice("PaperForge: Redo OCR failed");return}new X.Notice("PaperForge: Redo OCR done")})}),this.addSettingTab(new Ze(this.app,this)),this.addCommand({id:"paperforge-status-panel",name:`PaperForge: ${o("guide_open")}`,callback:()=>Re.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:s,extraArgs:a=[]}=z(r,this.settings,void 0,void 0),n=Array.isArray(t.args)?[...t.args]:[];(0,fe.execFile)(s,[...a,"-m","paperforge",t.cmd,...n],{cwd:r,timeout:3e5},(i,c,l)=>{if(i){new X.Notice(`[!!] ${t.cmd} failed: ${(l||i.message).slice(0,120)}`,8e3);return}new X.Notice(`[OK] ${t.okMsg||c.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=le(e).memoryStatePath;if(!Z.existsSync(r)){let s=z(e,this.settings,void 0,void 0);[["runtime-health","--json"],["memory","status","--json"],["embed","status","--json"]].forEach(n=>{let i=[...s.extraArgs,"-m","paperforge","--vault",e,...n];(0,fe.execFile)(s.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),s=this.manifest.version,a=`paperforge==${s}`,n=`git+https://github.com/LLLin000/PaperForge.git@${s}`,i=(c,l)=>{(0,fe.spawn)(t,[...r,"-m","pip","install","--upgrade",c],{cwd:e,timeout:12e4,env:we()}).on("close",_=>l(_===0))};(0,fe.execFile)(t,[...r,"-c","import paperforge; print(paperforge.__version__)"],{cwd:e,timeout:1e4},(c,l)=>{let h=p=>{console.log(`[PaperForge] Auto-update: trying PyPI (paperforge==${s})`),i(a,g=>{if(g){console.log("[PaperForge] Auto-update: installed via PyPI"),new X.Notice(`[OK] PaperForge CLI ${p}`,5e3);return}console.warn("[PaperForge] Auto-update: PyPI failed, falling back to git..."),i(n,y=>{y&&(console.log("[PaperForge] Auto-update: installed via git"),new X.Notice(`[OK] PaperForge CLI ${p} (via git)`,5e3))})})};if(c){h("installed");return}let _=l.trim();_!==s&&h(`${_} -> ${s}`)})}_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=le(e).exportsDir;if(!Z.existsSync(t))return;let r=0;try{Z.readdirSync(t).forEach(s=>{if(!s.endsWith(".json"))return;let a=Z.statSync(Fe.join(t,s));a.mtimeMs>r&&(r=a.mtimeMs)})}catch(s){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,fe.exec)(r,{timeout:12e4,encoding:"utf-8"},(s,a,n)=>{this._autoSyncRunning=!1,this._memoryStatusText=null,s||(this._lastSyncTime=new Date().toLocaleTimeString());try{let i=le(e).exportsDir,c=0;Z.readdirSync(i).forEach(l=>{l.endsWith(".json")&&(c=Math.max(c,Z.statSync(Fe.join(i,l)).mtimeMs))}),this._lastExportMtime=c}catch(i){}})}_checkOcr(e){if(this._autoSyncRunning)return;let t=le(e).ocrDir;if(Z.existsSync(t))try{Z.readdirSync(t,{withFileTypes:!0}).forEach(r=>{if(!r.isDirectory())return;let s=Fe.join(t,r.name,"meta.json");if(!Z.existsSync(s))return;let a=Z.statSync(s),n=this._lastOcrMtimes[r.name]||0;if(a.mtimeMs<=n||(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 c=`"${i.path}" -m paperforge --vault "${e}" sync`;(0,fe.exec)(c,{timeout:3e4,encoding:"utf-8"},()=>{this._autoSyncRunning=!1,this._memoryStatusText=null})})}catch(r){}}readPaperforgeJson(){let e=this.app.vault.adapter.basePath,t=Fe.join(e,"paperforge.json"),r={system_dir:"System",resources_dir:"Resources",literature_dir:"Literature",base_dir:"Bases"};try{if(!Z.existsSync(t))return r;let s=Z.readFileSync(t,"utf-8"),a=JSON.parse(s),n=a.vault_config||{};return{system_dir:n.system_dir||a.system_dir||r.system_dir,resources_dir:n.resources_dir||a.resources_dir||r.resources_dir,literature_dir:n.literature_dir||a.literature_dir||r.literature_dir,base_dir:n.base_dir||a.base_dir||r.base_dir}}catch(s){return console.warn("PaperForge: Failed to read paperforge.json, using defaults",s),r}}savePaperforgeJson(e){let t=this.app.vault.adapter.basePath,r=Fe.join(t,"paperforge.json"),s={};try{Z.existsSync(r)&&(s=JSON.parse(Z.readFileSync(r,"utf-8")))}catch(n){console.warn("PaperForge: Failed to read paperforge.json for update",n)}(!s.vault_config||typeof s.vault_config!="object")&&(s.vault_config={});let a=["system_dir","resources_dir","literature_dir","base_dir"];for(let n of a)e[n]!==void 0&&(s.vault_config[n]=e[n]);s.schema_version||(s.schema_version="2");for(let n of a)delete s[n];try{if(Z.writeFileSync(r,JSON.stringify(s,null,2),"utf-8"),this.settings){let n=this.readPaperforgeJson();this.settings.system_dir=n.system_dir,this.settings.resources_dir=n.resources_dir,this.settings.literature_dir=n.literature_dir,this.settings.base_dir=n.base_dir}}catch(n){console.error("PaperForge: Failed to write paperforge.json",n),new X.Notice("PaperForge: Failed to save configuration to paperforge.json")}}onunload(){this._pollTimer&&clearInterval(this._pollTimer),this.app.workspace.detachLeavesOfType(ke)}async loadSettings(){this.settings=Object.assign({},Be,await this.loadData()),this.settings.features&&Be.features&&(this.settings.features=Object.assign({},Be.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();Z.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(Be))t in this.settings&&(e[t]=this.settings[t]);await this.saveData(e)}_checkReleaseNotes(){let e=this.manifest.version;if(this.settings.last_seen_version===e)return;let a=(Xe().versions||[]).find(i=>i.version===e);class n extends X.Modal{constructor(c,l){super(c),this._entry=l}onOpen(){let{contentEl:c}=this;if(c.createEl("h2",{text:`PaperForge v${e} \u66F4\u65B0\u8BF4\u660E`}),this._entry){if(c.createEl("p",{text:this._entry.title,cls:"paperforge-modal-subtitle"}),this._entry.breaking_or_migration&&this._entry.breaking_or_migration.length>0){c.createEl("h4",{text:"\u884C\u4E3A\u53D8\u66F4 / \u8FC1\u79FB\u6CE8\u610F"});for(let l of this._entry.breaking_or_migration)c.createEl("p",{text:`\u2022 ${l}`,cls:"paperforge-modal-item"})}if(this._entry.new_features&&this._entry.new_features.length>0){c.createEl("h4",{text:"\u65B0\u529F\u80FD"});for(let l of this._entry.new_features)c.createEl("p",{text:`\u2022 ${l}`,cls:"paperforge-modal-item"})}if(this._entry.fixes&&this._entry.fixes.length>0){c.createEl("h4",{text:"\u4FEE\u590D"});for(let l of this._entry.fixes)c.createEl("p",{text:`\u2022 ${l}`,cls:"paperforge-modal-item"})}if(this._entry.recommended_actions&&this._entry.recommended_actions.length>0){let l=c.createEl("div",{cls:"paperforge-release-recommended"});l.createEl("h4",{text:"\u5EFA\u8BAE\u64CD\u4F5C",cls:""}),l.style.marginBottom="8px";for(let h of this._entry.recommended_actions)l.createEl("p",{text:`\u2022 ${h}`,cls:"paperforge-release-item-bold"})}}else c.createEl("p",{text:"\u7248\u672C\u5DF2\u66F4\u65B0\u81F3 v"+e+"\uFF0C\u8BF7\u524D\u5F80\u8BBE\u7F6E \u2192 \u66F4\u65B0\u4E0E\u624B\u518C \u67E5\u770B\u5B8C\u6574\u66F4\u65B0\u8BB0\u5F55\u3002"});new X.Setting(c).addButton(l=>l.setButtonText("\u77E5\u9053\u4E86").setCta().onClick(()=>{this.close()}))}onClose(){let{contentEl:c}=this;c.empty()}}new n(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(i=>{if(i)return this.app.vault.adapter.read(s)}).then(async i=>{if(!i)return;let c=this._parseDiscussionMD(i);if(!c||c.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 f of c){let u=r.createEl("div",{cls:"paperforge-discussion-item"}),g=u.createEl("div",{cls:"paperforge-discussion-q"});g.createEl("span",{cls:"paperforge-discussion-q-label",text:"\u63D0\u95EE\uFF1A"}),g.createEl("span",{cls:"paperforge-discussion-q-text",text:f.question});let m=u.createEl("div",{cls:"paperforge-discussion-a"}),b=!1;if(f.answer&&f.answer.length>500&&(b=!0,m.classList.add("paperforge-discussion-a-collapsed")),await M.MarkdownRenderer.render(this.app,f.answer||"",m,s,this),b){let h=!1;u.style.cursor="pointer",u.addEventListener("click",()=>{h=!h,m.classList.toggle("paperforge-discussion-a-collapsed",!h),m.classList.toggle("paperforge-discussion-a-expanded",h)})}}r.createEl("a",{cls:"paperforge-discussion-viewall",text:"\u67E5\u770B\u5168\u90E8\u8BA8\u8BBA \u2192"}).addEventListener("click",f=>{f.preventDefault(),this.app.vault.getAbstractFileByPath(s)?this.app.workspace.openLinkText(s,""):new M.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 c=s.substring(0,i.index).trim(),l=s.substring(i.index+3+4).trim();n.push({question:c,answer:l})}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"}),c=[{key:"do_ocr",label:"OCR",hint:"\u52A0\u5165 OCR"},{key:"analyze",label:"\u7CBE\u8BFB",hint:"\u6807\u8BB0\u7CBE\u8BFB"}];for(let u of c){let g=i.createEl("label",{cls:"paperforge-workflow-toggle"}),m=g.createEl("input",{type:"checkbox",cls:"paperforge-workflow-checkbox"});m.checked=t[u.key]===!0,g.createEl("span",{cls:"paperforge-workflow-toggle-label",text:u.label}),g.createEl("span",{cls:"paperforge-workflow-toggle-hint",text:u.hint}),m.addEventListener("change",async()=>{let b=t.note_path?this.app.vault.getAbstractFileByPath(t.note_path):null;if(!b){new M.Notice("[!!] Note file not found",6e3);return}let h=m.checked;await this.app.fileManager.processFrontMatter(b,v=>{v[u.key]=h}),this._patchCachedEntry(r,{[u.key]:h}),this._currentPaperEntry=Qe(this._currentPaperEntry,{[u.key]:h})})}let l=t.health||{},_=[["PDF Health",l.pdf_health||"\u2014"],["OCR Status",t.ocr_status||"\u2014"],["Asset Health",l.asset_health||"\u2014"],["Note Path",t.note_path||"\u2014"],["Fulltext Path",t.fulltext_path||"\u2014"]],f=new Set(["Note Path","Fulltext Path","Key"]);for(let[u,g]of _){let m=s.createEl("div",{cls:"paperforge-technical-row"});m.createEl("span",{cls:"paperforge-technical-label",text:u});let b=m.createEl("span",{cls:"paperforge-technical-value",text:String(g)});f.has(u)&&g&&g!=="\u2014"&&(b.addClass("pf-copy"),b.addEventListener("click",()=>{navigator.clipboard.writeText(g),new M.Notice(u+" copied")}))}}_renderNextStepCard(e,t,r){var c,l;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 f=ie.find(u=>u.cmd===s.cmd);f&&this._runAction(f,_)})}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 b="/pf-deep "+r;navigator.clipboard.writeText(b).then(()=>{_.setText("\u2713 "+o("copied")),new M.Notice(b+" copied")}).catch(()=>{new M.Notice("[!!] Clipboard write failed",6e3)})});let f=((l=(c=this.app.plugins.plugins.paperforge)==null?void 0:c.settings)==null?void 0:l.agent_platform)||"opencode",g={opencode:"OpenCode",claude:"Claude Code",cursor:"Cursor",github_copilot:"GitHub Copilot",windsurf:"Windsurf",codex:"Codex",gemini:"Gemini CLI",cline:"Cline"}[f]||f;i.createEl("div",{cls:"paperforge-agent-platform-label"}).setText(o("run_in_agent").replace("{0}",g))}else n==="ready"&&i.createEl("button",{cls:"paperforge-next-step-trigger"}).createEl("span",{text:"\u2713 "+s.label})}_openFulltext(e){if(!e){new M.Notice("[!!] No fulltext path available for this paper",6e3);return}let t=this.app.vault.getAbstractFileByPath(e);t?this.app.workspace.openLinkText(t.path,""):new M.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,c=0,l=0,_=0,f=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"&&c++;let E=y.ocr_status||"";E==="pending"||E==="queued"?l++:E==="processing"?_++:(E==="failed"||E==="blocked"||E==="done_incomplete"||E==="nopdf")&&f++}r.createEl("div",{cls:"paperforge-collection-header"}).createEl("div",{cls:"paperforge-collection-title",text:e});let g=r.createEl("div",{cls:"paperforge-workflow-overview"});g.createEl("div",{cls:"paperforge-section-label",text:"Workflow Overview"});let m=g.createEl("div",{cls:"paperforge-workflow-funnel"}),b=[{value:n,label:"Total"},{value:a,label:"PDF Ready"},{value:s,label:"OCR Done"},{value:c,label:"Deep Read"}];for(let y=0;y0){let y=r.createEl("div",{cls:"paperforge-ocr-section"}),E=y.createEl("div",{cls:"paperforge-collection-ocr-header"});E.createEl("h4",{cls:"paperforge-ocr-title",text:"OCR Pipeline"});let w=E.createEl("span",{cls:"paperforge-ocr-badge idle"});_>0?(w.addClass("active"),w.setText("Processing")):l>0?w.setText("Pending"):(w.addClass("idle"),w.setText("Idle"));let D=y.createEl("div",{cls:"paperforge-progress-track"});_>0&&D.addClass("paperforge-processing");let T=l+_+s+f,I=[{cls:"pending",count:l},{cls:"active",count:_},{cls:"done",count:s},{cls:"failed",count:f}];for(let V of I)if(V.count>0){let A=(V.count/T*100).toFixed(1);D.createEl("div",{cls:`paperforge-progress-seg ${V.cls}`,attr:{style:`width:${A}%`}})}let B=y.createEl("div",{cls:"paperforge-ocr-counts"}),$=[{cls:"pending",value:l,label:"Pending"},{cls:"active",value:_,label:"Processing"},{cls:"done",value:s,label:"Done"},{cls:"failed",value:f,label:"Attention"}];for(let V of $){let A=B.createEl("div",{cls:"paperforge-ocr-count"});A.createEl("div",{cls:"paperforge-ocr-count-value",text:V.value.toString()}),A.createEl("div",{cls:"paperforge-ocr-count-label",text:V.label})}}let h=r.createEl("div",{cls:"paperforge-collection-actions"}),v=h.createEl("button",{cls:"paperforge-contextual-btn primary"});v.createEl("span",{cls:"paperforge-contextual-btn-icon",text:"\u229E"}),v.createEl("span",{text:"Run OCR"}),v.addEventListener("click",()=>{let y=ie.find(E=>E.id==="paperforge-ocr");y&&this._runAction(y,v)});let x=h.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(E=>E.id==="paperforge-sync");y&&this._runAction(y,x)});let k=h.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(E=>E.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 M.Notice("Cannot determine vault path");return}this._versionPapers=vt(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=vt(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 c=a.createEl("div",{cls:"paperforge-version-paper-list"}),l=()=>{c.empty();let v=this._versionFilter.toLowerCase(),x=this._versionPapers?this._versionPapers.filter(y=>!v||y.key.toLowerCase().includes(v)||y.title.toLowerCase().includes(v)):[];if(x.length===0){c.createEl("div",{cls:"paperforge-meta",text:o("version_no_backups")});return}let k=c.createEl("div",{cls:"paperforge-meta",text:o("version_papers_count").replace("{n}",String(x.length))});for(let y of x){let E=c.createEl("div",{cls:"paperforge-version-paper-item"}),w=E.createEl("span",{cls:"paperforge-version-paper-title",text:y.title}),D=E.createEl("span",{cls:"paperforge-version-paper-versions",text:y.versions.map(T=>T.label).join(" ")});E.addEventListener("click",()=>{c.querySelectorAll(".paperforge-version-paper-item.selected").forEach(T=>T.removeClass("selected")),E.addClass("selected"),f(y)})}};i.addEventListener("input",()=>{this._versionFilter=i.value,l()});let _=s.createEl("div",{cls:"paperforge-version-timeline-area"}),f=v=>{if(_.empty(),_.createEl("div",{cls:"paperforge-version-timeline-header"}).createEl("span",{cls:"pf-title",text:v.title}),v.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 v.versions){let E=y.label===v.currentLabel,w=k.createEl("div",{cls:"paperforge-version-entry"+(E?" paperforge-version-current":"")}),D=w.createEl("div",{cls:"paperforge-version-dot"}),T=w.createEl("div",{cls:"paperforge-version-content"}),I=T.createEl("div",{cls:"paperforge-version-label-row"});I.createEl("span",{cls:"paperforge-version-label",text:y.label}),E&&I.createEl("span",{cls:"paperforge-version-current-tag",text:o("version_current")});let B=y.created_at?y.created_at.slice(0,10):"";T.createEl("div",{cls:"paperforge-meta",text:B+" \u2014 "+y.source});let $=y.fulltext_size?y.fulltext_size>1024?(y.fulltext_size/1024).toFixed(0)+"KB":y.fulltext_size+"B":"";$&&T.createEl("div",{cls:"paperforge-meta",text:$});let V=T.createEl("div",{cls:"paperforge-version-actions"});V.createEl("button",{cls:"pf-btn-primary",text:o("version_restore_btn")}).addEventListener("click",()=>{Wt(n,v.key,y.label)?new M.Notice(o("version_restore_done").replace("{label}",y.label)):new M.Notice("Restore failed",6e3)}),v.versions.length>1&&!E&&V.createEl("button",{cls:"pf-btn-secondary",text:o("version_compare_btn")}).addEventListener("click",()=>{g(v,y.label,v.currentLabel)})}},u=s.createEl("div",{cls:"paperforge-version-compare"});u.style.display="none";let g=(v,x,k)=>{let y=Ut(n,v.key,x,k);u.style.display="block",u.empty();let E=u.createEl("div",{cls:"paperforge-version-compare-header"});if(E.createEl("span",{cls:"pf-title",text:o("version_compare_title").replace("{vA}",x).replace("{vB}",k)}),E.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 w=u.createEl("div",{cls:"paperforge-version-diff-list"});for(let D of y){let T=w.createEl("div",{cls:"paperforge-version-diff-row"}),I=D.type==="added"?"[+]":D.type==="removed"?"[-]":"[~]",B=D.heading||"paragraph "+(D.paragraphIndex+1);T.createEl("span",{cls:"paperforge-version-diff-label",text:I+" "+B}),D.oldText&&T.createEl("pre",{cls:"paperforge-version-diff-old",text:D.oldText.slice(0,200)}),D.newText&&T.createEl("pre",{cls:"paperforge-version-diff-new",text:D.newText.slice(0,200)})}},m=e.createEl("div",{cls:"paperforge-version-actions-bar"}),b=m.createEl("button",{cls:"pf-btn-primary",text:o("version_restore_selected")}),h=m.createEl("button",{cls:"pf-btn-secondary",text:o("version_clear_old").replace("{size}","")});l()}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 c=this._searchResults.length;a.key==="ArrowDown"?this._searchActiveIndex=Math.min(this._searchActiveIndex+1,c-1):this._searchActiveIndex=Math.max(this._searchActiveIndex-1,-1);let l=(i=this._searchResultsEl)==null?void 0:i.querySelectorAll(".paperforge-search-result-card");l&&l.forEach((_,f)=>{f===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 c=this._searchMode;this._searchMode="@",this.executeSearch(),this._searchMode=c;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:c=[]}=z(s,null,void 0,void 0);(0,ve.spawn)(i,[...c,"-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 b=a.basePath;s=typeof b=="string"?b:""}if(!s){this._searchState="backend-unavailable",this._renderSearchState();return}let i=null,l=this.app.plugins;if(l&&typeof l=="object"&&"plugins"in l){let b=l.plugins;if(b&&typeof b=="object"&&"paperforge"in b){let h=b.paperforge;h&&typeof h=="object"&&"settings"in h&&(i=h.settings)}}let{path:_,extraArgs:f=[]}=z(s,i,void 0,void 0),u=n==="retrieve"?["--deep"]:[],g=(0,ve.spawn)(_,[...f,"-m","paperforge","--vault",s,n,r,...u,"--json"],{cwd:s,timeout:3e4}),m=[];g.stdout.on("data",b=>{m.push(b.toString("utf-8"))}),g.stderr.on("data",()=>{}),g.on("close",b=>{if(b!==0){let y=lt(String(b));this._searchState=this._mapErrorToSearchState(y.type),this._renderSearchState();return}let h=m.join(""),v=h.indexOf("{"),x=h.lastIndexOf("}"),k="";if(v!==-1&&x>v)k=h.slice(v,x+1);else{let y=h.indexOf("["),E=h.lastIndexOf("]");y!==-1&&E>y&&(k=h.slice(y,E+1))}if(!k){this._searchState="internal-error",this._renderSearchState();return}try{let y=JSON.parse(k),E=[];if(y&&typeof y=="object"&&"data"in y){let w=y.data;if(w&&typeof w=="object"){let D=w;"matches"in D&&Array.isArray(D.matches)&&(E=D.matches)}}this._searchResults=E,this._searchState=E.length>0?"results":"empty",this._renderSearchState()}catch(y){let E=y instanceof Error?y.message:String(y);this._searchState="internal-error",this._renderSearchState()}}),g.on("error",b=>{let h=b.code;if(typeof h=="string"){let v=lt(h);this._searchState=this._mapErrorToSearchState(v.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;ax!==null&&typeof x=="object"&&"zotero_key"in x&&x.zotero_key===f);if(v&&typeof v=="object"){let x=v;m=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}}m?l.addEventListener("click",h=>{let v=h.ctrlKey||h.metaKey;this.app.workspace.openLinkText(m,"",v)}):l.addEventListener("click",()=>{new M.Notice("[!!] Note not found: "+(f||"unknown"),6e3)}),l.addEventListener("keydown",h=>{if(h.key==="Enter"&&m){h.preventDefault();let v=h.ctrlKey||h.metaKey;this.app.workspace.openLinkText(m,"",v)}});let b=l.createEl("div",{cls:"paperforge-search-result-meta"});if(typeof i.first_author=="string"&&i.first_author&&b.createEl("span",{cls:"paperforge-search-result-author",text:i.first_author}),typeof i.journal=="string"&&i.journal&&b.createEl("span",{cls:"paperforge-search-result-journal",text:i.journal}),i.score!==void 0){let h=i.score,v=typeof h=="number"?h.toFixed(3):String(h);b.createEl("span",{cls:"paperforge-search-result-score",text:"Score: "+v})}if(typeof i.domain=="string"&&i.domain&&l.createEl("span",{cls:"paperforge-search-result-tag",text:i.domain}),typeof i.abstract=="string"&&i.abstract){let h=i.abstract;l.createEl("div",{cls:"paperforge-search-result-abstract",text:h.length>200?h.slice(0,200)+"...":h})}if(t&&typeof i.text=="string"&&i.text){let h=i.text;l.createEl("div",{cls:"paperforge-search-result-source",text:h.length>300?h.slice(0,300)+"...":h})}}}_renderSearchError(e){this._searchResultsEl&&(this._searchResultsEl.empty(),this._searchResultsEl.createEl("div",{cls:"paperforge-search-error",text:e}))}_runAction(e,t){var u,g;if(e.disabled){new M.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 m=this.app.workspace.getActiveFile(),b=null;if(m){let h=this.app.metadataCache.getFileCache(m);if(h&&h.frontmatter&&h.frontmatter.zotero_key?b=h.frontmatter.zotero_key:b=this._extractZoteroKeyFromPath(m.path),b)n=[...n,b];else if(h&&h.frontmatter){this._showMessage("[!!] No zotero_key in active note frontmatter","error"),new M.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 M.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 M.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,(g=(u=this.app.plugins.plugins.paperforge)==null?void 0:u.settings)!=null?g:null,void 0,void 0),c=(0,ve.spawn)(s,[...i,"-m","paperforge",e.cmd,...n],{cwd:r,timeout:a}),l=[],_=Date.now(),f=setInterval(()=>this._fetchStats(!0),4e3);c.stdout.on("data",m=>{let b=m.toString("utf-8").split(`
+`).filter(Boolean);for(let h of b){let v=h.trim();v&&(l.push(v),this._showMessage(l.slice(-8).join(`
+`),"running"))}}),c.stderr.on("data",m=>{let b=m.toString("utf-8").split(`
+`).filter(Boolean);for(let h of b){if(h.includes("\r")||h.includes("%")||h.includes("\u2588"))continue;let v=h.trim();v&&!v.match(/^\d+%|^\|/)&&(l.push(v),this._showMessage(l.slice(-8).join(`
+`),"running"))}}),c.on("close",m=>{clearInterval(f),t.removeClass("running");let b=((Date.now()-_)/1e3).toFixed(1);if(m!==0){let h=l.slice(-3).join(" | ")||"exit code "+m;(e.cmd==="repair"||e.cmd==="ocr")&&m===1?(this._showMessage("[WARN] "+h,"running"),new M.Notice("[WARN] "+e.cmd+" partial: "+h,8e3),this._fetchStats(!0)):(this._showMessage("[!!] "+h,"error"),new M.Notice("[!!] "+e.cmd+" failed: "+h,8e3))}else if(e.needsKey||e.needsFilter){let h=l.join(`
+`);if(h.trim())try{JSON.parse(h),navigator.clipboard.writeText(h).then(()=>{let v=`${b}s \u2014 ${h.length} chars copied`;this._showMessage("[OK] "+e.title+": "+v,"ok"),new M.Notice("[OK] "+e.okMsg+" \u2014 "+h.length+" chars")}).catch(v=>{this._showMessage("[!!] Clipboard write failed: "+v.message,"error"),new M.Notice("[!!] Clipboard error",6e3)})}catch(v){this._showMessage("[!!] Invalid JSON from "+e.title,"error"),new M.Notice("[!!] "+e.title+" returned invalid JSON: "+v.message.slice(0,100),8e3)}else this._showMessage("[!!] No output from context command","error"),new M.Notice("[!!] Context command returned empty output",8e3);this._fetchStats(!0)}else{let v=l.filter(k=>k.match(/updated \d+/)).pop()||l[l.length-1]||"",x=`${b}s \u2014 ${v}`;this._showMessage("[OK] "+e.title+": "+x,"ok"),new M.Notice("[OK] "+e.okMsg),this._contentEl&&this._contentEl.removeClass("switching"),this._cachedStats=null;try{this._fetchStats(!1)}catch(k){console.log("[PF] fetchStats error:",k)}console.log("[PF] close cmd="+e.cmd+" id="+e.id),e.cmd==="sync"&&Ze(this.app,this.app.plugins.plugins.paperforge,r)}}),c.on("error",m=>{t.removeClass("running"),this._contentEl&&this._contentEl.removeClass("switching"),this._showMessage("[!!] "+m.message,"error"),new M.Notice("[!!] Cannot start: "+m.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(ke);if(t.length>0){e.app.workspace.revealLeaf(t[0]);return}let r=e.app.workspace.getRightLeaf(!1);r&&(await r.setViewState({type:ke,active:!0}),e.app.workspace.revealLeaf(r))}};var Xe=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(),Pt(this.app),this.registerView(ke,t=>new Te(t));try{(0,Y.addIcon)(Be,Et)}catch(t){}this.addRibbonIcon(Be,"PaperForge Dashboard",()=>Te.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 Ye(this.app,this)),this.addCommand({id:"paperforge-status-panel",name:`PaperForge: ${o("guide_open")}`,callback:()=>Te.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,c,l)=>{if(i){new Y.Notice(`[!!] ${t.cmd} failed: ${(l||i.message).slice(0,120)}`,8e3);return}new Y.Notice(`[OK] ${t.okMsg||c.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=le(e).memoryStatePath;if(!U.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=(c,l)=>{(0,ge.spawn)(t,[...r,"-m","pip","install","--upgrade",c],{cwd:e,timeout:12e4,env:we()}).on("close",f=>l(f===0))};(0,ge.execFile)(t,[...r,"-c","import paperforge; print(paperforge.__version__)"],{cwd:e,timeout:1e4},(c,l)=>{let _=u=>{console.log(`[PaperForge] Auto-update: trying PyPI (paperforge==${n})`),i(a,g=>{if(g){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,m=>{m&&(console.log("[PaperForge] Auto-update: installed via git"),new Y.Notice(`[OK] PaperForge CLI ${u} (via git)`,5e3))})})};if(c){_("installed");return}let f=l.trim();f!==n&&_(`${f} -> ${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=le(e).exportsDir;if(!U.existsSync(t))return;let r=0;try{U.readdirSync(t).forEach(n=>{if(!n.endsWith(".json"))return;let a=U.statSync(Ae.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=le(e).exportsDir,c=0;U.readdirSync(i).forEach(l=>{l.endsWith(".json")&&(c=Math.max(c,U.statSync(Ae.join(i,l)).mtimeMs))}),this._lastExportMtime=c}catch(i){}})}_checkOcr(e){if(this._autoSyncRunning)return;let t=le(e).ocrDir;if(U.existsSync(t))try{U.readdirSync(t,{withFileTypes:!0}).forEach(r=>{if(!r.isDirectory())return;let n=Ae.join(t,r.name,"meta.json");if(!U.existsSync(n))return;let a=U.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 c=`"${i.path}" -m paperforge --vault "${e}" sync`;(0,ge.exec)(c,{timeout:3e4,encoding:"utf-8"},()=>{this._autoSyncRunning=!1,this._memoryStatusText=null})})}catch(r){}}readPaperforgeJson(){let e=this.app.vault.adapter.basePath,t=Ae.join(e,"paperforge.json"),r={system_dir:"System",resources_dir:"Resources",literature_dir:"Literature",base_dir:"Bases"};try{if(!U.existsSync(t))return r;let n=U.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=Ae.join(t,"paperforge.json"),n={};try{U.existsSync(r)&&(n=JSON.parse(U.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(U.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(ke)}async loadSettings(){this.settings=Object.assign({},Oe,await this.loadData()),this.settings.features&&Oe.features&&(this.settings.features=Object.assign({},Oe.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();U.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(Oe))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=(it().versions||[]).find(i=>i.version===e);class s extends Y.Modal{constructor(c,l){super(c),this._entry=l}onOpen(){let{contentEl:c}=this;if(c.createEl("h2",{text:`PaperForge v${e} \u66F4\u65B0\u8BF4\u660E`}),this._entry){if(c.createEl("p",{text:this._entry.title,cls:"paperforge-modal-subtitle"}),this._entry.breaking_or_migration&&this._entry.breaking_or_migration.length>0){c.createEl("h4",{text:"\u884C\u4E3A\u53D8\u66F4 / \u8FC1\u79FB\u6CE8\u610F"});for(let l of this._entry.breaking_or_migration)c.createEl("p",{text:`\u2022 ${l}`,cls:"paperforge-modal-item"})}if(this._entry.new_features&&this._entry.new_features.length>0){c.createEl("h4",{text:"\u65B0\u529F\u80FD"});for(let l of this._entry.new_features)c.createEl("p",{text:`\u2022 ${l}`,cls:"paperforge-modal-item"})}if(this._entry.fixes&&this._entry.fixes.length>0){c.createEl("h4",{text:"\u4FEE\u590D"});for(let l of this._entry.fixes)c.createEl("p",{text:`\u2022 ${l}`,cls:"paperforge-modal-item"})}if(this._entry.recommended_actions&&this._entry.recommended_actions.length>0){let l=c.createEl("div",{cls:"paperforge-release-recommended"});l.createEl("h4",{text:"\u5EFA\u8BAE\u64CD\u4F5C",cls:""}),l.style.marginBottom="8px";for(let _ of this._entry.recommended_actions)l.createEl("p",{text:`\u2022 ${_}`,cls:"paperforge-release-item-bold"})}}else c.createEl("p",{text:"\u7248\u672C\u5DF2\u66F4\u65B0\u81F3 v"+e+"\uFF0C\u8BF7\u524D\u5F80\u8BBE\u7F6E \u2192 \u66F4\u65B0\u4E0E\u624B\u518C \u67E5\u770B\u5B8C\u6574\u66F4\u65B0\u8BB0\u5F55\u3002"});new Y.Setting(c).addButton(l=>l.setButtonText("\u77E5\u9053\u4E86").setCta().onClick(()=>{this.close()}))}onClose(){let{contentEl:c}=this;c.empty()}}new s(this.app,a).open(),this.settings.last_seen_version=e,this.saveSettings()}};
diff --git a/paperforge/plugin/src/constants.ts b/paperforge/plugin/src/constants.ts
index 7843f98a..28f5140d 100644
--- a/paperforge/plugin/src/constants.ts
+++ b/paperforge/plugin/src/constants.ts
@@ -88,6 +88,7 @@ export interface PaperForgeSettings {
vector_db_api_base: string;
vector_db_api_model: string;
last_seen_version: string;
+ capabilityState: Record;
_python_path_stale?: boolean;
[key: string]: unknown;
}
@@ -115,6 +116,7 @@ export const DEFAULT_SETTINGS: PaperForgeSettings = {
resources_dir: "",
literature_dir: "",
base_dir: "",
+ capabilityState: {},
last_seen_version: "",
};
@@ -141,3 +143,304 @@ export function overlayEntryWorkflowState(app: any, entry: any): WorkflowState {
export function patchEntryWorkflowState(entry: any, patch: Partial): any {
return entry ? { ...entry, ...patch } : entry;
}
+
+// ── Capability probe types (schema-v1) ──
+
+export const SCHEMA_VERSION = 1;
+
+export type CapabilityModule = "installation" | "help" | "library" | "ocr" | "memory" | "maintenance";
+
+export const CAPABILITY_MODULES: readonly CapabilityModule[] = [
+ "installation",
+ "library",
+ "ocr",
+ "memory",
+ "maintenance",
+ "help",
+] as const;
+
+export type CapabilityState = "unknown" | "unavailable" | "missing_input" | "needs_action" | "limited" | "ready";
+export type ActivityState = "idle" | "running";
+export type Severity = "unknown" | "ok" | "warning" | "error";
+
+export interface ActionPrimary {
+ verb: string;
+ label: string;
+ destructive: boolean;
+ destructive_scope: string | null;
+ destructive_effect: string | null;
+ confirmation_required: boolean;
+ confirmation_prompt: string | null;
+ command: string;
+ scope: string;
+ scope_count: number;
+}
+
+export interface ProbeReason {
+ code: string;
+ text: string;
+}
+
+export interface ProbeAction {
+ primary: ActionPrimary | null;
+}
+
+export interface ProbeNotice {
+ level: string;
+ message: string;
+}
+
+export interface ProbeActivityProgress {
+ current: number;
+ total: number;
+}
+
+export interface ProbeEnvelope {
+ schema_version: number;
+ module: string;
+ capability_state: CapabilityState;
+ activity_state: ActivityState;
+ activity_label: string | null;
+ activity_progress: ProbeActivityProgress | null;
+ severity: Severity;
+ reason: ProbeReason;
+ action: ProbeAction;
+ notices: ProbeNotice[];
+ updated_at: string;
+ ttl_seconds: number;
+}
+
+const VALID_CAPABILITY_STATES: readonly string[] = ["unknown", "unavailable", "missing_input", "needs_action", "limited", "ready"];
+const VALID_SEVERITIES: readonly string[] = ["unknown", "ok", "warning", "error"];
+const VALID_ACTIVITY_STATES: readonly string[] = ["idle", "running"];
+
+function isValidActionPrimary(p: unknown): p is ActionPrimary {
+ if (!p || typeof p !== "object" || Array.isArray(p)) return false;
+ const a = p as Record;
+ if (typeof a.verb !== "string") return false;
+ if (typeof a.label !== "string") return false;
+ if (typeof a.destructive !== "boolean") return false;
+ if (a.destructive_scope !== null && typeof a.destructive_scope !== "string") return false;
+ if (a.destructive_effect !== null && typeof a.destructive_effect !== "string") return false;
+ if (typeof a.confirmation_required !== "boolean") return false;
+ if (a.confirmation_prompt !== null && typeof a.confirmation_prompt !== "string") return false;
+ if (typeof a.command !== "string") return false;
+ if (typeof a.scope !== "string") return false;
+ if (typeof a.scope_count !== "number") return false;
+ return true;
+}
+
+/** Build a full probe action (non-destructive, probe verb). */
+export function probeAction(module: string): ActionPrimary {
+ return {
+ verb: "probe",
+ label: "Check",
+ destructive: false,
+ destructive_scope: null,
+ destructive_effect: null,
+ confirmation_required: false,
+ confirmation_prompt: null,
+ command: `probe ${module}`,
+ scope: module,
+ scope_count: 1,
+ };
+}
+
+/** Build a full setup action. */
+export function setupAction(): ActionPrimary {
+ return {
+ verb: "setup",
+ label: "Open Setup Wizard",
+ destructive: false,
+ destructive_scope: null,
+ destructive_effect: null,
+ confirmation_required: false,
+ confirmation_prompt: null,
+ command: "setup",
+ scope: "installation",
+ scope_count: 1,
+ };
+}
+
+/**
+ * Validate that a parsed JSON value is a valid ProbeEnvelope.
+ * When expectedModule is supplied, also checks module field matches.
+ * Never reconstructs or coerces — passes validated object through unchanged.
+ */
+export function isValidEnvelope(raw: unknown, expectedModule?: string): raw is ProbeEnvelope {
+ if (!raw || typeof raw !== "object") return false;
+ const e = raw as Record;
+
+ if (e.schema_version !== 1) return false;
+ if (typeof e.module !== "string" || !e.module) return false;
+ if (!CAPABILITY_MODULES.includes(e.module as CapabilityModule)) return false;
+ if (expectedModule !== undefined && e.module !== expectedModule) return false;
+ if (typeof e.capability_state !== "string" || !VALID_CAPABILITY_STATES.includes(e.capability_state)) return false;
+ if (typeof e.severity !== "string" || !VALID_SEVERITIES.includes(e.severity)) return false;
+ if (typeof e.activity_state !== "string" || !VALID_ACTIVITY_STATES.includes(e.activity_state)) return false;
+
+ // activity_label: null or string (required)
+ if (e.activity_label !== null && typeof e.activity_label !== "string") return false;
+
+ // activity_progress: null or {current:number, total:number} (required)
+ if (e.activity_progress !== null) {
+ if (typeof e.activity_progress !== "object") return false;
+ const ap = e.activity_progress as Record;
+ if (typeof ap.current !== "number" || typeof ap.total !== "number") return false;
+ }
+
+ if (!Array.isArray(e.notices)) return false;
+ if (!e.reason || typeof e.reason !== "object") return false;
+ const r = e.reason as Record;
+ if (typeof r.code !== "string" || typeof r.text !== "string") return false;
+ if (!e.action || typeof e.action !== "object") return false;
+ const a = e.action as Record;
+ if (a.primary !== null && !isValidActionPrimary(a.primary)) return false;
+ if (typeof e.updated_at !== "string" || !e.updated_at) return false;
+ if (typeof e.ttl_seconds !== "number") return false;
+
+ return true;
+}
+
+/** Unknown envelope — all modules get verb=probe; setup verb is backend-only. */
+export function createUnknownEnvelope(module: CapabilityModule): ProbeEnvelope {
+ return {
+ schema_version: SCHEMA_VERSION, module,
+ capability_state: "unknown",
+ activity_state: "idle",
+ activity_label: null,
+ activity_progress: null,
+ severity: "unknown",
+ reason: { code: `${module}.no_probe`, text: `${module} has not been probed yet.` },
+ action: { primary: probeAction(module) },
+ notices: [],
+ updated_at: new Date(0).toISOString(),
+ ttl_seconds: 0,
+ };
+}
+
+export function createStaleEnvelope(module: CapabilityModule): ProbeEnvelope {
+ return {
+ schema_version: SCHEMA_VERSION, module,
+ capability_state: "unknown",
+ activity_state: "idle",
+ activity_label: null,
+ activity_progress: null,
+ severity: "unknown",
+ reason: { code: `${module}.stale`, text: `Cached probe data for ${module} is stale.` },
+ action: { primary: probeAction(module) },
+ notices: [],
+ updated_at: new Date(0).toISOString(),
+ ttl_seconds: 0,
+ };
+}
+
+export function createInvalidEnvelope(module: CapabilityModule): ProbeEnvelope {
+ return {
+ schema_version: SCHEMA_VERSION, module,
+ capability_state: "unknown",
+ activity_state: "idle",
+ activity_label: null,
+ activity_progress: null,
+ severity: "unknown",
+ reason: { code: `${module}.invalid_response`, text: `Probe response for ${module} was invalid.` },
+ action: { primary: probeAction(module) },
+ notices: [],
+ updated_at: new Date(0).toISOString(),
+ ttl_seconds: 0,
+ };
+}
+
+export function isEnvelopeStale(e: ProbeEnvelope): boolean {
+ // Running state is always fresh — don't erase in-progress probes
+ if (e.activity_state === "running") return false;
+ if (e.ttl_seconds <= 0) return true;
+ const updated = new Date(e.updated_at).getTime();
+ if (isNaN(updated)) return true;
+ return Date.now() - updated > e.ttl_seconds * 1000;
+}
+
+export function isReadyEnvelope(e: ProbeEnvelope): boolean {
+ return e.capability_state === "ready" && e.action.primary === null;
+}
+
+/**
+ * Classify a capability envelope's primary action.
+ * - 'set_config' and 'update' verbs → 'setup' kind (dispatch to setup flow)
+ * - 'probe' verb → 'probe' kind (trigger a re-probe)
+ * - all others → 'action' kind (execute the action directly)
+ */
+export function classifyCapabilityAction(
+ envelope: ProbeEnvelope
+): { kind: 'setup' | 'probe' | 'action'; verb: string; label: string } {
+ const primary = envelope.action?.primary;
+ const verb = primary?.verb ?? 'probe';
+ const label = primary?.label ?? verb;
+ if (verb === 'setup' || verb === 'set_config' || verb === 'update') {
+ return { kind: 'setup', verb, label };
+ }
+ if (verb === 'probe') {
+ return { kind: 'probe', verb, label };
+ }
+ return { kind: 'action', verb, label };
+}
+
+/**
+ * Compute a summary from a capability-state map.
+ * Returns { coreReady: true } when all REAL_PROBED_MODULES are 'ready',
+ * otherwise { coreReady: false, attentionModules: [...] } with unknown
+ * degraded modules counted as attention.
+ */
+export function computeModuleSummary(
+ stateMap: Record,
+ realModules: readonly string[]
+): { coreReady: boolean; attentionModules: string[] } {
+ const attentionModules: string[] = [];
+ for (const mod of realModules) {
+ const env = stateMap[mod];
+ if (!env || !isReadyEnvelope(env)) {
+ attentionModules.push(mod);
+ }
+ }
+ return {
+ coreReady: attentionModules.length === 0,
+ attentionModules,
+ };
+}
+
+/**
+ * Validate a persisted capability-state map before render.
+ * - Missing required modules → replaced with unknown envelope
+ * - Malformed entries (failing isValidEnvelope) → replaced with invalid envelope
+ * - Stale entries (past TTL) → replaced with stale envelope via createStaleEnvelope
+ * Valid entries pass through unchanged.
+ */
+export function validatePersistedEnvelopes(
+ stateMap: Record,
+ allModules: readonly string[]
+): Record {
+ const result: Record = {};
+
+ for (const mod of allModules) {
+ const raw = stateMap[mod];
+
+ if (!raw || typeof raw !== 'object') {
+ result[mod] = createUnknownEnvelope(mod as CapabilityModule);
+ continue;
+ }
+
+ if (!isValidEnvelope(raw, mod)) {
+ result[mod] = createInvalidEnvelope(mod as CapabilityModule);
+ continue;
+ }
+
+ if (isEnvelopeStale(raw as ProbeEnvelope)) {
+ result[mod] = createStaleEnvelope(mod as CapabilityModule);
+ continue;
+ }
+
+ result[mod] = raw as ProbeEnvelope;
+ }
+
+ return result;
+}
diff --git a/paperforge/plugin/src/i18n.ts b/paperforge/plugin/src/i18n.ts
index ddf48b4a..76f4be14 100644
--- a/paperforge/plugin/src/i18n.ts
+++ b/paperforge/plugin/src/i18n.ts
@@ -332,8 +332,69 @@ const LANG: Record> = {
retrieval_force_rebuild: "Force Rebuild",
retrieval_rebuild_warning:
"Rebuild will replace {n} existing chunk(s). Continue?",
+ // ── Control Center (Issue #76) ──
+ 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 — 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: "正在执行 ",
api_key_missing: "未配置 ✗",
@@ -627,6 +688,10 @@ const LANG: Record> = {
retrieval_build_idle: "向量数据库就绪",
retrieval_build_ready: "已构建 {n} 个向量",
retrieval_build_stopping: "正在停止...",
+ cc_summary_attention: "部分模块需要关注",
+ cc_summary_ok_body: "PaperForge 环境已完全就绪。安装和帮助文档均正常可用。",
+ cc_summary_core_ok_body: "安装和帮助模块已可用。文献索引、OCR、记忆层和维护模块将在后端接入后显示实时状态。",
+ cc_summary_attention_body: "一个或多个核心模块需要您的关注才能正常运行。",
retrieval_build_stopped: "构建已停止({n}/{t} 篇)",
retrieval_build_failed: "构建失败",
retrieval_build_stale: "向量已过期 — 建议重建",
@@ -637,6 +702,63 @@ const LANG: Record> = {
retrieval_internal_error: "发生内部错误",
retrieval_force_rebuild: "强制重建",
retrieval_rebuild_warning: "重建将替换 {n} 个现有文本块,是否继续?",
+ cc_summary_ok: "全部正常",
+ cc_summary_core_ok: "核心环境正常;{n} 个模块状态检测待接入",
+ cc_badge_ok: "已就绪",
+ cc_badge_pending: "待接入",
+ cc_badge_setup: "需要安装",
+ cc_badge_attention: "需要注意",
+ cc_diagnostic_toggle: "详情",
+ cc_n_ready: "{n} 已就绪",
+ cc_n_pending: "{n} 待接入",
+ // ── Control Center (Issue #76) ──
+ cc_title: "系统状态",
+ cc_desc: "PaperForge 核心模块的实时状态。有待处理操作的模块需要您的关注。",
+ cc_zone_attention: "需要关注",
+ cc_zone_modules: "所有模块",
+ cc_module_installation: "安装",
+ cc_module_help: "帮助与文档",
+ cc_module_library: "文献索引",
+ cc_module_ocr: "OCR 引擎",
+ cc_module_memory: "记忆层",
+ cc_module_maintenance: "维护",
+ cc_state_ready: "就绪",
+ cc_state_limited: "受限",
+ cc_state_unavailable: "不可用",
+ cc_state_unknown: "未知",
+ cc_severity_ok: "正常",
+ cc_severity_unknown: "未知",
+ cc_severity_warning: "警告",
+ cc_severity_error: "错误",
+ cc_state_missing_input: "缺少输入",
+ cc_state_needs_action: "需要操作",
+ cc_action_setup: "打开安装向导",
+ cc_action_probe: "检测",
+ cc_action_set_config: "配置设置",
+ cc_action_update: "更新",
+ cc_reason_installation_ready: "PaperForge 环境已正确配置。",
+ cc_reason_config_missing: "配置文件缺失,请运行安装向导。",
+ cc_reason_config_corrupt: "配置文件损坏,请运行安装向导修复。",
+ cc_reason_python_version_unsupported: "Python 版本不受支持,请安装 Python 3.11+。",
+ cc_reason_help_ready: "帮助文档已可用。",
+ cc_reason_docs_missing: "帮助文档尚未安装。",
+ cc_reason_placeholder: "状态检测待接入,接入后将显示实时状态。",
+ cc_diag_module: "模块",
+ cc_diag_state: "状态",
+ cc_diag_severity: "严重程度",
+ cc_diag_activity: "活动",
+ cc_diag_reason: "原因",
+ cc_diag_ttl: "TTL",
+ cc_diag_updated: "更新时间",
+ cc_reason_no_probe: "{module} 尚未检测。",
+ cc_reason_stale: "{module} 的检测数据已过期。",
+ cc_reason_invalid_response: "{module} 的检测响应无效。",
+ cc_reason_probing: "正在检测 {module} 状态...",
+ cc_notice_placeholder: "{module} 模块检测功能尚未可用,将在后续版本中添加。",
+ cc_notice_refreshed: "模块状态已刷新。",
+ cc_notice_refresh_failed: "模块状态刷新失败。",
+ cc_activity_idle: "空闲",
+ cc_activity_running: "运行中",
},
};
diff --git a/paperforge/plugin/src/settings.ts b/paperforge/plugin/src/settings.ts
index 62416100..db1d62e6 100644
--- a/paperforge/plugin/src/settings.ts
+++ b/paperforge/plugin/src/settings.ts
@@ -4,7 +4,7 @@ import * as path from "path";
import * as os from "os";
import { execFile, execFileSync, spawn, exec } from "child_process";
import { t, setLanguage } from "./i18n";
-import { PaperForgeSettings } from "./constants";
+import { PaperForgeSettings, ProbeEnvelope, CapabilityModule, CAPABILITY_MODULES, createUnknownEnvelope, createStaleEnvelope, createInvalidEnvelope, isValidEnvelope, isEnvelopeStale, isReadyEnvelope, probeAction, validatePersistedEnvelopes, classifyCapabilityAction } from "./constants";
import releaseNotesData from "./release-notes.json";
import {
resolvePythonExecutable,
@@ -90,6 +90,12 @@ export class PaperForgeSettingTab extends PluginSettingTab {
total: 0,
key: "",
};
+ /** Cached capability probe envelopes, keyed by module name. */
+ private _capabilityState: Record | null = null;
+ /** Tracks which modules are currently being probed. */
+ private _probing: Set = new Set();
+ /** Modules that have already been auto-probed (prevents endless re-probe). */
+ private _attemptedProbes: Set = new Set();
constructor(app: App, plugin: ISettingPlugin) {
super(app, plugin as any);
@@ -105,6 +111,8 @@ export class PaperForgeSettingTab extends PluginSettingTab {
const { containerEl } = this;
containerEl.empty();
this._refreshPfConfig();
+ this._initCapabilityState();
+ this._applyStaleTolerance();
// Inject tab CSS once
if (!document.getElementById("paperforge-tab-styles")) {
@@ -181,7 +189,6 @@ export class PaperForgeSettingTab extends PluginSettingTab {
this._renderReleaseNotesTab(tabContents["release-notes"]);
}
}
-
_renderSetupTab(containerEl: HTMLElement) {
const vaultPath = (this.app.vault.adapter as any).basePath as string;
if (!this.plugin.settings.vault_path) {
@@ -189,259 +196,24 @@ export class PaperForgeSettingTab extends PluginSettingTab {
this._debouncedSave();
}
- /* Validate setup_complete against paperforge.json */
- if (this.plugin.settings.setup_complete) {
- if (!fs.existsSync(path.join(vaultPath, "paperforge.json"))) {
- this.plugin.settings.setup_complete = false;
- this._debouncedSave();
- }
- }
-
- /* Header */
+ /* Header — brief, not authoritative status (that's the control center) */
containerEl.createEl("h2", { text: t("header_title") || "PaperForge" });
containerEl.createEl("p", {
text: t("desc"),
cls: "paperforge-settings-desc",
});
- /* Setup Status */
- const statusRow = containerEl.createEl("div", {
- cls: "paperforge-setup-bar",
- });
- const statusLabel = statusRow.createEl("span", {
- cls: "paperforge-setup-label",
- });
- if (this.plugin.settings.setup_complete) {
- statusLabel.setText(t("setup_done"));
- statusLabel.addClass("paperforge-setup-done");
- } else {
- statusLabel.setText(t("setup_pending"));
- statusLabel.addClass("paperforge-setup-pending");
- }
+ // ── Control Center (Issue #76) ──
+ this._renderControlCenter(containerEl);
- /* Python Interpreter Section */
- const vaultPathForPython = (this.app.vault.adapter as any)
- .basePath as string;
- const pyResult = resolvePythonExecutable(
- vaultPathForPython,
- this.plugin.settings,
- undefined,
- undefined
- );
- const pyPath = pyResult.path;
- const pySource = this.plugin.settings._python_path_stale
- ? "stale"
- : pyResult.source;
-
- const pyInterpSetting = new Setting(containerEl)
- .setName(t("field_python_interp"))
- .setDesc(this._getPythonDesc(pyPath, pySource));
- this._pythonInterpDescEl = pyInterpSetting.descEl;
-
- const customSetting = new Setting(containerEl)
- .setName(t("field_python_custom"))
- .setDesc("");
- this._customPathDescEl = customSetting.descEl;
-
- customSetting.addText((text) => {
- text
- .setPlaceholder("e.g. C:\\Python310\\python.exe")
- .setValue(this.plugin.settings.python_path || "")
- .onChange((value) => {
- this.plugin.settings.python_path = value;
- this.plugin.saveSettings();
-
- if (value && value.trim()) {
- const exists = fs.existsSync(value.trim());
- this.plugin.settings._python_path_stale = !exists;
- } else {
- this.plugin.settings._python_path_stale = false;
- }
-
- const pyResult2 = resolvePythonExecutable(
- (this.app.vault.adapter as any).basePath as string,
- this.plugin.settings,
- undefined,
- undefined
- );
- const pySource2 = this.plugin.settings._python_path_stale
- ? "stale"
- : pyResult2.source;
- if (this._pythonInterpDescEl) {
- this._pythonInterpDescEl.textContent = this._getPythonDesc(
- pyResult2.path,
- pySource2
- );
- }
- });
- });
-
- customSetting.addButton((btn) => {
- btn
- .setButtonText(t("btn_validate"))
- .onClick(() => this._validatePythonOverride());
- });
-
- /* Runtime Health Section */
- containerEl.createEl("h3", { text: t("runtime_health") });
- containerEl.createEl("p", {
- text: t("runtime_health_desc"),
- cls: "paperforge-settings-desc",
- });
-
- const versionRow = new Setting(containerEl)
- .setName("PaperForge")
- .setDesc(t("runtime_health_checking"));
-
- const badgeEl = versionRow.descEl.createEl("span", {
- cls: "paperforge-runtime-badge",
- });
- let syncBtn: any = null;
-
- versionRow.addButton((btn) => {
- syncBtn = btn;
- btn
- .setButtonText(t("runtime_health_sync"))
- .setDisabled(true)
- .onClick(() => this._syncRuntime(btn));
- });
-
- {
- const vp = (this.app.vault.adapter as any).basePath as string;
- const { path: pythonExe, extraArgs = [] } = resolvePythonExecutable(
- vp,
- this.plugin.settings,
- undefined,
- undefined
- );
- const pluginVer = this.plugin.manifest.version || "?";
-
- execFile(
- pythonExe,
- [
- ...extraArgs,
- "-c",
- "import paperforge; print(paperforge.__version__)",
- ],
- { cwd: vp, timeout: 10000 },
- (err, stdout) => {
- const setupDone = this.plugin.settings.setup_complete;
- const pyVer = !err && stdout ? stdout.trim() : null;
- const descText = pyVer
- ? `${t("runtime_health_plugin_ver").replace("{0}", pluginVer)} \u2192 ${t("runtime_health_package_ver").replace("{0}", pyVer)}`
- : setupDone
- ? `Plugin v${pluginVer} \u2192 Python package not installed. Click "Sync Runtime" to install.`
- : `Plugin v${pluginVer} \u2192 Not configured. Please open the setup wizard first.`;
- versionRow.setDesc(descText);
- if (pyVer === pluginVer) {
- badgeEl.setText(t("runtime_health_match"));
- badgeEl.className = "paperforge-runtime-badge match";
- if (syncBtn) syncBtn.setDisabled(true);
- } else if (pyVer) {
- badgeEl.setText(t("runtime_health_mismatch"));
- badgeEl.className = "paperforge-runtime-badge mismatch";
- if (syncBtn) syncBtn.setDisabled(false);
- } else {
- badgeEl.setText(setupDone ? "Not installed" : "Setup needed");
- badgeEl.className = "paperforge-runtime-badge missing";
- if (syncBtn) syncBtn.setDisabled(false);
- }
+ // Auto-probe never-probed/migrated modules once per session
+ for (const mod of CAPABILITY_MODULES) {
+ 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") {
+ this._probeModule(mod);
}
- );
- }
-
- /* Preparation Guide */
- containerEl.createEl("h3", { text: t("section_prep") });
- containerEl.createEl("p", {
- text: t("section_prep_desc"),
- cls: "paperforge-settings-desc",
- });
- const prep = containerEl.createEl("div", { cls: "paperforge-guide" });
- const prepData = [
- ["prep_python", "prep_python_desc"],
- ["prep_zotero", "prep_zotero_desc"],
- ["prep_bbt", "prep_bbt_desc"],
- ["prep_key", "prep_key_desc"],
- ];
- for (const [kTitle, kDesc] of prepData) {
- const row = prep.createEl("div", { cls: "paperforge-guide-item" });
- row.createEl("strong", { text: t(kTitle) });
- row.createEl("span", { text: " \u2014 " + t(kDesc) });
- }
-
- /* Pre-check status area */
- this._checkEl = containerEl.createEl("div", { cls: "paperforge-message" });
-
- /* Install / Reconfigure Button */
- const needSetup = !this.plugin.settings.setup_complete;
- new Setting(containerEl)
- .setName(t(needSetup ? "btn_install" : "btn_reconfig"))
- .setDesc(t(needSetup ? "btn_install_desc" : "btn_reconfig_desc"))
- .addButton((btn) => {
- btn
- .setButtonText(t(needSetup ? "btn_install" : "btn_reconfig"))
- .setCta()
- .onClick(() => {
- if (!needSetup) {
- new PaperForgeSetupModal(this.app, this.plugin).open();
- } else {
- this._preCheck(() => {
- new PaperForgeSetupModal(this.app, this.plugin).open();
- });
- }
- });
- });
-
- /* Operation Guide */
- containerEl.createEl("h3", { text: t("section_guide") });
- const guide = containerEl.createEl("div", { cls: "paperforge-guide" });
- const guideData = [
- ["guide_open", "guide_open_desc"],
- ["guide_sync", "guide_sync_desc"],
- ["guide_ocr", "guide_ocr_desc"],
- ];
- for (const [kTitle, kDesc] of guideData) {
- const row = guide.createEl("div", { cls: "paperforge-guide-item" });
- row.createEl("strong", { text: t(kTitle) });
- row.createEl("span", { text: " \u2014 " + t(kDesc) });
- }
-
- /* Config Summary */
- if (this.plugin.settings.setup_complete) {
- containerEl.createEl("h3", { text: t("section_config") });
- const summary = containerEl.createEl("div", {
- cls: "paperforge-summary",
- });
- const s = this.plugin.settings;
- const pf = this._pfConfig;
- const items = [
- { label: t("dir_vault"), val: vaultPath },
- { label: t("dir_resources"), val: `${vaultPath}/${pf?.resources_dir}` },
- {
- label: " " + t("dir_notes"),
- val: `${vaultPath}/${pf?.resources_dir}/${pf?.literature_dir}`,
- },
- { label: t("dir_base"), val: `${vaultPath}/${pf?.base_dir}` },
- { label: t("dir_system"), val: `${vaultPath}/${pf?.system_dir}` },
- {
- label: "API Key",
- val: s.paddleocr_api_key ? t("api_key_set") : t("api_key_missing"),
- },
- {
- label: t("field_zotero_data"),
- val: s.zotero_data_dir || t("not_set"),
- },
- ];
- for (const item of items) {
- const row = summary.createEl("div", { cls: "paperforge-summary-row" });
- row.createEl("span", {
- cls: "paperforge-summary-label",
- text: item.label,
- });
- row.createEl("span", {
- cls: "paperforge-summary-value",
- text: item.val,
- });
}
}
}
@@ -2593,4 +2365,328 @@ export class PaperForgeSettingTab extends PluginSettingTab {
});
manualLink.setAttr("target", "_blank");
}
+
+ // ── Capability state management (Issue #76) ──
+
+ /**
+ * Ensure capabilityState exists for all six modules.
+ * Always materializes unknown envelopes when stored map is absent/partial,
+ * regardless of setup_complete, so first-run immediately probes Installation+Help.
+ */
+ _initCapabilityState(): void {
+ const stored = this.plugin.settings.capabilityState;
+ this._capabilityState = validatePersistedEnvelopes(
+ (stored ?? {}) as Record,
+ CAPABILITY_MODULES as unknown as string[]
+ );
+ this._persistCapabilityState();
+ }
+
+ /** Persist capability state to plugin settings. */
+ _persistCapabilityState(): void {
+ if (!this._capabilityState) return;
+ this.plugin.settings.capabilityState = this._capabilityState;
+ this.plugin.saveSettings();
+ }
+
+ /** Call `paperforge probe --json` and store the validated envelope unchanged. */
+ _probeModule(mod: CapabilityModule): void {
+ if (this._probing.has(mod)) return;
+ this._probing.add(mod);
+
+ // Show probing state immediately
+ const current = this._capabilityState?.[mod];
+ const probing: ProbeEnvelope = {
+ schema_version: 1,
+ module: mod,
+ capability_state: current?.capability_state ?? "unknown",
+ activity_state: "running",
+ activity_label: "Probing...",
+ activity_progress: null,
+ severity: "unknown",
+ reason: { code: `${mod}.probing`, text: `Checking ${mod} status...` },
+ action: { primary: probeAction(mod) },
+ notices: current?.notices ?? [],
+ updated_at: new Date().toISOString(),
+ ttl_seconds: current?.ttl_seconds ?? 0,
+ };
+ this._updateCapabilityEnvelope(mod, probing);
+
+ const vp = (this.app.vault.adapter as any).basePath as string;
+ const py = getCachedPython(vp, this.plugin.settings);
+ if (!py.path) {
+ this._probing.delete(mod);
+ this._updateCapabilityEnvelope(mod, createInvalidEnvelope(mod));
+ return;
+ }
+
+ const args = [
+ ...py.extraArgs,
+ "-m",
+ "paperforge",
+ "--vault",
+ vp,
+ "probe",
+ mod,
+ "--json",
+ ];
+
+ execFile(
+ py.path,
+ args,
+ { cwd: vp, timeout: 15000 },
+ (err: Error | null, stdout: string, stderr: string) => {
+ this._probing.delete(mod);
+ if (err) {
+ console.warn(`[PaperForge] Probe ${mod} failed:`, err.message);
+ this._updateCapabilityEnvelope(mod, createInvalidEnvelope(mod));
+ return;
+ }
+ try {
+ const parsed: unknown = JSON.parse(stdout);
+ // Backend JSON passed through unchanged after strict validation
+ if (isValidEnvelope(parsed, mod)) {
+ this._updateCapabilityEnvelope(mod, parsed as ProbeEnvelope);
+ } else {
+ console.warn(`[PaperForge] Probe ${mod}: invalid envelope schema`, stdout?.slice(0, 200));
+ this._updateCapabilityEnvelope(mod, createInvalidEnvelope(mod));
+ }
+ } catch {
+ console.warn(`[PaperForge] Probe ${mod}: unparseable JSON`, stdout?.slice(0, 200));
+ this._updateCapabilityEnvelope(mod, createInvalidEnvelope(mod));
+ }
+ }
+ );
+ }
+
+ /** Update a single module envelope and refresh the display. */
+ _updateCapabilityEnvelope(mod: string, envelope: ProbeEnvelope): void {
+ if (!this._capabilityState) this._capabilityState = {};
+ const prev = this._capabilityState[envelope.module];
+ this._capabilityState[envelope.module] = envelope;
+ this._persistCapabilityState();
+ // Show notice when probe completes (transition from running to idle)
+ if (prev?.activity_state === "running" && envelope.activity_state !== "running") {
+ new Notice(t("cc_notice_refreshed"), 3000);
+ }
+ // Re-render the current tab to reflect changes
+ this.display();
+ }
+
+ /** Derive badge i18n key from envelope severity + module. */
+ private _ccBadgeKey(env: ProbeEnvelope, mod: CapabilityModule): string {
+ if (env.severity === "ok") return "cc_badge_ok";
+ if (env.severity === "error" && mod === "installation") return "cc_badge_setup";
+ if (env.severity === "warning" || env.severity === "error") return "cc_badge_attention";
+ return "cc_badge_pending";
+ }
+
+ /** CSS severity class from backend severity string. Unknown maps to neutral. */
+ _sevClass(severity: string): string {
+ if (severity === "error") return "error";
+ if (severity === "warning") return "warn";
+ if (severity === "unknown") return "unknown";
+ return "ok";
+ }
+
+
+ /** Reason code → localized string via i18n key, or null if unmapped.
+ * Tries full dotted code normalized to underscores first (e.g. "installation.ready" → "cc_reason_installation_ready"),
+ * then falls back to bare code (e.g. "ready" → "cc_reason_ready"). */
+ private _localizeReason(code: string, module: string): string | null {
+ // Try full dotted code: "installation.ready" → "cc_reason_installation_ready"
+ const fullKey = "cc_reason_" + code.replace(/\./g, "_");
+ const fullTranslated = t(fullKey);
+ if (fullTranslated !== fullKey) {
+ return fullTranslated.replace("{module}", module);
+ }
+ // Fallback to bare code: "installation.ready" → "ready" → "cc_reason_ready"
+ const bare = code.replace(/^[a-z]+\./, "");
+ const bareKey = "cc_reason_" + bare;
+ const bareTranslated = t(bareKey);
+ if (bareTranslated === bareKey) return null;
+ return bareTranslated.replace("{module}", module);
+ }
+
+ /** Modules with real Python probe support. */
+ private static _REAL_PROBE = new Set(["installation", "help"]);
+
+ _renderCard(container: HTMLElement, mod: CapabilityModule, envelope: ProbeEnvelope): void {
+ const env = envelope;
+ const sevClass = this._sevClass(env.severity);
+ const isReal = PaperForgeSettingTab._REAL_PROBE.has(mod);
+ const card = container.createEl("div", {
+ cls: "pf-cc-card",
+ attr: { role: "listitem", tabindex: "0", "aria-label": `${t("cc_module_" + mod)} — ${t(this._ccBadgeKey(env, mod))}` },
+ });
+
+ // Header: name + badge
+ const header = card.createEl("div", { cls: "pf-cc-card-header" });
+ header.createEl("div", { cls: "pf-cc-card-name", text: t("cc_module_" + mod) });
+ header.createEl("div", {
+ cls: `pf-cc-card-badge pf-cc-card-badge--${sevClass}`,
+ text: t(this._ccBadgeKey(env, mod)),
+ });
+
+ // Reason text — localized via code map, fallback to backend text
+ // Placeholder modules (library, ocr, memory, maintenance) show "pending integration"
+ let reasonText: string;
+ if (!isReal) {
+ reasonText = t("cc_reason_placeholder").replace("{module}", t("cc_module_" + mod));
+ } else {
+ const l10nReason = this._localizeReason(env.reason.code, mod);
+ reasonText = l10nReason ?? env.reason.text;
+ }
+ card.createEl("div", { cls: "pf-cc-card-reason", text: reasonText });
+
+ // Activity label + progress bar (DOM style.width, never inline attribute)
+ if (env.activity_state === "running" && env.activity_label) {
+ const activityRow = card.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 + "%";
+ }
+ }
+
+ // Footer: action button + diagnostics
+ const footer = card.createEl("div", { cls: "pf-cc-card-footer" });
+
+ // Action button — only for real modules (installation/help); placeholders show no action
+ if (isReal && env.action.primary && !isReadyEnvelope(env)) {
+ const action = classifyCapabilityAction(env);
+ const isCta = action.kind === "setup";
+ const btnCls = isCta ? "pf-cc-card-action pf-cc-card-action--primary" : "pf-cc-card-action";
+ const btn = footer.createEl("button", {
+ cls: btnCls,
+ text: action.label,
+ attr: { "aria-label": action.label },
+ });
+ btn.addEventListener("click", () => {
+ if (action.kind === "setup") {
+ new PaperForgeSetupModal(this.app, this.plugin, () => {
+ this._probeModule("installation");
+ this._probeModule("help");
+ }).open();
+ } else {
+ this._probeModule(mod);
+ }
+ });
+ }
+
+ // Diagnostics — native with localized field labels and values
+ const details = card.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" });
+
+ // Localized values
+ 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;
+
+ // Format updated_at with locale
+ let dateLabel: string;
+ try {
+ dateLabel = new Date(env.updated_at).toLocaleString();
+ } catch {
+ dateLabel = env.updated_at;
+ }
+
+ 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}` });
+ // Reason: localized text (or placeholder message) plus technical code in
+ const reasonRow = body.createEl("div");
+ reasonRow.appendText(t("cc_diag_reason") + ": " + reasonText + " ");
+ const codeEl = reasonRow.createEl("code", { text: env.reason.code });
+ body.createEl("div", { text: `${t("cc_diag_ttl")}: ${String(env.ttl_seconds)}s` });
+ body.createEl("div", { text: `${t("cc_diag_updated")}: ${dateLabel}` });
+ }
+
+ /** Render Vercel-inspired control center: summary card + six-card responsive grid. */
+ _renderControlCenter(containerEl: HTMLElement): void {
+ const cc = containerEl.createEl("div", { cls: "pf-control-center" });
+
+ // Compute summary counts from envelopes
+ const modules = CAPABILITY_MODULES;
+ const envelopes: Record = this._capabilityState ?? {};
+ let realReady = 0;
+ let realAttention = 0;
+ let placeholderCount = 0;
+
+ for (const mod of modules) {
+ const env = envelopes[mod] ?? createUnknownEnvelope(mod);
+ if (env.severity === "ok" && env.capability_state === "ready" && env.action.primary === null) {
+ // Backend-confirmed ready, no pending action
+ realReady++;
+ } else if (PaperForgeSettingTab._REAL_PROBE.has(mod)) {
+ // Real module that has been probed but isn't ready
+ if (env.severity === "error" || env.severity === "warning" || env.severity === "unknown") {
+ realAttention++;
+ }
+ } else {
+ // Placeholder module (library, ocr, memory, maintenance) — not yet connected
+ placeholderCount++;
+ }
+ }
+
+ // ── Summary Card ──
+ const summaryEl = cc.createEl("div", { cls: "pf-cc-summary" });
+ summaryEl.createEl("div", { cls: "pf-cc-summary-eyebrow", text: t("cc_title") });
+
+ // Decisive title based on state
+ let summaryTitle: string;
+ let summaryBodyText: string;
+ if (realAttention > 0) {
+ summaryTitle = t("cc_summary_attention");
+ summaryBodyText = t("cc_summary_attention_body");
+ } else if (realReady === modules.length) {
+ summaryTitle = t("cc_summary_ok");
+ summaryBodyText = t("cc_summary_ok_body");
+ } else if (realReady > 0 && placeholderCount > 0 && realAttention === 0) {
+ summaryTitle = t("cc_summary_core_ok").replace("{n}", String(placeholderCount));
+ summaryBodyText = t("cc_summary_core_ok_body");
+ } else {
+ summaryTitle = t("cc_summary_core_ok").replace("{n}", String(modules.length - realReady));
+ summaryBodyText = t("cc_desc");
+ }
+ summaryEl.createEl("div", { cls: "pf-cc-summary-title", text: summaryTitle });
+ summaryEl.createEl("div", { cls: "pf-cc-summary-body", text: summaryBodyText });
+
+ // Summary counts row
+ const countsEl = summaryEl.createEl("div", { cls: "pf-cc-summary-counts" });
+ countsEl.createEl("div", {
+ cls: "pf-cc-summary-count",
+ text: t("cc_n_ready").replace("{n}", String(realReady)),
+ });
+ if (placeholderCount > 0) {
+ countsEl.createEl("div", {
+ cls: "pf-cc-summary-count",
+ text: t("cc_n_pending").replace("{n}", String(placeholderCount)),
+ });
+ }
+
+ // ── Module Grid ──
+ const grid = cc.createEl("div", { cls: "pf-cc-grid", attr: { role: "list", "aria-label": t("cc_zone_modules") } });
+ for (const mod of modules) {
+ const env = envelopes[mod] ?? createUnknownEnvelope(mod);
+ this._renderCard(grid, mod, env);
+ }
+ }
+ /** Apply stale-tolerance: if an envelope is stale, replace with unknown+probe. */
+ _applyStaleTolerance(): void {
+ if (!this._capabilityState) return;
+ let changed = false;
+ for (const mod of CAPABILITY_MODULES) {
+ const env = this._capabilityState[mod];
+ if (env && isEnvelopeStale(env)) {
+ this._capabilityState[mod] = createStaleEnvelope(mod);
+ changed = true;
+ }
+ }
+ if (changed) this._persistCapabilityState();
+ }
}
diff --git a/paperforge/plugin/src/views/modals.ts b/paperforge/plugin/src/views/modals.ts
index 19c65d74..d5704055 100644
--- a/paperforge/plugin/src/views/modals.ts
+++ b/paperforge/plugin/src/views/modals.ts
@@ -212,11 +212,13 @@ export class PaperForgeSetupModal extends Modal {
private _apiKeyValidated!: boolean;
private _apiKeyStatus!: HTMLElement;
private _showSkipConfirm: boolean = false;
+ private _onComplete: (() => void) | undefined;
- constructor(app: App, plugin: IPluginRef) {
+ constructor(app: App, plugin: IPluginRef, onComplete?: () => void) {
super(app);
this.plugin = plugin;
this._step = 1;
+ this._onComplete = onComplete;
}
onOpen() {
@@ -704,6 +706,7 @@ export class PaperForgeSetupModal extends Modal {
this._log(t('install_complete'));
s.setup_complete = true;
await this.plugin.saveSettings();
+ if (this._onComplete) this._onComplete();
setTimeout(() => { this._step = 5; this._render(); }, 800);
} catch (err) {
console.error('PaperForge setup failed:', (err as Error).message);
diff --git a/paperforge/plugin/styles.css b/paperforge/plugin/styles.css
index 422f387c..2c99d7e7 100644
--- a/paperforge/plugin/styles.css
+++ b/paperforge/plugin/styles.css
@@ -4412,3 +4412,270 @@
max-height: 60px;
overflow-y: auto;
}
+
+/* ==========================================================================
+ SECTION 14 — Control Center (Issue #76) — Capability state cards
+ Vercel-inspired status dashboard. Self-contained under .pf-control-center.
+ CSS tokens bridge Obsidian variables; no dependency on .paperforge-status-panel.
+ ========================================================================== */
+
+.pf-control-center {
+ container-type: inline-size;
+ container-name: pfcc;
+}
+
+/* ── Summary Card ── */
+.pf-control-center .pf-cc-summary {
+ background: var(--background-primary);
+ border: 1px solid var(--background-modifier-border);
+ border-radius: var(--radius-m);
+ padding: 16px 20px;
+ margin-bottom: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.pf-control-center .pf-cc-summary-eyebrow {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1.3;
+}
+
+.pf-control-center .pf-cc-summary-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--text-normal);
+ line-height: 1.35;
+ margin: 0;
+}
+
+.pf-control-center .pf-cc-summary-body {
+ font-size: 13px;
+ color: var(--text-muted);
+ line-height: 1.5;
+ margin: 0;
+}
+
+.pf-control-center .pf-cc-summary-counts {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.pf-control-center .pf-cc-summary-count {
+ font-size: 12px;
+ color: var(--text-muted);
+ line-height: 1.4;
+}
+
+.pf-control-center .pf-cc-summary-count strong {
+ font-weight: 600;
+ color: var(--text-normal);
+}
+
+/* ── Module Grid ── */
+.pf-control-center .pf-cc-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 12px;
+}
+
+@container pfcc (max-width: 620px) {
+ .pf-control-center .pf-cc-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* ── Module Card ── */
+.pf-control-center .pf-cc-card {
+ background: var(--background-primary);
+ border: 1px solid var(--background-modifier-border);
+ border-radius: var(--radius-m);
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ min-width: 0;
+ transition: border-color 0.15s;
+}
+.pf-control-center .pf-cc-card:hover {
+ border-color: var(--background-modifier-border-hover);
+}
+.pf-control-center .pf-cc-card:focus-visible {
+ outline: 2px solid var(--interactive-accent);
+ outline-offset: 2px;
+}
+
+.pf-control-center .pf-cc-card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 8px;
+}
+
+.pf-control-center .pf-cc-card-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-normal);
+ line-height: 1.4;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.pf-control-center .pf-cc-card-badge {
+ display: inline-flex;
+ align-items: center;
+ padding: 2px 8px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 500;
+ line-height: 1.4;
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+
+.pf-control-center .pf-cc-card-badge--ok {
+ background: color-mix(in srgb, var(--text-success) 10%, transparent);
+ color: var(--text-success);
+}
+
+.pf-control-center .pf-cc-card-badge--warn {
+ background: color-mix(in srgb, var(--text-warning) 10%, transparent);
+ color: var(--text-warning);
+}
+
+.pf-control-center .pf-cc-card-badge--error {
+ background: color-mix(in srgb, var(--text-error) 10%, transparent);
+ color: var(--text-error);
+}
+
+.pf-control-center .pf-cc-card-badge--unknown {
+ background: var(--background-secondary);
+ color: var(--text-muted);
+}
+
+.pf-control-center .pf-cc-card-reason {
+ font-size: 12px;
+ color: var(--text-muted);
+ line-height: 1.5;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ min-height: 0;
+ margin: 0;
+}
+
+/* ── Activity / Progress ── */
+.pf-control-center .pf-cc-card-activity {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 11px;
+ color: var(--text-muted);
+ line-height: 1.3;
+}
+
+.pf-control-center .pf-cc-card-progress {
+ flex: 1;
+ height: 4px;
+ border-radius: 2px;
+ background: var(--background-modifier-border);
+ overflow: hidden;
+ min-width: 40px;
+}
+
+.pf-control-center .pf-cc-card-progress-fill {
+ height: 100%;
+ border-radius: 2px;
+ background: var(--interactive-accent);
+ transition: width 0.3s;
+}
+
+/* ── Card Footer ── */
+.pf-control-center .pf-cc-card-footer {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-top: auto;
+ padding-top: 4px;
+}
+
+.pf-control-center .pf-cc-card-action {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ background: var(--interactive-normal);
+ border: 1px solid var(--background-modifier-border);
+ color: var(--text-normal);
+ border-radius: var(--button-radius, 4px);
+ font-size: 11px;
+ font-weight: 500;
+ padding: 3px 10px;
+ cursor: pointer;
+ transition: background 0.15s;
+ white-space: nowrap;
+}
+
+.pf-control-center .pf-cc-card-action:hover {
+ background: var(--interactive-hover);
+ border-color: var(--background-modifier-border-hover);
+}
+
+.pf-control-center .pf-cc-card-action--primary {
+ background: var(--interactive-accent);
+ color: var(--text-on-accent);
+ border: none;
+}
+
+.pf-control-center .pf-cc-card-action--primary:hover {
+ opacity: 0.85;
+}
+
+.pf-control-center .pf-cc-card-diagnostic {
+ font-size: 11px;
+ color: var(--text-faint);
+ cursor: pointer;
+ user-select: none;
+ line-height: 1.3;
+ margin-left: auto;
+}
+.pf-control-center .pf-cc-card-diagnostic > summary {
+ cursor: pointer;
+ user-select: none;
+ color: var(--text-faint);
+ font-size: 11px;
+ line-height: 1.3;
+ display: inline;
+}
+.pf-control-center .pf-cc-card-diagnostic > summary:hover {
+ color: var(--text-muted);
+}
+
+.pf-control-center .pf-cc-card-diagnostic:hover {
+ color: var(--text-muted);
+}
+
+.pf-control-center .pf-cc-card-diagnostic-body {
+ font-size: 11px;
+ color: var(--text-faint);
+ line-height: 1.4;
+ padding: 4px 0 0 0;
+ word-break: break-all;
+ white-space: pre-wrap;
+}
+
+/* ── Reduced Motion ── */
+@media (prefers-reduced-motion: reduce) {
+ .pf-control-center .pf-cc-card-progress-fill {
+ transition: none;
+ }
+ .pf-control-center .pf-cc-card {
+ transition: none;
+ }
+}
diff --git a/paperforge/plugin/tests/capability-state.test.ts b/paperforge/plugin/tests/capability-state.test.ts
new file mode 100644
index 00000000..2f24d72f
--- /dev/null
+++ b/paperforge/plugin/tests/capability-state.test.ts
@@ -0,0 +1,664 @@
+/**
+ * Focused tests for capability-state types, envelope validation, helpers,
+ * and the six-module control center rendering.
+ */
+import { describe, expect, it } from "vitest";
+import {
+ isValidEnvelope,
+ createUnknownEnvelope,
+ createStaleEnvelope,
+ createInvalidEnvelope,
+ isEnvelopeStale,
+ isReadyEnvelope,
+ classifyCapabilityAction,
+ computeModuleSummary,
+ validatePersistedEnvelopes,
+ CAPABILITY_MODULES,
+ SCHEMA_VERSION,
+ ProbeEnvelope,
+ ActionPrimary,
+ probeAction,
+ setupAction,
+} from "../src/constants";
+
+// ── Helpers ──
+
+/** Minimal valid envelope matching backend int/schema. */
+function validEnvelope(overrides: Partial> = {}): Record {
+ return {
+ schema_version: 1,
+ module: "installation",
+ capability_state: "ready",
+ severity: "ok",
+ activity_state: "idle",
+ activity_label: null,
+ activity_progress: null,
+ notices: [],
+ reason: { code: "ok", text: "All good" },
+ action: { primary: null },
+ updated_at: "2026-01-15T00:00:00.000Z",
+ ttl_seconds: 3600,
+ ...overrides,
+ };
+}
+
+/** Full setup action from backend. */
+const FULL_SETUP_ACTION: ActionPrimary = {
+ verb: "setup",
+ label: "Open Setup Wizard",
+ destructive: false,
+ destructive_scope: null,
+ destructive_effect: null,
+ confirmation_required: false,
+ confirmation_prompt: null,
+ command: "setup",
+ scope: "installation",
+ scope_count: 0,
+};
+
+// ── 1. Schema validation ──
+
+describe("isValidEnvelope", () => {
+ it("passes for a valid envelope", () => {
+ expect(isValidEnvelope(validEnvelope())).toBe(true);
+ });
+
+ it("passes with backend-typical null activity_label and null activity_progress", () => {
+ expect(isValidEnvelope(validEnvelope({
+ activity_label: null,
+ activity_progress: null,
+ }))).toBe(true);
+ });
+
+ it("passes with full setup action primary", () => {
+ expect(isValidEnvelope(validEnvelope({
+ action: { primary: FULL_SETUP_ACTION },
+ }))).toBe(true);
+ });
+
+ it("rejects null input", () => {
+ expect(isValidEnvelope(null)).toBe(false);
+ });
+
+ it("rejects undefined input", () => {
+ expect(isValidEnvelope(undefined)).toBe(false);
+ });
+
+ it("rejects non-object input", () => {
+ expect(isValidEnvelope("string")).toBe(false);
+ expect(isValidEnvelope(42)).toBe(false);
+ });
+
+ it("rejects wrong schema_version", () => {
+ expect(isValidEnvelope(validEnvelope({ schema_version: "v1" }))).toBe(false);
+ expect(isValidEnvelope(validEnvelope({ schema_version: 2 }))).toBe(false);
+ });
+
+ it("rejects unknown module", () => {
+ expect(isValidEnvelope(validEnvelope({ module: "bogus" }))).toBe(false);
+ });
+
+ it("rejects module mismatch", () => {
+ expect(isValidEnvelope(validEnvelope({ module: "help" }), "installation")).toBe(false);
+ });
+
+ it("accepts matching module", () => {
+ expect(isValidEnvelope(validEnvelope({ module: "help" }), "help")).toBe(true);
+ });
+
+ it("accepts all six capability_states", () => {
+ for (const s of ["unknown", "unavailable", "missing_input", "needs_action", "limited", "ready"]) {
+ expect(isValidEnvelope(validEnvelope({ capability_state: s }))).toBe(true);
+ }
+ });
+
+ it("rejects invalid capability_state", () => {
+ expect(isValidEnvelope(validEnvelope({ capability_state: "bogus" }))).toBe(false);
+ });
+
+ it("accepts all four severities", () => {
+ for (const s of ["unknown", "ok", "warning", "error"]) {
+ expect(isValidEnvelope(validEnvelope({ severity: s }))).toBe(true);
+ }
+ });
+
+ it("rejects invalid severity", () => {
+ expect(isValidEnvelope(validEnvelope({ severity: "critical" }))).toBe(false);
+ });
+
+ it("rejects missing activity_state", () => {
+ const { activity_state, ...rest } = validEnvelope();
+ expect(isValidEnvelope(rest)).toBe(false);
+ });
+
+ it("rejects missing activity_label", () => {
+ const { activity_label, ...rest } = validEnvelope();
+ expect(isValidEnvelope(rest)).toBe(false);
+ });
+
+ it("rejects missing activity_progress", () => {
+ const { activity_progress, ...rest } = validEnvelope();
+ expect(isValidEnvelope(rest)).toBe(false);
+ });
+
+ it("accepts valid activity_progress object", () => {
+ expect(isValidEnvelope(validEnvelope({
+ activity_state: "running",
+ activity_label: "Probing...",
+ activity_progress: { current: 1, total: 5 },
+ }))).toBe(true);
+ });
+
+ it("rejects activity_progress with string current", () => {
+ expect(isValidEnvelope(validEnvelope({
+ activity_progress: { current: "1", total: 3 },
+ }))).toBe(false);
+ });
+
+ it("accepts notices array", () => {
+ expect(isValidEnvelope(validEnvelope({
+ notices: [{ level: "info", message: "test" }],
+ }))).toBe(true);
+ });
+
+ it("rejects missing notices", () => {
+ const { notices, ...rest } = validEnvelope();
+ expect(isValidEnvelope(rest)).toBe(false);
+ });
+
+ it("rejects missing reason", () => {
+ const { reason, ...rest } = validEnvelope();
+ expect(isValidEnvelope(rest)).toBe(false);
+ });
+
+ it("rejects reason missing code", () => {
+ expect(isValidEnvelope(validEnvelope({ reason: { text: "nope" } }))).toBe(false);
+ });
+
+ it("rejects reason missing text", () => {
+ expect(isValidEnvelope(validEnvelope({ reason: { code: "nope" } }))).toBe(false);
+ });
+
+ it("rejects missing action", () => {
+ const { action, ...rest } = validEnvelope();
+ expect(isValidEnvelope(rest)).toBe(false);
+ });
+
+ it("rejects action with string primary", () => {
+ expect(isValidEnvelope(validEnvelope({ action: { primary: "setup" } }))).toBe(false);
+ });
+
+ it("rejects action with array primary", () => {
+ expect(isValidEnvelope(validEnvelope({ action: { primary: [] } }))).toBe(false);
+ });
+
+ it("rejects action with incomplete primary (missing verb)", () => {
+ expect(isValidEnvelope(validEnvelope({ action: { primary: { label: "x" } } }))).toBe(false);
+ });
+
+ it("accepts action with null primary", () => {
+ expect(isValidEnvelope(validEnvelope({ action: { primary: null } }))).toBe(true);
+ });
+
+ it("accepts action with full setup primary", () => {
+ expect(isValidEnvelope(validEnvelope({ action: { primary: FULL_SETUP_ACTION } }))).toBe(true);
+ });
+
+ it("rejects missing updated_at", () => {
+ const { updated_at, ...rest } = validEnvelope();
+ expect(isValidEnvelope(rest)).toBe(false);
+ });
+
+ it("rejects missing ttl_seconds", () => {
+ const { ttl_seconds, ...rest } = validEnvelope();
+ expect(isValidEnvelope(rest)).toBe(false);
+ });
+
+ it("rejects non-number ttl_seconds", () => {
+ expect(isValidEnvelope(validEnvelope({ ttl_seconds: "3600" }))).toBe(false);
+ });
+});
+
+// ── 2. Stale / Invalid helpers (severity unknown, full probe action) ──
+
+describe("isEnvelopeStale", () => {
+ function makeEnv(overrides: Partial = {}): ProbeEnvelope {
+ return {
+ schema_version: 1, module: "installation",
+ capability_state: "ready", severity: "ok",
+ activity_state: "idle",
+ activity_label: null,
+ activity_progress: null,
+ notices: [],
+ reason: { code: "ok", text: "ok" },
+ action: { primary: null },
+ updated_at: new Date().toISOString(),
+ ttl_seconds: 3600,
+ ...overrides,
+ };
+ }
+
+ it("returns true when ttl_seconds is 0", () => {
+ expect(isEnvelopeStale(makeEnv({ ttl_seconds: 0 }))).toBe(true);
+ });
+
+ it("returns true for epoch date", () => {
+ expect(isEnvelopeStale(makeEnv({ updated_at: new Date(0).toISOString() }))).toBe(true);
+ });
+
+ it("returns false when recent within TTL", () => {
+ expect(isEnvelopeStale(makeEnv({ ttl_seconds: 3600 }))).toBe(false);
+ });
+});
+
+describe("createStaleEnvelope", () => {
+ it("produces unknown state, module-prefixed reason, probe action", () => {
+ const env = createStaleEnvelope("help");
+ expect(env.capability_state).toBe("unknown");
+ expect(env.severity).toBe("unknown");
+ expect(env.reason.code).toBe("help.stale");
+ expect(env.ttl_seconds).toBe(0);
+ expect(env.activity_label).toBeNull();
+ expect(env.activity_progress).toBeNull();
+ expect(env.action.primary).not.toBeNull();
+ expect(env.action.primary!.verb).toBe("probe");
+ });
+});
+
+describe("createInvalidEnvelope", () => {
+ it("produces unknown state, module-prefixed reason, probe action", () => {
+ const env = createInvalidEnvelope("library");
+ expect(env.capability_state).toBe("unknown");
+ expect(env.severity).toBe("unknown");
+ expect(env.reason.code).toBe("library.invalid_response");
+ expect(env.activity_label).toBeNull();
+ expect(env.activity_progress).toBeNull();
+ expect(env.action.primary!.verb).toBe("probe");
+ });
+});
+
+// ── 3. createUnknownEnvelope ──
+
+describe("createUnknownEnvelope", () => {
+ it("produces six distinct unknown envelopes", () => {
+ for (const mod of CAPABILITY_MODULES) {
+ const env = createUnknownEnvelope(mod);
+ expect(env.module).toBe(mod);
+ expect(env.capability_state).toBe("unknown");
+ expect(env.schema_version).toBe(SCHEMA_VERSION);
+ expect(env.activity_label).toBeNull();
+ expect(env.activity_progress).toBeNull();
+ }
+ });
+
+ it("ALL modules get verb=probe (never setup) in unknown state", () => {
+ for (const mod of CAPABILITY_MODULES) {
+ const env = createUnknownEnvelope(mod);
+ expect(env.action.primary).not.toBeNull();
+ expect(env.action.primary!.verb).toBe("probe");
+ }
+ });
+
+ it("probe action has full ActionPrimary shape", () => {
+ const env = createUnknownEnvelope("installation");
+ const p = env.action.primary!;
+ expect(typeof p.verb).toBe("string");
+ expect(typeof p.label).toBe("string");
+ expect(typeof p.destructive).toBe("boolean");
+ expect(typeof p.command).toBe("string");
+ expect(typeof p.scope).toBe("string");
+ expect(typeof p.scope_count).toBe("number");
+ });
+});
+
+// ── 4. Six-module grid & order ──
+
+describe("CAPABILITY_MODULES", () => {
+ it("contains exactly six modules", () => {
+ expect(CAPABILITY_MODULES).toHaveLength(6);
+ });
+
+ it("renders in approved prototype order: installation, library, ocr, memory, maintenance, help", () => {
+ expect(CAPABILITY_MODULES[0]).toBe("installation");
+ expect(CAPABILITY_MODULES[1]).toBe("library");
+ expect(CAPABILITY_MODULES[2]).toBe("ocr");
+ expect(CAPABILITY_MODULES[3]).toBe("memory");
+ expect(CAPABILITY_MODULES[4]).toBe("maintenance");
+ expect(CAPABILITY_MODULES[5]).toBe("help");
+ });
+
+ it("includes all expected modules", () => {
+ expect(CAPABILITY_MODULES).toContain("installation");
+ expect(CAPABILITY_MODULES).toContain("library");
+ expect(CAPABILITY_MODULES).toContain("ocr");
+ expect(CAPABILITY_MODULES).toContain("memory");
+ expect(CAPABILITY_MODULES).toContain("maintenance");
+ expect(CAPABILITY_MODULES).toContain("help");
+ });
+});
+
+// ── 5. probeAction / setupAction builders ──
+
+describe("probeAction", () => {
+ it("returns full ActionPrimary with correct verb", () => {
+ const a = probeAction("installation");
+ expect(a.verb).toBe("probe");
+ expect(a.label).toBe("Check");
+ expect(a.destructive).toBe(false);
+ expect(a.command).toBe("probe installation");
+ expect(a.scope).toBe("installation");
+ expect(a.scope_count).toBe(1);
+ });
+});
+
+describe("setupAction", () => {
+ it("returns full ActionPrimary with setup verb", () => {
+ const a = setupAction();
+ expect(a.verb).toBe("setup");
+ expect(a.label).toBe("Open Setup Wizard");
+ expect(a.destructive).toBe(false);
+ });
+});
+
+// ── 5. Ready envelope ──
+
+describe("isReadyEnvelope", () => {
+ function make(overrides: Partial = {}): ProbeEnvelope {
+ return {
+ schema_version: 1, module: "installation",
+ capability_state: "ready", severity: "ok",
+ activity_state: "idle",
+ activity_label: null,
+ activity_progress: null,
+ reason: { code: "ok", text: "ok" },
+ action: { primary: null },
+ notices: [],
+ updated_at: new Date().toISOString(),
+ ttl_seconds: 3600,
+ ...overrides,
+ };
+ }
+
+ it("returns true for ready with null primary", () => {
+ expect(isReadyEnvelope(make())).toBe(true);
+ });
+
+ it("returns false for ready with non-null action", () => {
+ expect(isReadyEnvelope(make({ action: { primary: FULL_SETUP_ACTION } }))).toBe(false);
+ });
+
+ it("returns false for non-ready state", () => {
+ expect(isReadyEnvelope(make({ capability_state: "unavailable" }))).toBe(false);
+ });
+
+ it("returns false for unknown state", () => {
+ expect(isReadyEnvelope(createUnknownEnvelope("installation"))).toBe(false);
+ });
+});
+
+// ── 6. Six-module grid ──
+
+describe("CAPABILITY_MODULES", () => {
+ it("contains exactly six modules", () => {
+ expect(CAPABILITY_MODULES).toHaveLength(6);
+ });
+
+ it("includes all expected modules", () => {
+ expect(CAPABILITY_MODULES).toContain("installation");
+ expect(CAPABILITY_MODULES).toContain("help");
+ expect(CAPABILITY_MODULES).toContain("library");
+ expect(CAPABILITY_MODULES).toContain("ocr");
+ expect(CAPABILITY_MODULES).toContain("memory");
+ expect(CAPABILITY_MODULES).toContain("maintenance");
+ });
+});
+
+// ── 7. Literal backend envelope test ──
+
+describe("literal backend envelope", () => {
+ /** Exact shape a real `paperforge probe installation --json` would emit. */
+ const BACKEND_INSTALLATION_READY: Record = {
+ schema_version: 1,
+ module: "installation",
+ capability_state: "ready",
+ activity_state: "idle",
+ activity_label: null,
+ activity_progress: null,
+ severity: "ok",
+ reason: { code: "ok", text: "PaperForge environment is set up correctly." },
+ action: { primary: null },
+ notices: [{ level: "info", message: "Installation verified at 2026-01-15" }],
+ updated_at: "2026-01-15T00:00:00.000Z",
+ ttl_seconds: 3600,
+ };
+
+ it("validates a ready backend envelope", () => {
+ expect(isValidEnvelope(BACKEND_INSTALLATION_READY)).toBe(true);
+ });
+
+ it("validates a backend envelope with full setup action", () => {
+ const envelope: Record = {
+ ...BACKEND_INSTALLATION_READY,
+ capability_state: "needs_action",
+ severity: "warning",
+ reason: { code: "setup_required", text: "Initial setup not yet complete." },
+ action: { primary: FULL_SETUP_ACTION },
+ };
+ expect(isValidEnvelope(envelope)).toBe(true);
+ });
+
+ it("validates a running backend envelope", () => {
+ const envelope: Record = {
+ ...BACKEND_INSTALLATION_READY,
+ capability_state: "unknown",
+ activity_state: "running",
+ activity_label: "Probing PaperForge installation...",
+ activity_progress: { current: 2, total: 5 },
+ severity: "unknown",
+ };
+ expect(isValidEnvelope(envelope)).toBe(true);
+ });
+});
+
+// ── 8. classifyCapabilityAction ──
+
+describe("classifyCapabilityAction", () => {
+ const base: Partial = {
+ schema_version: 1, module: "installation", capability_state: "needs_action",
+ activity_state: "idle", severity: "warning", reason: { code: "test", text: "test" },
+ updated_at: new Date().toISOString(), ttl_seconds: 3600,
+ };
+
+ it("preserves the backend-selected action label", () => {
+ const env: ProbeEnvelope = { ...base as ProbeEnvelope,
+ module: "help",
+ action: { primary: { verb: "setup", label: "Restore help", destructive: false } },
+ };
+ expect(classifyCapabilityAction(env).label).toBe("Restore help");
+ });
+
+ it("classifies set_config verb as setup kind", () => {
+ const env: ProbeEnvelope = { ...base as ProbeEnvelope,
+ action: { primary: { verb: "set_config", label: "Configure", destructive: false } },
+ };
+ const result = classifyCapabilityAction(env);
+ expect(result.kind).toBe("setup");
+ expect(result.verb).toBe("set_config");
+ });
+
+ it("classifies update verb as setup kind", () => {
+ const env: ProbeEnvelope = { ...base as ProbeEnvelope,
+ action: { primary: { verb: "update", label: "Update", destructive: false } },
+ };
+ const result = classifyCapabilityAction(env);
+ expect(result.kind).toBe("setup");
+ expect(result.verb).toBe("update");
+ });
+
+ it("classifies probe verb as probe kind", () => {
+ const env: ProbeEnvelope = { ...base as ProbeEnvelope,
+ action: { primary: { verb: "probe", label: "Refresh", destructive: false } },
+ };
+ const result = classifyCapabilityAction(env);
+ expect(result.kind).toBe("probe");
+ });
+
+ it("classifies sync/run/rebuild_index as action kind", () => {
+ for (const verb of ["sync", "run", "rebuild_index", "migrate"]) {
+ const env: ProbeEnvelope = { ...base as ProbeEnvelope,
+ action: { primary: { verb, label: verb, destructive: false } },
+ };
+ expect(classifyCapabilityAction(env).kind).toBe("action");
+ }
+ });
+
+ it("defaults to probe when action is null", () => {
+ const env: ProbeEnvelope = { ...base as ProbeEnvelope, action: null };
+ expect(classifyCapabilityAction(env).kind).toBe("probe");
+ });
+
+ it("defaults to probe when primary action is null", () => {
+ const env: ProbeEnvelope = { ...base as ProbeEnvelope, action: { primary: null } };
+ expect(classifyCapabilityAction(env).kind).toBe("probe");
+ });
+});
+
+// ── 9. computeModuleSummary ──
+
+describe("computeModuleSummary", () => {
+ const readyEnv: ProbeEnvelope = {
+ schema_version: 1, module: "installation", capability_state: "ready",
+ activity_state: "idle", severity: "ok", reason: null,
+ action: { primary: null }, updated_at: new Date().toISOString(), ttl_seconds: 3600,
+ };
+ const unknownEnv: ProbeEnvelope = {
+ schema_version: 1, module: "installation", capability_state: "unknown",
+ activity_state: "idle", severity: "unknown", reason: { code: "test", text: "test" },
+ action: { primary: { verb: "probe", label: "Probe", destructive: false } },
+ updated_at: new Date(0).toISOString(), ttl_seconds: 0,
+ };
+ const realModules = ["installation", "help"];
+
+ it("reports coreReady=true when all real modules are ready", () => {
+ const map: Record = {
+ installation: { ...readyEnv, module: "installation" },
+ help: { ...readyEnv, module: "help" },
+ };
+ expect(computeModuleSummary(map, realModules).coreReady).toBe(true);
+ });
+
+ it("reports coreReady=false when a real module is unknown", () => {
+ const map: Record = {
+ installation: unknownEnv,
+ help: { ...readyEnv, module: "help" },
+ };
+ const result = computeModuleSummary(map, realModules);
+ expect(result.coreReady).toBe(false);
+ expect(result.attentionModules).toContain("installation");
+ });
+
+ it("includes missing module as attention", () => {
+ const map: Record = {
+ installation: { ...readyEnv, module: "installation" },
+ // help is missing
+ };
+ const result = computeModuleSummary(map, realModules);
+ expect(result.coreReady).toBe(false);
+ expect(result.attentionModules).toContain("help");
+ });
+});
+
+describe("validatePersistedEnvelopes", () => {
+ const allModules = ["installation", "help"];
+ const fresh = Date.now();
+
+ /** Build a full valid envelope matching worktree isValidEnvelope strict checks. */
+ function validEnv(mod: string): Record {
+ return {
+ schema_version: 1,
+ module: mod,
+ capability_state: "ready",
+ activity_state: "idle",
+ activity_label: null,
+ activity_progress: null,
+ severity: "ok",
+ reason: { code: `${mod}.ready`, text: `${mod} is fully functional.` },
+ action: { primary: null },
+ notices: [],
+ updated_at: new Date(fresh).toISOString(),
+ ttl_seconds: 86400,
+ };
+ }
+
+ it("passes through valid fresh envelopes", () => {
+ const input: Record = {
+ installation: validEnv("installation"),
+ help: validEnv("help"),
+ };
+ const result = validatePersistedEnvelopes(input, allModules);
+ expect(result["installation"].capability_state).toBe("ready");
+ expect(result["help"].capability_state).toBe("ready");
+ });
+
+ it("replaces malformed entries with invalid envelopes", () => {
+ const input: Record = {
+ installation: { schema_version: "not-a-number", module: "installation" },
+ };
+ const result = validatePersistedEnvelopes(input, allModules);
+ expect(result["installation"].capability_state).toBe("unknown");
+ expect(result["installation"].reason?.code).toBe("installation.invalid_response");
+ });
+
+ it("replaces stale entries with stale envelopes", () => {
+ const oldDate = new Date(0).toISOString();
+ const input: Record = {
+ installation: {
+ ...validEnv("installation"),
+ updated_at: oldDate,
+ ttl_seconds: 1,
+ },
+ };
+ const result = validatePersistedEnvelopes(input, allModules);
+ expect(result["installation"].capability_state).toBe("unknown");
+ expect(result["installation"].reason?.code).toBe("installation.stale");
+ });
+
+ it("creates unknown envelopes for missing modules", () => {
+ const result = validatePersistedEnvelopes({}, allModules);
+ expect(result["installation"].capability_state).toBe("unknown");
+ expect(result["help"].capability_state).toBe("unknown");
+ });
+
+ it("replaces non-object entries with unknown envelopes", () => {
+ const input: Record = { installation: "string-value" };
+ const result = validatePersistedEnvelopes(input, allModules);
+ expect(result["installation"].capability_state).toBe("unknown");
+ expect(result["installation"].reason?.code).toBe("installation.no_probe");
+ });
+
+ it("replaces entries with wrong module name with invalid envelope", () => {
+ const input: Record = {
+ installation: { ...validEnv("help") }, // expects "installation" key but module="help"
+ };
+ const result = validatePersistedEnvelopes(input, allModules);
+ expect(result["installation"].capability_state).toBe("unknown");
+ expect(result["installation"].reason?.code).toBe("installation.invalid_response");
+ });
+
+ it("replaces entries with non-ready severity/state mismatch with invalid envelope", () => {
+ // isValidEnvelope checks validity not correctness — this passes validation
+ const input: Record = {
+ installation: {
+ ...validEnv("installation"),
+ capability_state: "unknown",
+ severity: "unknown",
+ updated_at: new Date(0).toISOString(),
+ ttl_seconds: 0,
+ },
+ };
+ const result = validatePersistedEnvelopes(input, allModules);
+ // Should be replaced as stale (TTL zero => always stale per isEnvelopeStale)
+ expect(result["installation"].capability_state).toBe("unknown");
+ expect(result["installation"].reason?.code).toBe("installation.stale");
+ });
+});
diff --git a/project/current/ocr-v2-active-queue.md b/project/current/ocr-v2-active-queue.md
index f505aa46..6b8fb3f7 100644
--- a/project/current/ocr-v2-active-queue.md
+++ b/project/current/ocr-v2-active-queue.md
@@ -1,5 +1,5 @@
# OCR-v2 Active Queue
-> Status: OCR-v2 is stable; the control-center contract and PRD are resolved; #75 canonical setup/config migration is complete and #76 is the next unblocked production slice.
+> 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
## Current checkpoint
@@ -23,6 +23,8 @@
- **[#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.
- **[#74](https://github.com/LLLin000/PaperForge/issues/74) published**: split into eight agent-ready issues (#75–#82) with native dependencies.
- **[#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.
## Verification status
@@ -35,6 +37,7 @@
- Prototype #72 (maintenance inbox): **Critical PASS (4/4), Important PASS (6/6)** — independent reviewer dimensions confirmed.
- 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.
- 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`.
@@ -44,7 +47,8 @@
- [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)).
-- [ ] Start [#76](https://github.com/LLLin000/PaperForge/issues/76) in a fresh Matt `/implement` session: Installation/Help capability envelope through the existing settings surface.
+- [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.
## Deferred
diff --git a/tests/test_probe.py b/tests/test_probe.py
new file mode 100644
index 00000000..ec8673c8
--- /dev/null
+++ b/tests/test_probe.py
@@ -0,0 +1,347 @@
+"""Tests for the paperforge probe CLI command (Issue #76, Issue #69 contract).
+
+Tests cover:
+- Envelope structure (all required fields, correct types)
+- Reason codes are module-prefixed snake_case
+- action.primary has full field set when non-null, null when ready
+- Installation probe state mapping (missing/corrupt/invalid-shape/ready/old-python)
+- Help probe state mapping (ready/limited)
+- Config shape validation (list/primitive/empty dict → config_corrupt)
+- CLI subprocess boundary
+"""
+
+from __future__ import annotations
+
+import json
+import subprocess
+import sys
+from pathlib import Path
+
+import pytest
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+REQUIRED_ENVELOPE_FIELDS = {
+ "schema_version", "module", "capability_state",
+ "activity_state", "activity_label", "activity_progress",
+ "severity", "reason", "action", "notices", "updated_at", "ttl_seconds",
+}
+
+REQUIRED_REASON_FIELDS = {"code", "text"}
+
+REQUIRED_ACTION_PRIMARY_FIELDS = {
+ "verb", "label", "destructive", "destructive_scope",
+ "destructive_effect", "confirmation_required", "confirmation_prompt",
+ "command", "scope", "scope_count",
+}
+
+VALID_STATES = {"unknown", "unavailable", "missing_input", "needs_action", "limited", "ready"}
+VALID_SEVERITIES = {"ok", "warning", "error", "info"}
+
+
+def _run_probe(module: str, vault: Path, extra_args: list[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).
+ """
+ cmd = [
+ sys.executable,
+ "-m",
+ "paperforge",
+ "--vault",
+ str(vault),
+ "probe",
+ module,
+ "--json",
+ ]
+ if extra_args:
+ cmd.extend(extra_args)
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
+ assert result.returncode == 0, (
+ f"CLI exited {result.returncode}\nstdout: {result.stdout[:500]}\nstderr: {result.stderr[:500]}"
+ )
+ return json.loads(result.stdout)
+
+
+def _assert_envelope_shape(data: dict) -> None:
+ """Assert envelope has all required fields with correct structural types."""
+ missing = REQUIRED_ENVELOPE_FIELDS - set(data.keys())
+ assert not missing, f"Missing envelope fields: {missing}"
+
+ assert isinstance(data["schema_version"], int), f"schema_version must be int, got {type(data['schema_version']).__name__}"
+ assert data["schema_version"] == 1, f"schema_version must be 1, got {data['schema_version']}"
+ assert isinstance(data["module"], str), "module must be str"
+ assert data["capability_state"] in VALID_STATES, f"invalid capability_state: {data['capability_state']}"
+ assert data["activity_state"] in ("idle", "running"), f"invalid activity_state: {data['activity_state']}"
+ assert data["activity_label"] is None or isinstance(data["activity_label"], str)
+ assert data["activity_progress"] is None or isinstance(data["activity_progress"], (int, float))
+ assert data["severity"] in VALID_SEVERITIES, f"invalid severity: {data['severity']}"
+ assert isinstance(data["reason"], dict), "reason must be dict"
+ reason_missing = REQUIRED_REASON_FIELDS - set(data["reason"].keys())
+ assert not reason_missing, f"Missing reason fields: {reason_missing}"
+ assert isinstance(data["reason"]["code"], str) and len(data["reason"]["code"]) > 0
+ assert isinstance(data["action"], dict), "action must be dict"
+ assert isinstance(data["notices"], list), "notices must be list"
+ assert isinstance(data["updated_at"], str), "updated_at must be str"
+ assert data["updated_at"].endswith("Z"), f"updated_at must be Z-suffixed, got {data['updated_at']}"
+ assert isinstance(data["ttl_seconds"], int), "ttl_seconds must be int"
+ assert data["ttl_seconds"] > 0, "ttl_seconds must be positive"
+
+
+def _assert_action_primary_shape(action_primary: dict | None) -> None:
+ """Assert action.primary has all required fields when non-null, or is None."""
+ if action_primary is None:
+ return
+ missing = REQUIRED_ACTION_PRIMARY_FIELDS - set(action_primary.keys())
+ assert not missing, f"Missing action.primary fields: {missing}"
+ assert isinstance(action_primary["verb"], str)
+ assert isinstance(action_primary["label"], str)
+ assert isinstance(action_primary["destructive"], bool)
+ assert action_primary["destructive_scope"] is None or isinstance(action_primary["destructive_scope"], str)
+ assert action_primary["destructive_effect"] is None or isinstance(action_primary["destructive_effect"], str)
+ assert isinstance(action_primary["confirmation_required"], bool)
+ assert isinstance(action_primary["scope"], str), f"scope must be str, got {type(action_primary['scope']).__name__}"
+ assert isinstance(action_primary["scope_count"], int), f"scope_count must be int, got {type(action_primary['scope_count']).__name__}"
+ assert isinstance(action_primary["command"], str)
+
+
+# ---------------------------------------------------------------------------
+# Envelope contract
+# ---------------------------------------------------------------------------
+
+class TestEnvelopeContract:
+ """Validate the schema-v1 envelope structure itself."""
+
+ def test_envelope_all_required_fields_present(self, tmp_path: Path) -> None:
+ """Installation probe output contains all required envelope fields."""
+ (tmp_path / "paperforge.json").write_text(
+ json.dumps({"system_dir": "99_System"}), encoding="utf-8",
+ )
+ data = _run_probe("installation", tmp_path)
+ _assert_envelope_shape(data)
+
+ def test_envelope_is_direct_not_pfresult_wrapped(self, tmp_path: Path) -> None:
+ """Output is a direct envelope, not wrapped in PFResult ok/command/data envelope."""
+ (tmp_path / "paperforge.json").write_text(
+ json.dumps({"system_dir": "99_System"}), encoding="utf-8",
+ )
+ data = _run_probe("installation", tmp_path)
+ assert "ok" not in data, "Output is PFResult-wrapped, expected bare envelope"
+ assert "command" not in data, "Output is PFResult-wrapped, expected bare envelope"
+ assert "module" in data
+ assert "capability_state" in data
+
+ def test_schema_version_is_integer_1(self, tmp_path: Path) -> None:
+ """schema_version must be the integer 1, never a string."""
+ (tmp_path / "paperforge.json").write_text(
+ json.dumps({"system_dir": "99_System"}), encoding="utf-8",
+ )
+ data = _run_probe("installation", tmp_path)
+ assert data["schema_version"] == 1
+ assert isinstance(data["schema_version"], int)
+ assert data["schema_version"] is not True # not a bool masquerading
+
+ def test_ready_state_action_primary_null(self, tmp_path: Path) -> None:
+ """When capability_state is 'ready', action.primary must be null."""
+ (tmp_path / "paperforge.json").write_text(
+ json.dumps({"system_dir": "99_System"}), encoding="utf-8",
+ )
+ data = _run_probe("installation", tmp_path)
+ if data["capability_state"] == "ready":
+ assert data["action"]["primary"] is None
+
+ def test_envelope_ttl_3600_for_installation(self, tmp_path: Path) -> None:
+ """Installation probe has ttl_seconds=3600."""
+ (tmp_path / "paperforge.json").write_text(
+ json.dumps({"system_dir": "99_System"}), encoding="utf-8",
+ )
+ data = _run_probe("installation", tmp_path)
+ assert data["ttl_seconds"] == 3600
+
+ def test_envelope_ttl_3600_for_help(self, tmp_path: Path) -> None:
+ """Help probe has ttl_seconds=3600."""
+ data = _run_probe("help", tmp_path)
+ assert data["ttl_seconds"] == 3600
+
+ def test_action_primary_full_fields_non_null(self, tmp_path: Path) -> None:
+ """Non-null action.primary has all required fields with correct types."""
+ (tmp_path / "paperforge.json").write_text(
+ json.dumps({"system_dir": "99_System"}), encoding="utf-8",
+ )
+ # Use the worktrees vault which has no paperforge.json → missing_input → has action
+ empty_vault = tmp_path / "no_config"
+ empty_vault.mkdir()
+ data = _run_probe("installation", empty_vault)
+ assert data["action"]["primary"] is not None
+ _assert_action_primary_shape(data["action"]["primary"])
+
+
+# ---------------------------------------------------------------------------
+# Installation probe states
+# ---------------------------------------------------------------------------
+
+class TestInstallationProbe:
+ """State mapping for the installation module probe."""
+
+ def test_missing_paperforge_json(self, tmp_path: Path) -> None:
+ """No paperforge.json -> missing_input + set_config action."""
+ data = _run_probe("installation", tmp_path)
+ assert data["module"] == "installation"
+ assert data["capability_state"] == "missing_input"
+ assert data["severity"] == "warning"
+ assert data["reason"]["code"] == "installation.config_missing"
+ assert data["action"]["primary"] is not None
+ _assert_action_primary_shape(data["action"]["primary"])
+ assert data["action"]["primary"]["verb"] == "set_config"
+ assert data["action"]["primary"]["label"] == "Set config"
+
+ def test_corrupt_paperforge_json(self, tmp_path: Path) -> None:
+ """Invalid JSON in paperforge.json -> unavailable + setup action."""
+ (tmp_path / "paperforge.json").write_text("not valid json {{{", encoding="utf-8")
+ data = _run_probe("installation", tmp_path)
+ assert data["capability_state"] == "unavailable"
+ assert data["severity"] == "error"
+ assert data["reason"]["code"] == "installation.config_corrupt"
+ assert data["action"]["primary"] is not None
+ _assert_action_primary_shape(data["action"]["primary"])
+ assert data["action"]["primary"]["verb"] == "setup"
+
+ def test_empty_object_config_corrupt(self, tmp_path: Path) -> None:
+ """Empty dict {} → config_corrupt/unavailable."""
+ (tmp_path / "paperforge.json").write_text("{}", encoding="utf-8")
+ data = _run_probe("installation", tmp_path)
+ assert data["capability_state"] == "unavailable"
+ assert data["severity"] == "error"
+ assert data["reason"]["code"] == "installation.config_corrupt"
+ assert data["action"]["primary"] is not None
+ assert data["action"]["primary"]["verb"] == "setup"
+
+ def test_list_config_corrupt(self, tmp_path: Path) -> None:
+ """JSON array → config_corrupt/unavailable."""
+ (tmp_path / "paperforge.json").write_text('["a", "b"]', encoding="utf-8")
+ data = _run_probe("installation", tmp_path)
+ assert data["capability_state"] == "unavailable"
+ assert data["reason"]["code"] == "installation.config_corrupt"
+ assert data["action"]["primary"]["verb"] == "setup"
+
+ def test_primitive_config_corrupt(self, tmp_path: Path) -> None:
+ """JSON string/number/true → config_corrupt/unavailable."""
+ for content in ('"just a string"', '42', 'true'):
+ (tmp_path / "paperforge.json").write_text(content, encoding="utf-8")
+ data = _run_probe("installation", tmp_path)
+ assert data["capability_state"] == "unavailable", f"content={content}"
+ assert data["reason"]["code"] == "installation.config_corrupt"
+
+ def test_unrecognized_keys_config_corrupt(self, tmp_path: Path) -> None:
+ """Dict with keys but no vault_config or legacy path keys → config_corrupt."""
+ (tmp_path / "paperforge.json").write_text(
+ json.dumps({"name": "Foo", "version": "1.0"}), encoding="utf-8",
+ )
+ data = _run_probe("installation", tmp_path)
+ assert data["capability_state"] == "unavailable"
+ assert data["reason"]["code"] == "installation.config_corrupt"
+ assert data["action"]["primary"]["verb"] == "setup"
+
+ def test_v2_vault_config_accepted(self, tmp_path: Path) -> None:
+ """v2 format with vault_config is accepted as valid config."""
+ (tmp_path / "paperforge.json").write_text(
+ json.dumps({"vault_config": {"system_dir": "99_System"}}), encoding="utf-8",
+ )
+ data = _run_probe("installation", tmp_path)
+ assert data["capability_state"] == "ready"
+ assert data["reason"]["code"] == "installation.ready"
+
+ def test_legacy_path_keys_accepted(self, tmp_path: Path) -> None:
+ """Legacy format with at least one legacy path key is accepted."""
+ (tmp_path / "paperforge.json").write_text(
+ json.dumps({"system_dir": "99_System", "resources_dir": "Resources"}), encoding="utf-8",
+ )
+ data = _run_probe("installation", tmp_path)
+ assert data["capability_state"] == "ready"
+ assert data["reason"]["code"] == "installation.ready"
+
+ def test_ready_reason_code(self, tmp_path: Path) -> None:
+ """Ready state has correct reason code and null action."""
+ (tmp_path / "paperforge.json").write_text(
+ json.dumps({"system_dir": "99_System"}), encoding="utf-8",
+ )
+ data = _run_probe("installation", tmp_path)
+ assert data["capability_state"] == "ready"
+ assert data["severity"] == "ok"
+ assert data["reason"]["code"] == "installation.ready"
+ assert data["action"]["primary"] is None
+
+
+# ---------------------------------------------------------------------------
+# Help probe states
+# ---------------------------------------------------------------------------
+
+class TestHelpProbe:
+ """State mapping for the help module probe."""
+
+ def test_ready(self, tmp_path: Path) -> None:
+ """Help probe returns ready when skill source is available."""
+ data = _run_probe("help", tmp_path)
+ assert data["module"] == "help"
+ assert data["capability_state"] == "ready"
+ assert data["severity"] == "ok"
+ assert data["action"]["primary"] is None
+ assert data["reason"]["code"] == "help.ready"
+
+ def test_limited_when_skill_source_missing(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
+ """When SKILL.md is absent, help probe returns limited with setup recovery."""
+ from paperforge.commands.probe import probe_help
+
+ orig = Path.exists
+ def fake_exists(self) -> bool:
+ s = str(self)
+ if "SKILL.md" in s and "skills" in s:
+ return False
+ return orig(self)
+
+ monkeypatch.setattr(Path, "exists", fake_exists)
+ envelope = probe_help(tmp_path)
+ assert envelope["capability_state"] == "limited"
+ assert envelope["severity"] == "warning"
+ assert envelope["reason"]["code"] == "help.docs_missing"
+ action = envelope["action"]["primary"]
+ _assert_action_primary_shape(action)
+ assert action["verb"] == "setup"
+
+
+# ---------------------------------------------------------------------------
+# CLI argument validation
+# ---------------------------------------------------------------------------
+
+class TestProbeCliArgs:
+ """CLI argument validation for the probe command."""
+
+ def test_rejects_unsupported_module(self, tmp_path: Path) -> None:
+ """Unknown module name is rejected by argparse."""
+ result = subprocess.run(
+ [sys.executable, "-m", "paperforge", "--vault", str(tmp_path),
+ "probe", "ocr", "--json"],
+ capture_output=True, text=True, timeout=15,
+ )
+ assert result.returncode != 0
+
+ def test_rejects_help_module(self, tmp_path: Path) -> None:
+ """--help/-h on probe subcommand shows usage."""
+ result = subprocess.run(
+ [sys.executable, "-m", "paperforge", "--vault", str(tmp_path),
+ "probe", "--help"],
+ capture_output=True, text=True, timeout=15,
+ )
+ assert result.returncode == 0
+ assert "usage:" in result.stdout.lower() or "usage:" in result.stderr.lower()
+
+ def test_module_required(self, tmp_path: Path) -> None:
+ """probe without a module argument fails."""
+ result = subprocess.run(
+ [sys.executable, "-m", "paperforge", "--vault", str(tmp_path),
+ "probe", "--json"],
+ capture_output=True, text=True, timeout=15,
+ )
+ assert result.returncode != 0