mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
fix: overhaul install flow clarity and non-destructive incremental setup
- Move Better BibTeX auto-export to post-install step (exports dir created by setup) - Clarify wizard overview: show resources_dir/literature_dir/control_dir hierarchy - Fix post-install flow order: BBT export → enable plugin → dashboard → sync - Split do_ocr and analyze marks into separate steps (OCR before deep-reading) - Mark all literature flags via Base views, not manual file editing - Make installer incremental: preserve existing files, only create missing ones - Remove destructive junction/link deletion in TUI wizard - Merge .env instead of overwriting; skip overwrite for AGENTS.md, docs, plugin - Update TUI, headless, and plugin completion output to match corrected flow - Add regression test for non-destructive setup behavior
This commit is contained in:
parent
1d52d94445
commit
29edb2d811
7 changed files with 353 additions and 189 deletions
54
AGENTS.md
54
AGENTS.md
|
|
@ -8,21 +8,26 @@
|
|||
|
||||
```
|
||||
[ ] Zotero 已安装 + Better BibTeX 插件已启用
|
||||
[ ] Better BibTeX 已配置自动导出 JSON(见下方配置)
|
||||
[ ] Obsidian 已打开当前 Vault
|
||||
[ ] PaperForge 已安装 (pip install git+https://github.com/LLLin000/PaperForge.git)
|
||||
[ ] PaddleOCR API Key 已配置(在 .env 中)
|
||||
[ ] 目录结构已创建(安装向导会自动完成)
|
||||
[ ] Zotero 数据目录已链接到 <system_dir>/Zotero
|
||||
[ ] Better BibTeX 已按下方步骤导出 JSON 到 <system_dir>/PaperForge/exports/
|
||||
```
|
||||
|
||||
> 安装向导是增量式的:如果你选择的 Vault 或目录里已经有文件,PaperForge 只会补充缺失的目录和文件,不会删除已有内容。
|
||||
|
||||
### Better BibTeX 自动导出配置
|
||||
|
||||
1. Zotero → Edit → Preferences → Better BibTeX
|
||||
2. 勾选 **"Keep updated"**(自动导出)
|
||||
3. 选择导出格式:**Better BibLaTeX** 或 **Better BibTeX**
|
||||
4. 导出路径设置为:`{你的Vault路径}/<system_dir>/PaperForge/exports/library.json`
|
||||
5. 点击 OK,JSON 文件会自动生成并保持同步
|
||||
这一步应在安装向导完成之后再做,因为 `exports/` 目录需要先由安装流程创建。
|
||||
|
||||
1. 打开 Zotero
|
||||
2. 对你要同步的文献库或分类右键 → `导出...`
|
||||
3. 选择格式:**Better BibTeX JSON**
|
||||
4. 勾选 **"Keep updated"**(自动导出)
|
||||
5. 保存到:`{你的Vault路径}/<system_dir>/PaperForge/exports/`
|
||||
6. JSON 文件名会作为 Base 名称,例如 `library.json`、`骨科.json`
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -330,11 +335,15 @@ pdf_link: "[[99_System/Zotero/storage/KEY/文件名.pdf]]"
|
|||
|
||||
## 8. 第一次使用指南(手把手)
|
||||
|
||||
### Step 1: 确认 Zotero 有文献
|
||||
### Step 1: 完成 Better BibTeX 自动导出
|
||||
|
||||
先按上面的步骤把 JSON 导出到 `<system_dir>/PaperForge/exports/`。这一步没完成前,`sync` 不会读到文献。
|
||||
|
||||
### Step 2: 确认 Zotero 有文献
|
||||
|
||||
确保 Zotero 中已有至少一篇带 PDF 的文献,且 Better BibTeX 已导出 JSON。
|
||||
|
||||
### Step 2: 运行 sync
|
||||
### Step 3: 运行 sync
|
||||
|
||||
```bash
|
||||
# 在 Vault 根目录执行
|
||||
|
|
@ -354,15 +363,11 @@ paperforge sync
|
|||
> - `paperforge sync --selection` — 仅同步 Zotero 到 library-records
|
||||
> - `paperforge sync --index` — 仅根据现有 library-records 生成正式笔记
|
||||
|
||||
### Step 3: 标记要精读的文献
|
||||
### Step 4: 在 Base 视图中标记 OCR
|
||||
|
||||
在 Obsidian 中:
|
||||
1. 打开 `<resources_dir>/<control_dir>/library-records/骨科/XXXXXXX.md`
|
||||
2. 将 `do_ocr: false` 改为 `do_ocr: true`
|
||||
3. 将 `analyze: false` 改为 `analyze: true`
|
||||
4. 保存文件
|
||||
打开 Obsidian Base 视图,找到该文献,将 `do_ocr` 设为 `true`。
|
||||
|
||||
### Step 4: 运行 OCR
|
||||
### Step 5: 运行 OCR
|
||||
|
||||
```bash
|
||||
paperforge ocr
|
||||
|
|
@ -370,21 +375,18 @@ paperforge ocr
|
|||
|
||||
等待完成(可能需要几分钟)。
|
||||
|
||||
### Step 5: 检查 OCR 状态
|
||||
### Step 6: 在 Base 视图中标记精读
|
||||
|
||||
OCR 完成后,在 Base 视图中找到该文献,将 `analyze` 设为 `true`。
|
||||
|
||||
### Step 7: 执行精读
|
||||
|
||||
先确认队列就绪:
|
||||
```bash
|
||||
paperforge deep-reading
|
||||
```
|
||||
|
||||
预期输出:
|
||||
```
|
||||
## 就绪 (1 篇) — OCR 完成
|
||||
- `XXXXXXX` | 骨科 | 论文标题
|
||||
```
|
||||
|
||||
### Step 6: 执行精读
|
||||
|
||||
在 OpenCode Agent 中输入:
|
||||
然后在 OpenCode Agent 中输入:
|
||||
```
|
||||
/pf-deep XXXXXXX
|
||||
```
|
||||
|
|
@ -394,7 +396,7 @@ Agent 会自动:
|
|||
2. 逐阶段填写精读内容
|
||||
3. 验证结构完整性
|
||||
|
||||
### Step 7: 查看结果
|
||||
### Step 8: 查看结果
|
||||
|
||||
在 Obsidian 中打开正式笔记,找到 `## 🔍 精读` 区域,精读已完成。
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
- Obsidian
|
||||
- PaddleOCR API Key([申请地址](https://paddleocr.baidu.com))
|
||||
|
||||
> 安装向导是**增量式**的:如果你选择的 Vault 或目录里已经有文件,PaperForge 只会创建缺失的目录和文件,不会删除已有内容。
|
||||
|
||||
---
|
||||
|
||||
## 安装(推荐方式:pip)
|
||||
|
|
@ -19,29 +21,44 @@
|
|||
# 1. 安装
|
||||
pip install git+https://github.com/LLLin000/PaperForge.git
|
||||
|
||||
# 2. 运行安装向导
|
||||
# 2. 运行安装向导(会把插件文件部署到当前 Vault)
|
||||
paperforge setup
|
||||
```
|
||||
|
||||
向导会引导你完成 Vault 配置、Zotero 链接、API Key 设置。
|
||||
向导会引导你完成 Vault 配置、Zotero 链接、API Key 设置,并把插件文件部署到 `.obsidian/plugins/paperforge/`。
|
||||
|
||||
安装向导完成后,才进入 Obsidian 启用插件;启用之前不能打开 Dashboard。
|
||||
|
||||
---
|
||||
|
||||
## Better BibTeX 配置
|
||||
|
||||
1. Zotero → Edit → Preferences → Better BibTeX
|
||||
2. 勾选 **"Keep updated"**
|
||||
3. 格式:Better BibLaTeX
|
||||
4. 导出路径:`{你的Vault}/[system_dir]/PaperForge/exports/library.json`
|
||||
5. 点击 OK
|
||||
这一步在 `paperforge setup` 完成之后再做,因为导出目录要先由安装向导创建。
|
||||
|
||||
1. 打开 Zotero
|
||||
2. 对你要同步的库或分类右键 → `Export...` / `导出...`
|
||||
3. 格式选择 **Better BibTeX JSON**
|
||||
4. 勾选 **"Keep updated"**
|
||||
5. 导出到:`{你的Vault}/[system_dir]/PaperForge/exports/`
|
||||
6. JSON 文件名会作为 Base 名称,例如:`library.json`、`骨科.json`
|
||||
|
||||
---
|
||||
|
||||
## 验证安装
|
||||
|
||||
```powershell
|
||||
paperforge status # 确认 setup 已经把 PaperForge 部署到当前 Vault
|
||||
```
|
||||
|
||||
然后在 Obsidian 中:
|
||||
|
||||
1. `Settings -> Community Plugins -> Installed -> PaperForge -> Enable`
|
||||
2. 按 `Ctrl+P` 搜索 `PaperForge`
|
||||
|
||||
之后再执行:
|
||||
|
||||
```powershell
|
||||
paperforge doctor # 诊断所有组件
|
||||
paperforge status # 查看系统状态
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -177,6 +177,8 @@ python -c "from paperforge.setup_wizard import EnvChecker; from pathlib import P
|
|||
|
||||
### Step 5: 创建目录并部署文件
|
||||
|
||||
重要安全规则:这个安装流程必须是增量式的。如果目标 Vault 或所选目录里已经有文件,PaperForge 只能创建缺失的目录和文件,不能覆盖已有内容。
|
||||
|
||||
把 Step 0 收集到的所有信息拼成一条命令:
|
||||
|
||||
```bash
|
||||
|
|
@ -238,20 +240,24 @@ python -m paperforge status
|
|||
|
||||
### Step 7: 告诉用户下一步
|
||||
|
||||
> 安装完成。接下来你需要做 3 件事:
|
||||
> 安装完成。接下来你需要做 4 件事:
|
||||
>
|
||||
> **1. 配置 Zotero 自动导出 JSON(必须)**
|
||||
> PaperForge 的数据来源,不做这一步 sync 无法工作:
|
||||
> - 打开 Zotero
|
||||
> - 文件 → 导出库 → 格式选 Better BibTeX
|
||||
> - 保存到 Vault 里的 <system_dir>/PaperForge/exports/
|
||||
> - 对要同步的文献库或分类右键 → 导出
|
||||
> - 格式选 Better BibTeX JSON
|
||||
> - 必须勾选 "保持更新"
|
||||
> - 保存到 Vault 里的 <system_dir>/PaperForge/exports/
|
||||
>
|
||||
> **2. 在 Obsidian 里启用 PaperForge 插件**
|
||||
> - 设置 → 社区插件 → 已安装 → PaperForge → 启用
|
||||
> - Ctrl+P 输入 "PaperForge"
|
||||
> - Ctrl+P 输入 "PaperForge",打开 Dashboard
|
||||
>
|
||||
> **3. 若跳过了 PaddleOCR Key**
|
||||
> **3. 同步文献**
|
||||
> - 在 Dashboard 中点击 `Sync Library`
|
||||
>
|
||||
> **4. 若跳过了 PaddleOCR Key**
|
||||
> - 在 <system_dir>/PaperForge/.env 里添加:
|
||||
> PADDLEOCR_API_TOKEN=<你的key>
|
||||
|
||||
|
|
|
|||
|
|
@ -177,6 +177,8 @@ python -c "from paperforge.setup_wizard import EnvChecker; from pathlib import P
|
|||
|
||||
### Step 5: Create directories and deploy files
|
||||
|
||||
Important safety rule: this setup flow is additive. If the target vault or selected directories already contain files, PaperForge should create only missing directories/files and preserve existing content.
|
||||
|
||||
Assemble one command from all Step 0 values:
|
||||
|
||||
```bash
|
||||
|
|
@ -243,15 +245,19 @@ python -m paperforge status
|
|||
> **1. Configure Zotero auto-export JSON (required)**
|
||||
> This is PaperForge's data source. Sync won't work without it:
|
||||
> - Open Zotero
|
||||
> - File → Export Library → Format: Better BibTeX
|
||||
> - Save to <system_dir>/PaperForge/exports/ in your vault
|
||||
> - Right-click the library or collection you want to sync → Export
|
||||
> - Format: Better BibTeX JSON
|
||||
> - Must check "Keep Updated"
|
||||
> - Save to <system_dir>/PaperForge/exports/ in your vault
|
||||
>
|
||||
> **2. Enable PaperForge plugin in Obsidian**
|
||||
> - Settings → Community Plugins → Installed → PaperForge → Enable
|
||||
> - Ctrl+P, type "PaperForge"
|
||||
> - Ctrl+P, type "PaperForge" and open the dashboard
|
||||
>
|
||||
> **3. If you skipped PaddleOCR Key**
|
||||
> **3. Sync literature**
|
||||
> - In the dashboard, click `Sync Library`
|
||||
>
|
||||
> **4. If you skipped PaddleOCR Key**
|
||||
> - Add to <system_dir>/PaperForge/.env:
|
||||
> PADDLEOCR_API_TOKEN=<your key>
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@
|
|||
2. Zotero 中已有至少一篇带 PDF 附件的文献
|
||||
3. Better BibTeX 插件已安装(Zotero → 工具 → 插件 → 搜索 Better BibTeX)
|
||||
|
||||
> 安装向导是**增量式**的:如果你选择的 Vault 或目录里已经有文件,PaperForge 只会创建缺失的目录和文件,不会删除已有内容。
|
||||
|
||||
---
|
||||
|
||||
## 2. 安装 PaperForge
|
||||
|
|
@ -93,47 +95,45 @@ paperforge setup
|
|||
|
||||
你会看到一个图形化界面(终端内的交互式界面)。
|
||||
|
||||
这一步会把 PaperForge 需要的文件部署到当前 Vault,包括 `.obsidian/plugins/paperforge/`。在这一步完成之前,Obsidian 里还不能启用插件,也不能打开 Dashboard。
|
||||
|
||||
### 3.2 向导步骤详解
|
||||
|
||||
**第 1 步:选择 Agent 平台**
|
||||
- 选择你用哪个 AI 编程工具:OpenCode / Cursor / Claude Code / Windsurf / GitHub Copilot / Cline / Augment / Trae
|
||||
- 这决定了后续脚本部署的位置
|
||||
**第 1 步:概览**
|
||||
- 向导会先展示目录层级,帮助你理解哪些内容在 Vault 根目录,哪些内容在 `resources_dir` 下面
|
||||
- 重点关系是:
|
||||
- `<resources_dir>/<literature_dir>/`:正式文献笔记
|
||||
- `<resources_dir>/<control_dir>/library-records/`:每篇文献的状态跟踪
|
||||
- `<system_dir>/PaperForge/exports/`:Better BibTeX JSON 导出目录
|
||||
- 这一步也会明确说明:如果目标目录里已有文件,安装只做增量创建,不覆盖已有内容
|
||||
|
||||
**第 2 步:目录配置**
|
||||
- 在这里设置目录名称,而不是手动创建目录
|
||||
- 默认结构如下:
|
||||
- `99_System`:系统文件、OCR 结果、exports、worker
|
||||
- `03_Resources`:用户文献数据根目录
|
||||
- `03_Resources/Literature`:正式文献笔记
|
||||
- `03_Resources/LiteratureControl/library-records`:状态卡片
|
||||
- `05_Bases`:Obsidian Base 视图
|
||||
|
||||
**第 3 步:平台与密钥**
|
||||
- 选择 Agent 平台:OpenCode / Cursor / Claude Code / Windsurf / GitHub Copilot / Cline / Augment / Trae
|
||||
- **如果不确定,选 OpenCode**
|
||||
- 填写 PaddleOCR API Key
|
||||
- 可选填写 Zotero 数据目录,便于自动定位 PDF
|
||||
|
||||
**第 2 步:配置 Vault 路径**
|
||||
- Vault 是你的 Obsidian 笔记库根目录
|
||||
- 如果已经在 Obsidian 中打开了一个 vault,直接输入该目录路径
|
||||
- 如果还没有 vault,建议先创建一个空目录
|
||||
|
||||
**第 3 步:配置目录名称**
|
||||
- **系统目录(system_dir)**:PaperForge 内部文件存放的目录,默认 `99_System`
|
||||
- 包含:OCR 结果、Zotero 导出、配置文件
|
||||
- 建议保持默认,或改为 `System`
|
||||
- **资源目录(resources_dir)**:文献笔记存放的目录,默认 `03_Resources`
|
||||
- 包含:文献索引、library-records
|
||||
- 建议保持默认
|
||||
|
||||
**第 4 步:配置 Zotero 数据目录**
|
||||
- 向导会自动检测 Zotero 数据目录位置
|
||||
- 如果自动检测失败,手动输入路径:
|
||||
- Windows 默认:`C:\Users\你的用户名\Zotero`
|
||||
- macOS 默认:`~/Zotero`
|
||||
- 向导会自动创建 junction/symlink 链接到 vault 内
|
||||
|
||||
**第 5 步:配置 PaddleOCR API Key**
|
||||
- 输入从 https://paddleocr.baidu.com 获取的 API Token
|
||||
- API URL 保持默认即可:`https://paddleocr.aistudio-app.com/api/v2/ocr/jobs`
|
||||
|
||||
**第 6 步:一键部署**
|
||||
**第 4 步:安装**
|
||||
- 向导会自动:
|
||||
1. 创建 vault 目录结构
|
||||
2. 复制 worker 脚本
|
||||
3. 复制精读脚本(ld_deep.py)
|
||||
4. 复制图表阅读指南
|
||||
5. 复制 Agent 命令文件
|
||||
6. 生成 `.env` 配置文件
|
||||
7. 生成 `paperforge.json`
|
||||
8. 验证文件完整性
|
||||
1. 创建缺失目录
|
||||
2. 部署 worker、技能和插件文件
|
||||
3. 写入 `paperforge.json`
|
||||
4. 创建或补充 `.env`
|
||||
5. 验证安装结果
|
||||
|
||||
**第 5 步:完成**
|
||||
- 先回到 Obsidian 启用插件:`Settings -> Community Plugins -> Installed -> PaperForge -> Enable`
|
||||
- 然后再配置 Better BibTeX 自动导出
|
||||
- 最后才是打开 Dashboard、运行 `Sync Library`、运行 OCR
|
||||
|
||||
### 3.3 安装后文件结构
|
||||
|
||||
|
|
@ -156,14 +156,13 @@ your-vault/
|
|||
│ └── .env # PaddleOCR API Key
|
||||
│
|
||||
├── [resources_dir]/ # 你自定义的资源目录
|
||||
│ └── [literature_dir]/ # 文献笔记
|
||||
│ └── [domain]/ # 按 Zotero 分类
|
||||
│ └── [key] - [Title].md # 正式文献笔记
|
||||
│
|
||||
├── [control_dir]/ # 文献状态控制
|
||||
│ └── library-records/
|
||||
│ └── [domain]/
|
||||
│ └── [key].md # 单条文献状态
|
||||
│ ├── [literature_dir]/ # 正式文献笔记
|
||||
│ │ └── [domain]/ # 按 Zotero 分类
|
||||
│ │ └── [key] - [Title].md # 正式文献笔记
|
||||
│ └── [control_dir]/ # 文献状态控制
|
||||
│ └── library-records/
|
||||
│ └── [domain]/
|
||||
│ └── [key].md # 单条文献状态
|
||||
│
|
||||
├── [base_dir]/ # Obsidian Base 视图
|
||||
│ └── [domain].base # 每个分类一个 Base
|
||||
|
|
@ -185,17 +184,20 @@ your-vault/
|
|||
|
||||
### 4.1 配置 Better BibTeX 自动导出
|
||||
|
||||
这一步要在安装向导完成后再做,因为 `exports/` 目录要先由安装流程创建。
|
||||
|
||||
这一步是关键——PaperForge 通过 Better BibTeX 的自动导出 JSON 来感知 Zotero 中的文献变化。
|
||||
|
||||
1. 打开 Zotero → Edit → Preferences → Better BibTeX
|
||||
2. **勾选 "Keep updated"**(自动导出)
|
||||
3. **导出格式**:选择 **Better BibLaTeX**
|
||||
4. **导出路径**设置为:
|
||||
1. 打开 Zotero
|
||||
2. 对你要同步的文献库或分类右键 → **Export... / 导出...**
|
||||
3. **导出格式**:选择 **Better BibTeX JSON**
|
||||
4. **勾选 "Keep updated"**(自动导出)
|
||||
5. **导出路径**设置为:
|
||||
```
|
||||
{你的Vault路径}/[system_dir]/PaperForge/exports/library.json
|
||||
```
|
||||
例如:`C:\MyVault\99_System\PaperForge\exports\library.json`
|
||||
5. **点击 OK 保存**
|
||||
6. **点击保存**
|
||||
|
||||
### 4.2 按分类导出(可选)
|
||||
|
||||
|
|
@ -279,18 +281,9 @@ paperforge sync
|
|||
- `[base_dir]/` 下有了 `.base` 文件(Base 视图)
|
||||
- 在 Obsidian 的 Base 插件中可以看到控制面板
|
||||
|
||||
### 6.3 标记要精读的文献
|
||||
### 6.3 在 Base 视图中标记 OCR
|
||||
|
||||
在 Obsidian 中找到 `library-records/[domain]/[key].md`,修改 frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
do_ocr: true # 需要 OCR 提取全文
|
||||
analyze: true # 需要精读
|
||||
---
|
||||
```
|
||||
|
||||
保存后,该文献会进入 OCR 队列。
|
||||
在 Obsidian Base 视图中找到该文献,将 `do_ocr` 设为 `true`。保存后该文献会进入 OCR 队列。
|
||||
|
||||
### 6.4 运行 OCR
|
||||
|
||||
|
|
@ -318,7 +311,11 @@ ocr: updated 2 records
|
|||
- `[system_dir]/PaperForge/ocr/[key]/meta.json` — OCR 元数据
|
||||
- `[system_dir]/PaperForge/ocr/[key]/figure-map.json` — 图表索引
|
||||
|
||||
### 6.5 执行精读
|
||||
### 6.5 在 Base 视图中标记精读
|
||||
|
||||
OCR 完成后,在 Obsidian Base 视图中找到该文献,将 `analyze` 设为 `true`。
|
||||
|
||||
### 6.6 执行精读
|
||||
|
||||
在 OpenCode 中(或你选择的 Agent 平台)输入:
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ Object.assign(LANG.en, {
|
|||
setup_done: 'PaperForge environment is ready',
|
||||
setup_pending: 'Not installed yet. Finish the preparation items below, then open the wizard.',
|
||||
section_prep: 'Preparation',
|
||||
section_prep_desc: 'Before first use, finish these 5 preparation items, then open the setup wizard:',
|
||||
section_prep_desc: 'Before first use, finish these 4 preparation items. Better BibTeX auto-export is configured after setup:',
|
||||
section_guide: 'How To Use',
|
||||
section_config: 'Current Configuration',
|
||||
prep_python_desc: 'Python must be available from the command line. If you are not sure, click below to auto-detect.',
|
||||
|
|
@ -38,7 +38,7 @@ Object.assign(LANG.en, {
|
|||
prep_key_desc: 'Get your API key from https://aistudio.baidu.com/paddleocr',
|
||||
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_desc: 'In the main panel, click Sync Library to import papers from Zotero into Obsidian and generate notes automatically.',
|
||||
guide_sync_desc: 'After Better BibTeX JSON export is configured, click Sync Library to import papers from Zotero into Obsidian and generate notes automatically.',
|
||||
guide_ocr_desc: 'In the main panel, click Run OCR to extract full text and figures from PDFs for later reading and analysis.',
|
||||
btn_install: 'Open Setup Wizard',
|
||||
btn_install_desc: 'Check whether the environment is ready, then open the step-by-step setup wizard',
|
||||
|
|
@ -46,12 +46,13 @@ Object.assign(LANG.en, {
|
|||
wizard_step2: 'Directory Setup',
|
||||
wizard_step3: 'Platform & Keys',
|
||||
wizard_intro: 'This wizard walks you through the full setup. In most cases, the default values are fine to keep.',
|
||||
wizard_dir_hint: 'The resources directory is the main folder for your literature data. PaperForge will create these folders inside it:',
|
||||
wizard_dir_sub_hint: 'Sub-folders under the resources directory:',
|
||||
wizard_sys_hint: 'System folders stored at the vault root:',
|
||||
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_sys_hint: 'These folders live at the vault root, outside the resources directory:',
|
||||
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_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 and agent config stay at the vault root, while literature data is stored under the resources 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.',
|
||||
field_zotero_placeholder: 'Optional. Helps auto-locate PDF attachments in Zotero storage',
|
||||
install_btn: 'Start Install',
|
||||
install_validating: 'Validating setup...',
|
||||
|
|
@ -63,8 +64,9 @@ Object.assign(LANG.en, {
|
|||
complete_next: 'Recommended next steps',
|
||||
complete_step1_desc: 'Press Ctrl+P and run "PaperForge: Open Main Panel", or click the PaperForge icon in the left sidebar.',
|
||||
complete_step2_desc: 'In the main panel, click Sync Library to bring papers from Zotero into Obsidian and generate notes.',
|
||||
complete_step3_desc: 'In the main panel, click Run OCR to extract full text and figures from PDFs.',
|
||||
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_export_path: 'Save Better BibTeX JSON exports into:',
|
||||
nav_prev: 'Back',
|
||||
nav_next: 'Next',
|
||||
|
|
@ -86,7 +88,7 @@ Object.assign(LANG.zh, {
|
|||
desc: 'Obsidian + Zotero 文献管理流水线。自动同步文献、生成笔记、OCR 提取全文,一站式完成文献整理与精读。',
|
||||
setup_done: 'PaperForge 环境已准备完成',
|
||||
setup_pending: '尚未完成安装。请先完成下面的准备,再打开安装向导。',
|
||||
section_prep_desc: '第一次使用前,请先完成下面 5 项准备。完成后再打开安装向导:',
|
||||
section_prep_desc: '第一次使用前,请先完成下面 4 项准备。Better BibTeX 自动导出放在安装完成后配置:',
|
||||
prep_python_desc: '确认系统可以直接运行 Python 命令。如果不确定,点击下方按钮自动检测。',
|
||||
prep_zotero_desc: '先安装 Zotero:https://www.zotero.org',
|
||||
prep_bbt_desc: '在 Zotero 中依次打开:工具 -> 插件 -> 安装 Better BibTeX。',
|
||||
|
|
@ -95,19 +97,20 @@ Object.assign(LANG.zh, {
|
|||
prep_export_path_label: '请把导出的 JSON 文件保存到这个文件夹:',
|
||||
guide_open: '打开主面板',
|
||||
guide_open_desc: '按 Ctrl+P,输入 PaperForge: 打开主面板;或点击左侧的 PaperForge 图标。',
|
||||
guide_sync_desc: '在主面板点击 Sync Library,把 Zotero 中的文献同步到 Obsidian,并自动生成笔记。',
|
||||
guide_sync_desc: '先完成 Better BibTeX JSON 自动导出配置,再在主面板点击 Sync Library,把 Zotero 文献同步到 Obsidian 并自动生成笔记。',
|
||||
guide_ocr_desc: '在主面板点击 Run OCR,从 PDF 中提取全文和图表,供后续精读和分析使用。',
|
||||
btn_install_desc: '先检查环境是否就绪,再打开分步安装向导',
|
||||
btn_reconfig_desc: '重新打开安装向导,修改目录、平台或密钥配置',
|
||||
wizard_step2: '目录配置',
|
||||
wizard_step3: '平台与密钥',
|
||||
wizard_intro: '这个向导会一步步帮你完成安装。大部分选项保持默认即可,安装时会自动创建所需目录。',
|
||||
wizard_dir_hint: '资源目录是文献数据的主目录。PaperForge 会在里面创建以下子目录:',
|
||||
wizard_dir_sub_hint: '资源目录中的子目录:',
|
||||
wizard_sys_hint: '位于 Vault 根目录的系统文件:',
|
||||
wizard_dir_hint: '资源目录用于存放用户可见的文献数据。下面这些目录都会位于资源目录内部:',
|
||||
wizard_dir_sub_hint: '根据下面的目录名,最终路径会是:',
|
||||
wizard_sys_hint: '这些目录位于 Vault 根目录,不属于资源目录:',
|
||||
wizard_agent_hint: '选择你平时使用的 AI Agent 平台。安装完成后,PaperForge 会把对应的命令和技能文件放到正确位置。',
|
||||
wizard_keys_hint: '下面填写 PaddleOCR API 密钥;如果你希望自动定位 Zotero 中的 PDF,也可以补充 Zotero 数据目录。',
|
||||
wizard_preview: '安装后:系统文件和 Agent 配置位于 Vault 根目录,文献数据统一放在资源目录内。以后仍可在设置页修改。',
|
||||
wizard_preview: '安装后:系统文件位于 Vault 根目录,文献数据统一放在资源目录内。以后仍可在设置页修改。',
|
||||
wizard_safety: '安全说明:如果你选择的目录里已经有文件,安装向导会保留已有内容,只补充缺失的 PaperForge 文件和目录。',
|
||||
field_zotero_placeholder: '可选。填写后可帮助自动定位 Zotero 存储中的 PDF 附件',
|
||||
install_validating: '正在校验安装环境...',
|
||||
install_bootstrapping: '未检测到 PaperForge Python 包,正在自动安装...',
|
||||
|
|
@ -117,9 +120,10 @@ Object.assign(LANG.zh, {
|
|||
complete_summary: '已保存的安装配置',
|
||||
complete_next: '建议下一步',
|
||||
complete_step1_desc: '按 Ctrl+P,输入 PaperForge: 打开主面板;或点击左侧的 PaperForge 图标。',
|
||||
complete_step2_desc: '在主面板点击 Sync Library,把 Zotero 文献同步到 Obsidian,并自动生成笔记。',
|
||||
complete_step3_desc: '在主面板点击 Run OCR,从 PDF 中提取全文和图表。',
|
||||
complete_step2_desc: '在主面板点击 Sync Library,把 Zotero 文献同步到 Obsidian 并自动生成笔记。',
|
||||
complete_step3_desc: '在 Obsidian Base 视图中将文献的 do_ocr 设为 true,然后在主面板点击 Run OCR。',
|
||||
complete_step4: '配置 Better BibTeX 自动导出',
|
||||
complete_step4_desc: '在 Zotero 中右键要同步的文献库或分类 -> 导出 -> 选择 Better BibTeX JSON -> 勾选“保持更新”。',
|
||||
complete_export_path: 'Better BibTeX 导出的 JSON 文件请保存到:',
|
||||
nav_prev: '上一步',
|
||||
nav_next: '下一步',
|
||||
|
|
@ -1548,17 +1552,12 @@ class PaperForgeSettingTab extends PluginSettingTab {
|
|||
['prep_python', 'prep_python_desc'],
|
||||
['prep_zotero', 'prep_zotero_desc'],
|
||||
['prep_bbt', 'prep_bbt_desc'],
|
||||
['prep_export', 'prep_export_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: ' — ' + t(kDesc) });
|
||||
if (kTitle === 'prep_export') {
|
||||
const expRow = prep.createEl('div', { cls: 'paperforge-guide-item' });
|
||||
expRow.createEl('span', { text: `${t('prep_export_path_label')} ${vaultPath}/${this._pfConfig.system_dir}/PaperForge/exports/` });
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Pre-check status area ── */
|
||||
|
|
@ -1805,6 +1804,21 @@ class PaperForgeSetupModal extends Modal {
|
|||
</div>`;
|
||||
|
||||
el.createEl('p', { text: t('wizard_preview'), cls: 'paperforge-modal-hint' });
|
||||
el.createEl('p', { text: t('wizard_safety'), cls: 'paperforge-modal-hint' });
|
||||
|
||||
const summary = el.createEl('div', { cls: 'paperforge-summary' });
|
||||
const overviewItems = [
|
||||
{ label: t('dir_resources'), val: `${vault}/${s.resources_dir || '03_Resources'}` },
|
||||
{ label: t('dir_notes'), val: `${vault}/${s.resources_dir || '03_Resources'}/${s.literature_dir || 'Literature'}` },
|
||||
{ label: t('dir_index'), val: `${vault}/${s.resources_dir || '03_Resources'}/${s.control_dir || 'LiteratureControl'}` },
|
||||
{ label: t('dir_base'), val: `${vault}/${s.base_dir || '05_Bases'}` },
|
||||
{ label: t('dir_system'), val: `${vault}/${s.system_dir || '99_System'}` },
|
||||
];
|
||||
for (const item of overviewItems) {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Step 2: Directory Config (editable) ── */
|
||||
|
|
@ -1830,6 +1844,21 @@ class PaperForgeSetupModal extends Modal {
|
|||
|
||||
this._modalInput(el, t('dir_system'), 'system_dir', s.system_dir, 'System');
|
||||
this._modalInput(el, t('dir_base'), 'base_dir', s.base_dir, 'Base');
|
||||
|
||||
el.createEl('p', { text: t('wizard_safety'), cls: 'paperforge-modal-hint' });
|
||||
const preview = el.createEl('div', { cls: 'paperforge-summary' });
|
||||
const previewItems = [
|
||||
{ label: t('dir_resources'), val: `${vault}/${s.resources_dir || ''}` },
|
||||
{ label: t('dir_notes'), val: `${vault}/${s.resources_dir || ''}/${s.literature_dir || ''}` },
|
||||
{ label: t('dir_index'), val: `${vault}/${s.resources_dir || ''}/${s.control_dir || ''}` },
|
||||
{ label: t('dir_system'), val: `${vault}/${s.system_dir || ''}` },
|
||||
{ label: t('dir_base'), val: `${vault}/${s.base_dir || ''}` },
|
||||
];
|
||||
for (const item of previewItems) {
|
||||
const row = preview.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 });
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Step 3: Keys, Zotero & Agent ── */
|
||||
|
|
@ -2076,11 +2105,11 @@ class PaperForgeSetupModal extends Modal {
|
|||
el.createEl('h3', { text: t('complete_next') });
|
||||
const nextList = el.createEl('div', { cls: 'paperforge-nextsteps' });
|
||||
const steps = [
|
||||
[t('complete_step4'), t('complete_step4_desc')],
|
||||
['', `${t('complete_export_path')} ${vault}/${s.system_dir}/PaperForge/exports/`],
|
||||
[t('complete_step1'), t('complete_step1_desc')],
|
||||
[t('complete_step2'), t('complete_step2_desc')],
|
||||
[t('complete_step3'), t('complete_step3_desc')],
|
||||
[t('complete_step4'), t('complete_step4_desc')],
|
||||
['', `${t('complete_export_path')} ${vault}/${s.system_dir}/PaperForge/exports/`],
|
||||
];
|
||||
for (const [title, desc] of steps) {
|
||||
const item = nextList.createEl('div', { cls: 'paperforge-nextstep-item' });
|
||||
|
|
|
|||
|
|
@ -790,15 +790,12 @@ class ZoteroStep(StepScreen):
|
|||
system_dir = getattr(self.app, "vault_config", {}).get("system_dir", "99_System")
|
||||
junction_path = vault / system_dir / "Zotero"
|
||||
|
||||
# Remove existing
|
||||
if junction_path.exists() or junction_path.is_symlink():
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
subprocess.run(["cmd", "/c", "rmdir", str(junction_path)], check=True, capture_output=True)
|
||||
else:
|
||||
junction_path.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
self.app.zotero_data_dir = str(zotero_data)
|
||||
self.app.zotero_link = str(junction_path)
|
||||
self.set_status("已存在 Zotero 目录/链接,安装向导不会覆盖已有内容", True)
|
||||
self.app.post_message(StepPassed(self.step_idx))
|
||||
return
|
||||
|
||||
try:
|
||||
junction_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
|
@ -867,8 +864,8 @@ class JsonStep(StepScreen):
|
|||
**Better BibTeX 自动导出**是 PaperForge 的数据来源。
|
||||
|
||||
**配置步骤:**
|
||||
1. Zotero → 文件 → 导出库...
|
||||
2. 格式选择 **Better BibTeX**
|
||||
1. 在 Zotero 中,对要同步的文献库或分类右键 → **导出...**
|
||||
2. 格式选择 **Better BibTeX JSON**
|
||||
3. 保存到:`{system_dir}/PaperForge/exports/`
|
||||
4. **[必须] 勾选 "保持更新"** — 这是自动同步的关键!
|
||||
|
||||
|
|
@ -1058,7 +1055,7 @@ class DeployStep(StepScreen):
|
|||
if worker_src.exists():
|
||||
import shutil
|
||||
|
||||
shutil.copy2(worker_src, worker_dst)
|
||||
_copy_file_incremental(worker_src, worker_dst)
|
||||
# Also copy other worker modules
|
||||
for mod in [
|
||||
"ocr.py",
|
||||
|
|
@ -1071,13 +1068,13 @@ class DeployStep(StepScreen):
|
|||
]:
|
||||
mod_src = repo_root / "paperforge/worker" / mod
|
||||
if mod_src.exists():
|
||||
shutil.copy2(mod_src, pf_path / "worker/scripts" / mod)
|
||||
_copy_file_incremental(mod_src, pf_path / "worker/scripts" / mod)
|
||||
else:
|
||||
# Fallback to old pipeline location for backward compatibility
|
||||
worker_src = repo_root / "pipeline/worker/scripts/literature_pipeline.py"
|
||||
worker_dst = pf_path / "worker/scripts/literature_pipeline.py"
|
||||
if worker_src.exists():
|
||||
shutil.copy2(worker_src, worker_dst)
|
||||
_copy_file_incremental(worker_src, worker_dst)
|
||||
else:
|
||||
self.set_status(f"错误:找不到 worker 脚本: {worker_src}", False)
|
||||
return False
|
||||
|
|
@ -1088,7 +1085,7 @@ class DeployStep(StepScreen):
|
|||
ld_src = repo_root / "skills/literature-qa/scripts/ld_deep.py"
|
||||
ld_dst = vault / skill_dir / "literature-qa/scripts/ld_deep.py"
|
||||
if ld_src.exists():
|
||||
shutil.copy2(ld_src, ld_dst)
|
||||
_copy_file_incremental(ld_src, ld_dst)
|
||||
else:
|
||||
self.set_status(f"错误:找不到 ld_deep.py: {ld_src}", False)
|
||||
return False
|
||||
|
|
@ -1099,7 +1096,7 @@ class DeployStep(StepScreen):
|
|||
prompt_src = repo_root / "skills/literature-qa/prompt_deep_subagent.md"
|
||||
prompt_dst = vault / skill_dir / "literature-qa/prompt_deep_subagent.md"
|
||||
if prompt_src.exists():
|
||||
shutil.copy2(prompt_src, prompt_dst)
|
||||
_copy_file_incremental(prompt_src, prompt_dst)
|
||||
else:
|
||||
self.set_status(f"错误:找不到 prompt_deep_subagent.md: {prompt_src}", False)
|
||||
return False
|
||||
|
|
@ -1111,7 +1108,7 @@ class DeployStep(StepScreen):
|
|||
chart_dst = vault / skill_dir / "literature-qa/chart-reading"
|
||||
if chart_src.exists() and chart_src.is_dir():
|
||||
for f in chart_src.glob("*.md"):
|
||||
shutil.copy2(f, chart_dst / f.name)
|
||||
_copy_file_incremental(f, chart_dst / f.name)
|
||||
|
||||
# Copy OpenCode command files when the target platform supports them.
|
||||
if getattr(self.app, "agent_key", "") == "opencode":
|
||||
|
|
@ -1123,15 +1120,20 @@ class DeployStep(StepScreen):
|
|||
command_dst.mkdir(parents=True, exist_ok=True)
|
||||
for f in command_src.glob("*.md"):
|
||||
text = apply_user_paths(f.read_text(encoding="utf-8"), skill_dir)
|
||||
(command_dst / f.name).write_text(text, encoding="utf-8")
|
||||
_write_text_incremental(command_dst / f.name, text)
|
||||
|
||||
# Copy user-facing docs. AGENTS.md is regenerated below with the chosen paths.
|
||||
docs_src = repo_root / "docs"
|
||||
docs_dst = vault / "docs"
|
||||
if docs_src.exists() and docs_src.is_dir():
|
||||
shutil.copytree(docs_src, docs_dst, dirs_exist_ok=True)
|
||||
for doc in docs_dst.rglob("*.md"):
|
||||
doc.write_text(apply_user_paths(doc.read_text(encoding="utf-8"), skill_dir), encoding="utf-8")
|
||||
for src_doc in docs_src.rglob("*.md"):
|
||||
rel = src_doc.relative_to(docs_src)
|
||||
text = apply_user_paths(src_doc.read_text(encoding="utf-8"), skill_dir)
|
||||
_write_text_incremental(docs_dst / rel, text)
|
||||
for src_other in docs_src.rglob("*"):
|
||||
if src_other.is_dir() or src_other.suffix.lower() == ".md":
|
||||
continue
|
||||
_copy_file_incremental(src_other, docs_dst / src_other.relative_to(docs_src))
|
||||
|
||||
# 5. 创建 .env(放到 PaperForge 目录下)
|
||||
from textual.widgets import Input
|
||||
|
|
@ -1161,7 +1163,15 @@ PADDLEOCR_MODEL=PaddleOCR-VL-1.5
|
|||
# Zotero data directory selected during setup
|
||||
ZOTERO_DATA_DIR={getattr(self.app, 'zotero_data_dir', '')}
|
||||
"""
|
||||
env_path.write_text(env_content, encoding="utf-8")
|
||||
_merge_env_incremental(
|
||||
env_path,
|
||||
{
|
||||
"PADDLEOCR_API_TOKEN": api_key,
|
||||
"PADDLEOCR_JOB_URL": api_url,
|
||||
"PADDLEOCR_MODEL": "PaddleOCR-VL-1.5",
|
||||
"ZOTERO_DATA_DIR": getattr(self.app, "zotero_data_dir", ""),
|
||||
},
|
||||
)
|
||||
|
||||
# Create a minimal domain mapping. The worker will keep this usable even
|
||||
# before JSON exports exist, and will infer domains from export filenames.
|
||||
|
|
@ -1214,7 +1224,7 @@ ZOTERO_DATA_DIR={getattr(self.app, 'zotero_data_dir', '')}
|
|||
agents_dst = vault / "AGENTS.md"
|
||||
if agents_src.exists():
|
||||
agents_text = apply_user_paths(agents_src.read_text(encoding="utf-8"), skill_dir)
|
||||
agents_dst.write_text(agents_text, encoding="utf-8")
|
||||
_write_text_incremental(agents_dst, agents_text)
|
||||
|
||||
# 7. 验证文件完整性
|
||||
self.set_status("验证文件完整性...", None)
|
||||
|
|
@ -1286,7 +1296,17 @@ PaperForge 已完成安装和初始化。以下是立即开始使用的步骤:
|
|||
|
||||
### 首次使用步骤:
|
||||
|
||||
**1. 同步 Zotero 文献并生成正式笔记**
|
||||
**1. 配置 Better BibTeX 自动导出**
|
||||
1. 在 Zotero 中,对要同步的文献库或分类右键 → 导出
|
||||
2. 格式选择 **Better BibTeX JSON**
|
||||
3. 保存到:`{system_dir}/PaperForge/exports/`
|
||||
4. **必须勾选 "保持更新"**
|
||||
|
||||
**2. 在 Obsidian 里启用插件并打开 Dashboard**
|
||||
1. `Settings -> Community Plugins -> Installed -> PaperForge -> Enable`
|
||||
2. `Ctrl+P` 搜索 `PaperForge`
|
||||
|
||||
**3. 同步 Zotero 文献并生成正式笔记**
|
||||
```bash
|
||||
paperforge sync
|
||||
```
|
||||
|
|
@ -1297,12 +1317,10 @@ paperforge sync --selection # 仅同步 Zotero 到 library-records
|
|||
paperforge sync --index # 仅根据现有 library-records 生成正式笔记
|
||||
```
|
||||
|
||||
**2. 标记精读文献**
|
||||
在 Obsidian 中打开 library-records 文件,设置:
|
||||
- `do_ocr: true`
|
||||
- `analyze: true`
|
||||
**4. 在 Base 视图中标记 OCR**
|
||||
打开 Obsidian Base 视图,找到该文献,将 `do_ocr` 设为 `true`。
|
||||
|
||||
**3. 运行 OCR**
|
||||
**5. 运行 OCR**
|
||||
```bash
|
||||
paperforge ocr
|
||||
```
|
||||
|
|
@ -1312,7 +1330,10 @@ paperforge ocr
|
|||
paperforge ocr --diagnose
|
||||
```
|
||||
|
||||
**4. 执行精读**
|
||||
**6. 在 Base 视图中标记精读**
|
||||
OCR 完成后,在 Base 视图中找到该文献,将 `analyze` 设为 `true`。
|
||||
|
||||
**7. 执行精读**
|
||||
在 OpenCode Agent 中输入:
|
||||
```
|
||||
/pf-deep <zotero_key>
|
||||
|
|
@ -1598,6 +1619,80 @@ def _substitute_vars(
|
|||
return text
|
||||
|
||||
|
||||
def _copy_file_incremental(src: Path, dst: Path) -> bool:
|
||||
"""Copy a file only when the destination is missing."""
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
if dst.exists():
|
||||
return False
|
||||
shutil.copy2(src, dst)
|
||||
return True
|
||||
|
||||
|
||||
def _write_text_incremental(dst: Path, text: str) -> bool:
|
||||
"""Write a text file only when the destination is missing."""
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
if dst.exists():
|
||||
return False
|
||||
dst.write_text(text, encoding="utf-8")
|
||||
return True
|
||||
|
||||
|
||||
def _copy_tree_incremental(src_dir: Path, dst_dir: Path) -> tuple[int, int]:
|
||||
"""Copy an entire tree without overwriting existing files."""
|
||||
created = 0
|
||||
skipped = 0
|
||||
for src in src_dir.rglob("*"):
|
||||
rel = src.relative_to(src_dir)
|
||||
dst = dst_dir / rel
|
||||
if src.is_dir():
|
||||
dst.mkdir(parents=True, exist_ok=True)
|
||||
continue
|
||||
if _copy_file_incremental(src, dst):
|
||||
created += 1
|
||||
else:
|
||||
skipped += 1
|
||||
return created, skipped
|
||||
|
||||
|
||||
def _merge_env_incremental(env_path: Path, values: dict[str, str]) -> str:
|
||||
"""Create .env if missing, otherwise append only missing keys."""
|
||||
lines = [
|
||||
"# PaperForge configuration",
|
||||
f"PADDLEOCR_API_TOKEN={values['PADDLEOCR_API_TOKEN']}",
|
||||
f"PADDLEOCR_JOB_URL={values['PADDLEOCR_JOB_URL']}",
|
||||
f"PADDLEOCR_MODEL={values['PADDLEOCR_MODEL']}",
|
||||
]
|
||||
if "ZOTERO_DATA_DIR" in values:
|
||||
lines.append(f"ZOTERO_DATA_DIR={values['ZOTERO_DATA_DIR']}")
|
||||
|
||||
if not env_path.exists():
|
||||
env_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
env_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
return "created"
|
||||
|
||||
existing_text = env_path.read_text(encoding="utf-8")
|
||||
existing_keys = {
|
||||
line.split("=", 1)[0].strip()
|
||||
for line in existing_text.splitlines()
|
||||
if line.strip() and not line.lstrip().startswith("#") and "=" in line
|
||||
}
|
||||
missing_lines = [
|
||||
f"{key}={value}"
|
||||
for key, value in values.items()
|
||||
if key not in existing_keys
|
||||
]
|
||||
if not missing_lines:
|
||||
return "preserved"
|
||||
|
||||
suffix = "\n".join(missing_lines) + "\n"
|
||||
if existing_text and not existing_text.endswith("\n"):
|
||||
existing_text += "\n"
|
||||
if existing_text.strip():
|
||||
existing_text += "\n"
|
||||
env_path.write_text(existing_text + suffix, encoding="utf-8")
|
||||
return "extended"
|
||||
|
||||
|
||||
def _deploy_skill_directory(
|
||||
vault: Path,
|
||||
skill_dir: str,
|
||||
|
|
@ -1628,26 +1723,25 @@ def _deploy_skill_directory(
|
|||
if deep_src.exists():
|
||||
text = deep_src.read_text(encoding="utf-8")
|
||||
text = _substitute_vars(text, system_dir, resources_dir, literature_dir, control_dir, base_dir, skill_dir, prefix)
|
||||
(pf_deep_dst / "SKILL.md").write_text(text, encoding="utf-8")
|
||||
_write_text_incremental(pf_deep_dst / "SKILL.md", text)
|
||||
imported.append("pf-deep")
|
||||
|
||||
# ld_deep.py
|
||||
ld_src = src_scripts / "ld_deep.py"
|
||||
ld_dst = pf_deep_dst / "scripts" / "ld_deep.py"
|
||||
if ld_src.exists():
|
||||
ld_dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(ld_src, ld_dst)
|
||||
_copy_file_incremental(ld_src, ld_dst)
|
||||
|
||||
# subagent prompt
|
||||
if src_prompt.exists():
|
||||
shutil.copy2(src_prompt, pf_deep_dst / "prompt_deep_subagent.md")
|
||||
_copy_file_incremental(src_prompt, pf_deep_dst / "prompt_deep_subagent.md")
|
||||
|
||||
# chart-reading guides
|
||||
if src_charts.exists() and src_charts.is_dir():
|
||||
chart_dst = pf_deep_dst / "chart-reading"
|
||||
chart_dst.mkdir(parents=True, exist_ok=True)
|
||||
for f in src_charts.glob("*.md"):
|
||||
shutil.copy2(f, chart_dst / f.name)
|
||||
_copy_file_incremental(f, chart_dst / f.name)
|
||||
|
||||
# --- pf-paper: lightweight paper Q&A skill ---
|
||||
pf_paper_dst = vault / skill_dir / "pf-paper"
|
||||
|
|
@ -1657,7 +1751,7 @@ def _deploy_skill_directory(
|
|||
if paper_src.exists():
|
||||
text = paper_src.read_text(encoding="utf-8")
|
||||
text = _substitute_vars(text, system_dir, resources_dir, literature_dir, control_dir, base_dir, skill_dir, prefix)
|
||||
(pf_paper_dst / "SKILL.md").write_text(text, encoding="utf-8")
|
||||
_write_text_incremental(pf_paper_dst / "SKILL.md", text)
|
||||
imported.append("pf-paper")
|
||||
|
||||
return imported
|
||||
|
|
@ -1687,7 +1781,7 @@ def _deploy_flat_command(
|
|||
for f in command_src.glob("pf-*.md"):
|
||||
text = f.read_text(encoding="utf-8")
|
||||
text = _substitute_vars(text, system_dir, resources_dir, literature_dir, control_dir, base_dir, skill_dir)
|
||||
(command_dst / f.name).write_text(text, encoding="utf-8")
|
||||
_write_text_incremental(command_dst / f.name, text)
|
||||
imported.append(f.stem)
|
||||
|
||||
return imported
|
||||
|
|
@ -1715,14 +1809,13 @@ def _deploy_rules_file(
|
|||
pf_deep_dst.mkdir(parents=True, exist_ok=True)
|
||||
ld_src = src_scripts / "ld_deep.py"
|
||||
if ld_src.exists():
|
||||
(pf_deep_dst / "scripts").mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(ld_src, pf_deep_dst / "scripts" / "ld_deep.py")
|
||||
_copy_file_incremental(ld_src, pf_deep_dst / "scripts" / "ld_deep.py")
|
||||
if src_prompt.exists():
|
||||
shutil.copy2(src_prompt, pf_deep_dst / "prompt_deep_subagent.md")
|
||||
_copy_file_incremental(src_prompt, pf_deep_dst / "prompt_deep_subagent.md")
|
||||
if src_charts.exists() and src_charts.is_dir():
|
||||
(pf_deep_dst / "chart-reading").mkdir(parents=True, exist_ok=True)
|
||||
for f in src_charts.glob("*.md"):
|
||||
shutil.copy2(f, pf_deep_dst / "chart-reading" / f.name)
|
||||
_copy_file_incremental(f, pf_deep_dst / "chart-reading" / f.name)
|
||||
|
||||
imported.append("clinerules")
|
||||
return imported
|
||||
|
|
@ -1898,9 +1991,10 @@ def headless_setup(
|
|||
zotero_data_hint = zotero_data or "<你的Zotero数据目录>"
|
||||
if sys.platform == "win32":
|
||||
print(f" 首次安装需要配置 BBT 自动导出:")
|
||||
print(f" 1. Zotero → 文件 → 导出库 → Better BibTeX")
|
||||
print(f" 2. 保存到 vault 的 {system_dir}/PaperForge/exports/")
|
||||
print(f' 3. 勾选 "保持更新"')
|
||||
print(f" 1. 在 Zotero 中右键要同步的文献库或分类 → 导出")
|
||||
print(f" 2. 格式选择 Better BibTeX JSON")
|
||||
print(f" 3. 保存到 vault 的 {system_dir}/PaperForge/exports/")
|
||||
print(f' 4. 勾选 "保持更新"')
|
||||
else:
|
||||
print(f" Configure BBT auto-export to: {system_dir}/PaperForge/exports/")
|
||||
print(f" 完成后运行: paperforge sync")
|
||||
|
|
@ -1931,13 +2025,13 @@ def headless_setup(
|
|||
print(f"Error: worker script not found: {worker_src}", file=sys.stderr)
|
||||
return 4
|
||||
|
||||
shutil.copy2(worker_src, worker_dst)
|
||||
_copy_file_incremental(worker_src, worker_dst)
|
||||
for mod in ["ocr.py", "repair.py", "status.py", "deep_reading.py",
|
||||
"update.py", "base_views.py", "__init__.py",
|
||||
"_utils.py", "_progress.py", "_retry.py"]:
|
||||
mod_src = repo_root / "paperforge/worker" / mod
|
||||
if mod_src.exists():
|
||||
shutil.copy2(mod_src, pf_path / "worker/scripts" / mod)
|
||||
_copy_file_incremental(mod_src, pf_path / "worker/scripts" / mod)
|
||||
print(f" [OK] worker scripts")
|
||||
|
||||
# Deploy skills based on agent format
|
||||
|
|
@ -1989,16 +2083,23 @@ def headless_setup(
|
|||
docs_src = repo_root / "docs"
|
||||
docs_dst = vault / "docs"
|
||||
if docs_src.exists() and docs_src.is_dir():
|
||||
shutil.copytree(docs_src, docs_dst, dirs_exist_ok=True)
|
||||
print(f" [OK] docs")
|
||||
created, skipped = _copy_tree_incremental(docs_src, docs_dst)
|
||||
print(f" [OK] docs (created {created}, preserved {skipped})")
|
||||
else:
|
||||
print(f" [WARN] docs source not found: {docs_src}")
|
||||
|
||||
# Obsidian plugin
|
||||
plugin_src = repo_root / "paperforge/plugin"
|
||||
plugin_dst = vault / ".obsidian" / "plugins" / "paperforge"
|
||||
if plugin_src.exists() and plugin_src.is_dir():
|
||||
created = 0
|
||||
skipped = 0
|
||||
for f in plugin_src.glob("*"):
|
||||
shutil.copy2(f, plugin_dst / f.name)
|
||||
print(f" [OK] Obsidian plugin")
|
||||
if _copy_file_incremental(f, plugin_dst / f.name):
|
||||
created += 1
|
||||
else:
|
||||
skipped += 1
|
||||
print(f" [OK] Obsidian plugin (created {created}, preserved {skipped})")
|
||||
else:
|
||||
print(f" [WARN] Plugin source not found: {plugin_src}")
|
||||
|
||||
|
|
@ -2016,7 +2117,7 @@ def headless_setup(
|
|||
("<skill_dir>", skill_dir),
|
||||
]:
|
||||
text = text.replace(old, new)
|
||||
agents_dst.write_text(text, encoding="utf-8")
|
||||
_write_text_incremental(agents_dst, text)
|
||||
print(f" [OK] AGENTS.md")
|
||||
else:
|
||||
print(f" [WARN] AGENTS.md source not found; skipping")
|
||||
|
|
@ -2028,13 +2129,16 @@ def headless_setup(
|
|||
|
||||
# .env
|
||||
if paddleocr_key:
|
||||
env_content = f"""# PaperForge 配置文件
|
||||
PADDLEOCR_API_TOKEN={paddleocr_key}
|
||||
PADDLEOCR_JOB_URL={paddleocr_url}
|
||||
PADDLEOCR_MODEL=PaddleOCR-VL-1.5
|
||||
"""
|
||||
(pf_path / ".env").write_text(env_content, encoding="utf-8")
|
||||
print(f" [OK] .env")
|
||||
env_status = _merge_env_incremental(
|
||||
pf_path / ".env",
|
||||
{
|
||||
"PADDLEOCR_API_TOKEN": paddleocr_key,
|
||||
"PADDLEOCR_JOB_URL": paddleocr_url,
|
||||
"PADDLEOCR_MODEL": "PaddleOCR-VL-1.5",
|
||||
"ZOTERO_DATA_DIR": zotero_data or "",
|
||||
},
|
||||
)
|
||||
print(f" [OK] .env ({env_status})")
|
||||
else:
|
||||
print(f" [INFO] No PaddleOCR key — create {pf_path}/.env manually")
|
||||
|
||||
|
|
@ -2130,16 +2234,19 @@ PADDLEOCR_MODEL=PaddleOCR-VL-1.5
|
|||
print(f" PaperForge v{__version__} installation complete!")
|
||||
print("=" * 60)
|
||||
print()
|
||||
print("Safety note:")
|
||||
print(" Existing files in the target vault were preserved; setup only created missing files and folders.")
|
||||
print()
|
||||
print("Next steps:")
|
||||
print(" 1. Open Obsidian → Settings → Community Plugins → Enable 'PaperForge'")
|
||||
print(" 2. Press Ctrl+P and type 'PaperForge' to see CLI commands")
|
||||
print(f" 1. In Zotero, export the library or a collection as Better BibTeX JSON into: {vault / system_dir / 'PaperForge' / 'exports'}")
|
||||
print(" Enable 'Keep updated' so Zotero keeps the JSON in sync.")
|
||||
print(" 2. Open Obsidian → Settings → Community Plugins → Enable 'PaperForge'")
|
||||
print(" 3. Press Ctrl+P and type 'PaperForge' to open the dashboard")
|
||||
print()
|
||||
print("First-run workflow:")
|
||||
print(f" 3. Configure Better BibTeX auto-export to: {system_dir}/PaperForge/exports/")
|
||||
print(" (Zotero → Export Library → Better BibTeX → Keep Updated)")
|
||||
print(" 4. Add papers to Zotero, then run: paperforge sync")
|
||||
print(" 5. Mark papers with do_ocr:true in library-records")
|
||||
print(" 6. Run: paperforge ocr")
|
||||
print(" 5. In Obsidian Base view, mark do_ocr:true, then run: paperforge ocr")
|
||||
print(" 6. After OCR done, mark analyze:true in Base view")
|
||||
print(" 7. Use /pf-deep <key> in your AI agent for deep reading")
|
||||
print()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue