feat: dashboard ui refine — arrow icons, maillard colors, responsive grid, setup bugfix

Dashboard UI:

- Replace text expand/collapse with SVG arrow + clickable gradient container

- Map agent_platform key to display name (OpenCode, not opencode)

- Maillard/Morandi palette for OCR progress bars (pending brown, done deep green)

- Shorten 'Needs Attention' to 'Attention' to prevent label wrap

- Responsive workflow overview: CSS Grid + container queries (narrow: 2x2, wide: 4x1 with max-width 160px stop)

Setup Wizard:

- Fix Phase 7 verification crash (undefined agents_src/agents_dst variables)

Skill restructure:

- Move chart-reading references under references/ directory

- Remove old pf-*.md script files (deployed via skill_deploy service)

- Add new reference files (deep-reading, paper-qa, paper-resolution, save-session)
This commit is contained in:
Research Assistant 2026-05-10 20:16:33 +08:00
parent f9fbbfb2f0
commit 5c6ccaad34
35 changed files with 1041 additions and 1703 deletions

View file

@ -1460,11 +1460,20 @@ class PaperForgeStatusView extends ItemView {
const truncated = extracted.length > 200 ? extracted.slice(0, 200) + '...' : extracted;
excerptEl.setText(truncated);
if (extracted.length > 200) {
const expandBtn = body.createEl('button', { cls: 'paperforge-paper-overview-expand', text: '展开' });
const expandContainer = body.createEl('div', { cls: 'paperforge-expand-container' });
const expandBtn = expandContainer.createEl('button', { cls: 'paperforge-expand-icon', title: '展开/收起' });
// Insert arrow SVG
expandBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>';
let expanded = false;
expandBtn.addEventListener('click', () => {
// Make the whole container clickable
expandContainer.addEventListener('click', () => {
excerptEl.setText(expanded ? truncated : extracted);
expandBtn.setText(expanded ? '展开' : '收起');
expandBtn.innerHTML = expanded
? '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>'
: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"></polyline></svg>';
expanded = !expanded;
});
}
@ -1556,14 +1565,23 @@ class PaperForgeStatusView extends ItemView {
: (qa.answer || '');
aEl.createEl('span', { cls: 'paperforge-discussion-a-text', text: '解答:' + shortAnswer });
if (qa.answer && qa.answer.length > 150) {
const expandLink = aEl.createEl('button', { cls: 'paperforge-discussion-expand', text: '展开' });
const expandContainer = aEl.createEl('div', { cls: 'paperforge-expand-container' });
const expandBtn = expandContainer.createEl('button', { cls: 'paperforge-expand-icon', title: '展开/收起' });
// Insert arrow SVG
expandBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>';
let expanded = false;
expandLink.addEventListener('click', () => {
// Make the whole container clickable
expandContainer.addEventListener('click', () => {
const textSpan = aEl.querySelector('.paperforge-discussion-a-text');
if (textSpan) {
textSpan.setText(expanded ? ('解答:' + shortAnswer) : ('解答:' + qa.answer));
}
expandLink.setText(expanded ? '展开' : '收起');
expandBtn.innerHTML = expanded
? '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>'
: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"></polyline></svg>';
expanded = !expanded;
});
}
@ -1692,9 +1710,19 @@ class PaperForgeStatusView extends ItemView {
});
// Show "Run in [agent_platform]" label below the button (DASH-02)
const platform = this.app.plugins.plugins['paperforge']?.settings?.agent_platform || 'opencode';
const platformKey = this.app.plugins.plugins['paperforge']?.settings?.agent_platform || 'opencode';
const AGENTS = {
'opencode': 'OpenCode',
'claude': 'Claude Code',
'cursor': 'Cursor',
'github_copilot': 'GitHub Copilot',
'windsurf': 'Windsurf',
'codex': 'Codex',
'cline': 'Cline'
};
const platformName = AGENTS[platformKey] || platformKey;
const labelEl = card.createEl('div', { cls: 'paperforge-agent-platform-label' });
labelEl.setText(t('run_in_agent').replace('{0}', platform));
labelEl.setText(t('run_in_agent').replace('{0}', platformName));
} else if (nextStep === 'ready') {
const trigger = card.createEl('button', { cls: 'paperforge-next-step-trigger' });
trigger.createEl('span', { text: '✓ ' + info.label });
@ -1806,7 +1834,7 @@ class PaperForgeStatusView extends ItemView {
{ cls: 'pending', value: ocrPending, label: 'Pending' },
{ cls: 'active', value: ocrProcessing, label: 'Processing' },
{ cls: 'done', value: ocrDone, label: 'Done' },
{ cls: 'failed', value: ocrFailed, label: 'Needs Attention' },
{ cls: 'failed', value: ocrFailed, label: 'Attention' },
];
for (const l of ocrLabels) {
const cnt = ocrCounts.createEl('div', { cls: 'paperforge-ocr-count' });

File diff suppressed because it is too large Load diff

View file

@ -1012,7 +1012,7 @@ def headless_setup(
"OCR dir": (pf_path / "ocr").exists(),
"paperforge.json": pf_json.exists(),
"Obsidian plugin": (vault / ".obsidian" / "plugins" / "paperforge" / "main.js").exists(),
"AGENTS.md": True if not agents_src.exists() else agents_dst.exists(),
"AGENTS.md": (vault / "AGENTS.md").exists(),
}
failed = [k for k, v in checks.items() if not v]
if failed:

View file

@ -0,0 +1,80 @@
---
name: literature-qa
description: >
学术文献精读与问答。MUST trigger when user types /pf-deep, /pf-paper, /pf-end,
or says "精读", "深度阅读", "读一下", "查一下这篇论文", "帮我看看这篇文章",
"这篇文章讲了什么", "保存讨论", "结束讨论", "做这篇文献的问答",
or any phrase about reading/analyzing papers in their Zotero library.
支持 Zotero key, DOI, 标题, 作者/年份, 自然语言描述定位论文.
license: Apache-2.0
compatibility: opencode
---
# Literature QA — 学术文献精读与问答
## 路由表
Agent 读到本文件后,首先根据用户意图路由到对应的 reference 文件:
| 用户意图 | 典型输入 | 加载文件 |
|---------|---------|---------|
| 三阶段精读(指定论文) | `/pf-deep <query>`, `pf-deep <query>`, "精读 XXX", "深度阅读 XXX" | [references/deep-reading.md](references/deep-reading.md) |
| 三阶段精读(查看队列) | `/pf-deep`(无参数), "精读队列", "有哪些该读了" | [references/deep-reading.md](references/deep-reading.md) |
| 论文问答 | `/pf-paper <query>`, `pf-paper <query>`, "做这篇的问答", "帮我看看 XXX" | [references/paper-qa.md](references/paper-qa.md) |
| 保存讨论记录 | `/pf-end`, `pf-end`, "保存", "结束讨论", "完成讨论" | [references/save-session.md](references/save-session.md) |
> **重要:** 加载 reference 文件后,严格按照该文件的流程执行。不要跳过任何步骤。
## 论文定位(所有路由共用,先执行)
详见 [references/paper-resolution.md](references/paper-resolution.md)。
**核心原则二路定位。Python 干确定的活Agent 干理解的活。路径全在环境变量里,零硬编码。**
### 第零步:加载环境变量(每条路由启动时跑一次)
```pwsh
python -m paperforge.worker.paper_resolver env --vault . --shell pwsh | Invoke-Expression
```
此后所有路径用环境变量引用,不再拼接:
| 变量 | 含义 |
| ------------------ | --------------------------- |
| `$env:PF_VAULT` | vault 根目录 |
| `$env:PF_INDEX_PATH` | formal-library.json 路径 |
| `$env:PF_LITERATURE_DIR` | formal notes 目录 |
| `$env:PF_OCR_DIR` | OCR 结果目录 |
### 第一步:判断输入类型,选择路径
| 输入特征 | 执行命令 |
|---------|---------|
| 8位 key | `python -m paperforge.worker.paper_resolver resolve-key <KEY> --vault "$env:PF_VAULT"` |
| DOI | `python -m paperforge.worker.paper_resolver resolve-doi "<DOI>" --vault "$env:PF_VAULT"` |
| 标题片段 | `python -m paperforge.worker.paper_resolver search --title "..." --vault "$env:PF_VAULT"` |
| 作者+年份 | `python -m paperforge.worker.paper_resolver search --author "Smith" --year 2024 --vault "$env:PF_VAULT"` |
| 自然语言 | Agent 读 `$env:PF_INDEX_PATH` 指向的 formal-library.json |
### 第二步:处理结果
- **Python 返回匹配:** 直接使用返回的 workspace`formal_note_path` 等由 config 动态计算)
- **Python 搜不到:** Agent grep fallback`rg -l "zotero_key:.*ABC" "$env:PF_LITERATURE_DIR/"`
- **自然语言搜不到:** 告知用户 "未找到,请确认或先运行 `paperforge sync`"
- **命中多篇:** 列出候选清单让用户选
## 文件结构
```
literature-qa/
├── SKILL.md ← 本文件(路由入口)
├── references/
│ ├── deep-reading.md ← 精读工作流
│ ├── paper-qa.md ← 问答工作流
│ ├── save-session.md ← 保存记录工作流
│ ├── paper-resolution.md ← 论文定位详细协议
│ ├── deep-subagent.md ← 子代理提示词模板
│ └── chart-reading/ ← 19 个图表类型阅读指南
└── scripts/
└── ld_deep.py ← 精读引擎Python
```

View file

@ -0,0 +1,171 @@
# 三阶段精读
Keshav 三阶段组会式精读。触发后执行以下工作流。
> **路径说明:** 本文件中的 `scripts/ld_deep.py` 相对于本 skill 目录。Agent 运行时 skill 目录由平台注入(通常为 `<installation_path>/paperforge/skills/literature-qa/`。如不确定AI 应从 SKILL.md 所在目录推断。
---
## 前置条件检查
执行前确认:
- [ ] 已完成论文定位(参考 [paper-resolution.md](paper-resolution.md)),拿到 zotero_key 和 workspace
- [ ] `analyze: true`(在 formal note frontmatter 中resolver 返回的 workspace 里可查到)
- [ ] `ocr_status: done`(在 resolver 返回的 workspace 里可查到)
如果前置条件不满足,告知用户并停止。
---
## 执行流程
### Step 1: Prepare机械操作跑脚本
```bash
python scripts/ld_deep.py prepare --vault . --key <ZOTERO_KEY>
```
返回 JSON 解析:
- `status: "ok"` → 继续
- `status: "error"` → 报告 `message` 给用户,停止
Prepare 做的事情Agent 不需要关心细节):
- 检查 analyze 和 ocr_status
- 生成 figure-map.json 和 chart-type-map.json
- 在 formal note 中插入 `## 🔍 精读` 骨架
读 formal note 确认骨架已插入。
---
### Step 2: Pass 1 — 概览
只填 `### Pass 1: 概览` 区域。不碰 Pass 2/3。
**填写内容:**
- **一句话总览**:论文类型 + 核心发现,一句话。
- **5 Cs 快速评估**
- **Category**类型RCT / 队列研究 / 病例对照 / 综述 / 基础研究 / ...
- **Context**(上下文):该领域当前共识,本文要解决什么问题
- **Correctness**(合理性初判):初步直觉,逻辑是否有明显漏洞
- **Contributions**贡献1-3 条
- **Clarity**(清晰度):写作质量,图表可读性
- **Figure 导读**(基于 fulltext.md 浏览各图 caption
- 关键主图:列出并一句话概括每个主图要证明什么
- 证据转折点:哪个 figure 是叙事的关键转折
- 需要重点展开的 supplementary如果有
- 关键表格:列出
填完立即保存 formal note。
---
### Step 3: Pass 2 — 精读还原
`### Pass 2: 精读还原` 区域。**按 figure 顺序逐个处理。**
#### 图表类型定位(两步)
**Step A: Python 给建议(快速初筛)**
```bash
python scripts/ld_deep.py chart-type-scan --vault . --key <ZOTERO_KEY>
```
输出每个 figure 的关键词命中结果。这只是建议Agent 不要盲信。
**Step B: Agent 读 caption 做最终判断**
对每个 figure
1. 读该 figure 的 caption来自 fulltext.md 或 figure-map.json
2. 根据 caption 内容,对照 [chart-reading/INDEX.md](chart-reading/INDEX.md) 判断图表类型
3. 如果 Python 建议和 Agent 判断不一致 → 以 Agent 判断为准
4. 如果无法确定类型 → 跳过 chart guide按通用 figure 结构分析
5. 确定类型后,读对应的 chart-reading 指南(如 `chart-reading/条形图与误差棒.md`),按指南中的检查清单分析
#### 每张 Figure 的子标题(固定,不可少)
按以下格式填入 formal note 中该 figure 的 callout block
```
**图像定位与核心问题**:页码 + 要回答什么问题
**方法与结果**:实验设计/数据来源/技术手段。核心数据、趋势、对比。
**图表质量审查**:按 chart-reading 指南检查坐标轴、单位、误差棒、统计标注等。
**作者解释**:作者在正文中对该图的解读
**我的理解**:自己的理解(区分于作者解释)
**疑点/局限**:读图时发现的疑问,用 `> [!warning]` 突出
```
#### 每张 Table 的子标题
```
回答什么问题、关键字段/分组、主要结果、我的理解、疑点/局限
```
#### 每张 figure 填完立即保存,再处理下一张。
#### 所有 figure/table 处理完后,填:
**关键方法补课**简要解释不熟悉的实验技术1-2 项即可)
**主要发现与新意**
- 发现 1...来源Figure X
- 发现 2...来源Figure Y / Table Z
保存。
---
### Step 4: Postprocess跑校验脚本修正问题
```bash
python scripts/ld_deep.py postprocess-pass2 <formal_note_path> --figures <N> --format text
```
- 输出 `OK` → 继续
- 输出错误 → 按错误提示修正(包含行号),修正后重新跑
- 最多 3 轮修正。3 轮后仍失败 → 报告剩余错误给用户
---
### Step 5: Pass 3 — 深度理解
`### Pass 3: 深度理解` 区域。基于 Pass 1/2 已写的内容。
**填写内容:**
- **假设挑战与隐藏缺陷**:隐含假设;如果放宽某个假设结论还成立吗;缺少哪些关键引用;实验/分析技术潜在问题
- **哪些结论扎实,哪些仍存疑**
- **较扎实**...
- **仍存疑**...(用 `> [!warning]`
- **Discussion 与 Conclusion 怎么读**:作者真正完成了什么;哪些地方有拔高;哪些是推测
- **对我的启发**研究设计上figure 组织上;方法组合上;未来工作想法
- **遗留问题**...(用 `> [!question]`
保存。
---
### Step 6: Final Validation
```bash
python scripts/ld_deep.py validate-note <formal_note_path> --fulltext <fulltext_path>
```
- 输出 `OK` → 告知用户精读完成
- 输出错误 → 修正缺失项,不报告成功直到通过
---
## Callout 格式规则
- `> [!important]`:每个 main finding
- `> [!warning]`:疑问、局限、证据边界、仍存疑条目
- `> [!question]`:遗留问题
- **间距:** 相邻 callout block 之间必须有空行,否则 Obsidian 会合并
- 正确:`> [!important] A\n\n> [!important] B`
- 错误:`> [!important] A\n> [!important] B`
## Supplementary 规则
- 默认不逐张展开 supplementary figure/table
- 仅在以下情况纳入:对主结论形成关键支撑、补足方法可信度、限制主文结论解释范围、作者在正文中明显依赖该补充材料

View file

@ -0,0 +1,61 @@
# 论文问答
交互式论文 Q&A 工作台。不强制要求 OCR但 OCR 完成后回答更准确。
---
## 前置条件
- [ ] 已完成论文定位(参考 [paper-resolution.md](paper-resolution.md)),拿到 zotero_key 和 workspace
- [ ] OCR 完成(推荐但非强制)
---
## 执行流程
### Step 1: 加载论文
1. 确认 workspace 路径
2. 读 `fulltext.md`(如果存在)作为主要回答依据
3. 读 formal note frontmatter 获取元数据(标题、作者、期刊、年份)
4. 如果 fulltext.md 不存在,告知用户 "OCR 文本不可用,回答将基于元数据和公开信息"
### Step 2: 显示论文信息
```
已加载论文: [title] ([year], [journal])
作者: [authors]
Zotero Key: [key]
领域: [domain]
OCR 状态: [done / 不可用]
结束对话时说 "保存" 即可保存讨论。
请问有什么问题?
```
### Step 3: 进入 Q&A 模式
- 等待用户提问
- 每次回答后等待下一个问题
- 持续到用户说 "保存"、"结束"、"完成" 等关键词
---
## 回答原则
- **严格基于** fulltext.md 中的文本内容回答
- 引用原文时标注来源页码/章节(如 "第 3 页Methods 部分"
- 用中文(简体中文)回答
- 论文中未提及的内容,明确说明 "论文中未提及该内容"
- 需要结合论文以外知识的问题,说明 "该问题需要结合论文以外的知识"
---
## 切换模式
用户在当前对话中可以说 "精读这篇文章" 切换到 deep-reading 模式。此时加载 [deep-reading.md](deep-reading.md) 执行精读流程。
---
## 保存记录
用户说 "保存"、"结束"、"完成"、"保存讨论" 时,加载 [save-session.md](save-session.md) 执行保存。不要自动保存。

View file

@ -0,0 +1,173 @@
# 论文定位协议
本文件定义如何将用户输入key / DOI / 标题 / 作者年份 / 自然语言)解析为论文 workspace。所有子流程deep-reading, paper-qa, save-session共用此协议。
## 核心原则
1. **确定性输入走 Python。** key、DOI、标题片段、作者+年份 —— 这些是机器能精确处理的。
2. **自然语言走 Agent。** "关于骨再生的那篇"、"去年那篇Nature" —— 需要 AI 理解语义。
3. **Python 搜不到时 Agent 兜底。** 不是报错,是换个方式再试一次。
4. **所有路径从环境变量获取。** 每条路由启动时先跑 `python -m paperforge.worker.paper_resolver env --vault . --shell pwsh | Invoke-Expression`。此后 `$env:PF_VAULT`、`$env:PF_LITERATURE_DIR`、`$env:PF_OCR_DIR`、`$env:PF_INDEX_PATH` 全程可用。不写死任何目录名。
---
## 前置:获取 vault 路径配置(每个路由启动时跑一次)
```bash
python -m paperforge.worker.paper_resolver paths --vault .
```
返回示例(所有路径均由 `paperforge.json` 动态计算):
```json
{
"ok": true,
"data": {
"vault_root": "/path/to/vault",
"index_path": "<system_dir>/PaperForge/indexes/formal-library.json",
"literature_dir": "<resources_dir>/<literature_dir>",
"ocr_dir": "<system_dir>/PaperForge/ocr"
}
}
```
**后续所有路径操作使用此输出中的值,不要自己拼接。**
---
## 输入类型判断
### 类型 1: Zotero Key8位字符
**识别规则:** 8位字母数字组合`XGT9Z257`、`ABC12345`
**执行命令:**
```bash
python -m paperforge.worker.paper_resolver resolve-key <KEY> --vault .
```
**返回示例(路径由 paperforge.json 配置决定,不固定):**
```json
{
"ok": true,
"data": {
"match": {
"key": "ABC12345",
"title": "TGF-beta in Bone Regeneration",
"domain": "骨科",
"formal_note_path": "...",
"ocr_path": "...",
"fulltext_path": "...",
"ocr_status": "done"
}
}
}
```
**返回 `"match": null` 时:** Agent fallback — 用 `paths` 命令获取的 `literature_dir` 路径 grep frontmatter
```bash
rg -l "zotero_key:.*ABC12345" <literature_dir>/
```
---
### 类型 2: DOI
**识别规则:** 以 `10.` 开头的标准 DOI 格式,可能带 URL 前缀
**执行命令:**
```bash
python -m paperforge.worker.paper_resolver resolve-doi "10.1016/j.jse.2018.01.001" --vault .
```
**返回格式同类型1。** 返回的路径直接使用,不自己拼接。
---
### 类型 3: 标题片段
**识别规则:** 看起来像论文标题的文本(含学术关键词,非自然语言问句)
**执行命令:**
```bash
python -m paperforge.worker.paper_resolver search --title "Predictive findings on MRI" --vault .
```
**返回示例:**
```json
{
"ok": true,
"data": {
"matches": [{ "key": "...", "title": "...", "formal_note_path": "...", ... }],
"count": 3
}
}
```
---
### 类型 4: 作者 + 年份
**识别规则:** 包含作者名(英文姓)和年份
**执行命令:**
```bash
python -m paperforge.worker.paper_resolver search --author "Smith" --year 2024 --vault .
```
---
### 类型 5: 自然语言
**识别规则:** 中文自然语言描述,如 "关于骨再生的那篇"、"去年Nature上那篇讲TGF的"
**Agent 操作:**
1. 用 `paths` 命令获取的 `index_path``formal-library.json`
2. 理解用户意图中的关键信息:主题词、年份、期刊、领域
3. 在 JSON 的 `title`、`domain`、`journal`、`abstract` 字段中搜索匹配
4. 如果 formal-library.json 无结果,用 `paths` 命令获取的 `literature_dir` grep formal notes
```bash
rg -i -l "骨再生|bone regeneration" <literature_dir>/ --include "*.md" -g "!*.canvas"
```
5. 读匹配的 frontmatter 确认是目标论文
---
## 多篇匹配处理
当搜索返回多个匹配时Agent 必须列出候选清单,让用户选择:
```
找到 3 篇匹配的论文:
[1] ABC12345 — TGF-beta in Bone Regeneration (2024, 骨科, OCR: done)
[2] DEF67890 — Bone Healing After Fracture (2023, 骨科, OCR: pending)
[3] GHI11111 — Scaffold Design for Bone Repair (2024, 骨科, OCR: done)
请输入编号选择,或 refine 搜索词。
```
**格式要求:**
- 编号 + key + title + (year, domain, ocr_status)
- 不要只列标题或只列 key
---
## Fallback 顺序
```
输入
├── 看起来像 key/DOI/标题/作者年份?
│ └── YES → Python paper_resolver
│ ├── 有结果 → 使用
│ └── 无结果 → Agent grep fallback
│ ├── 有结果 → 使用
│ └── 无结果 → 告知用户
└── 看起来像自然语言?
└── Agent 读 formal-library.json
├── 有结果 → 列出/使用
└── 无结果 → Agent grep fallback
├── 有结果 → 使用
└── 无结果 → 告知用户
```

View file

@ -0,0 +1,55 @@
# 保存讨论记录
将 paper-qa 会话中的 Q&A 记录持久化到论文工作区。
---
## 触发条件
- 用户显式说 "保存"、"保存记录"、"结束"、"完成讨论"、"save"
- 或显式输入 `/pf-end`
- 不要自动触发
---
## 执行
### Step 1: 收集 Q&A 对
汇总本次 paper-qa 会话中所有 Q&A序列化为 JSON 数组:
```json
[
{
"question": "用户的问题",
"answer": "Agent 的回答",
"source": "user_question",
"timestamp": "2026-05-10T12:00:00+08:00"
}
]
```
`source``"user_question"`(用户提问)或 `"agent_analysis"`Agent 主动分析)。
### Step 2: 调用 discussion 模块
```bash
python -m paperforge.worker.discussion record <ZOTERO_KEY> \
--vault . \
--agent pf-paper \
--model "<CURRENT_MODEL>" \
--qa-pairs '<JSON_ARRAY>'
```
### Step 3: 确认结果
CLI 返回 `{"status": "ok", ...}` → 告知用户记录已保存。
返回 `{"status": "error"}` → 记录错误,重试一次。仍失败则告知用户。
---
## 注意事项
- 仅 paper-qa 会话需要记录。deep-reading 的内容直接写入 formal note不需要通过本文件。
- 如果无法从 formal-library.json 找到论文 domain/title记录失败不应影响用户使用。

View file

@ -1,237 +0,0 @@
---
name: pf-deep
description: Complete three-pass deep reading of an academic paper (Keshav method). Requires OCR fulltext. Searches by Zotero key, title, DOI, or PMID.
allowed-tools: [Read, Bash, Edit]
---
# <prefix>pf-deep
## Purpose
基于单篇论文的组会式精读入口。
1. 解析 `<prefix>pf-deep <query>` 中的查询词
2. 支持 Zotero key、标题片段、DOI、PMID、关键词
3. 优先搜索本地 Zotero 并锁定单篇论文
4. 绑定该论文对应的:
- `<system_dir>/PaperForge/ocr/<KEY>/fulltext.md`
- `<system_dir>/PaperForge/ocr/<KEY>/meta.json`
- `<resources_dir>/<literature_dir>/.../KEY - Title.md`
5. 在正式文献卡片中检查或创建 `## 精读`
6. 以"研究思路 + figure-by-figure"方式一次性完成精读写回
## CLI Equivalent
```bash
# 准备阶段(间接)
python .opencode/skills/pf-deep/scripts/ld_deep.py prepare --vault "<VAULT_PATH>" --key <ZOTERO_KEY>
# 返回 JSON{status, formal_note, fulltext_md, figures, tables}
```
> `<prefix>pf-deep`**Agent 层命令**,通过 Python 代码自动检测论文状态,无需先行 CLI 准备。
## Detection自动检测无需手动 sync
启动时Agent 执行以下 Python 检测命令,代码会自动判断是否需要精读:
```bash
python .opencode/skills/pf-deep/scripts/ld_deep.py prepare --vault "<VAULT_PATH>" --key <ZOTERO_KEY>
```
返回 JSON
- `status: "ok"` → 就绪,可以开始精读
- `status: "error"` → 被阻塞(`message` 说明原因analyze=false / OCR 未完成 / 未找到论文)
Agent 根据返回的 `status` 决定是否进入精读流程,不自行读取 frontmatter。
**队列模式**无参数时自动检测Agent 运行:
```bash
python .opencode/skills/pf-deep/scripts/ld_deep.py queue --vault "<VAULT_PATH>"
```
代码自动扫描 canonical index 中 `analyze=true``deep_reading_status=pending` 的论文,按 OCR 状态分组。
## Arguments
| 参数 | 必需 | 说明 |
|------|------|------|
| `<query>` | 是queue 模式除外) | Zotero key、标题片段、DOI、PMID 或关键词 |
| `queue` | 否 | 启动批量精读队列模式 |
### 参数说明
1. 如果输入看起来像 8 位 Zotero key则直接按 key 解析。
2. 否则先在本地 Zotero 中搜索标题/摘要。
3. 若命中唯一结果或明显最佳结果,则直接载入。
4. 若存在多个合理候选,则先列候选清单再让用户选。
5. 不要强迫用户先知道 Zotero key。
## Example
### 单篇精读(已知 key
```bash
<prefix>pf-deep XGT9Z257
<prefix>pf-deep Predictive findings on magnetic resonance imaging
<prefix>pf-deep 10.1016/j.jse.2018.01.001
```
### 无参数:自动检测队列
```bash
<prefix>pf-deep
```
当不提供具体 key/标题时agent 自动检测精读队列:
1. 运行 `python .opencode/skills/pf-deep/scripts/ld_deep.py queue --vault "<VAULT_PATH>"` 扫描队列
2. 解析输出的 JSON按 OCR 状态分组展示
3. 由用户选择篇目
无需先跑 `paperforge sync`
## Output
Agent 在正式笔记中创建或更新 `## 精读` 区域,包含:
- **Pass 1: 概览** — 一句话总览、5 Cs 快速评估、Figure 导读
- **Pass 2: 精读还原** — Figure-by-Figure 解析、Table-by-Table 解析、关键方法补课、主要发现与新意
- **Pass 3: 深度理解** — 假设挑战与隐藏缺陷、结论扎实性评估、Discussion 解读、个人启发、遗留问题
## Error Handling
### OCR 未完成
- **表现**Agent 提示 `ocr_status` 不是 `done`
- **解决**:先运行 `paperforge ocr`,确认 `meta.json``ocr_status` 变为 `done`
### 内容已存在(覆盖确认)
- **表现**:正式笔记中已存在 `## 精读` 区域且包含非占位符的实质内容
- **处理**Agent **必须**询问用户:追加 / 覆盖 / 跳过
### 未找到论文
- **表现**Zotero key 无效或搜索无结果
- **解决**:确认 key 正确,或尝试用标题片段搜索
## 精读结构参考
### 执行原则
- `<prefix>pf-deep` 对用户来说是一次触发直接完成。
- 内部逻辑分两步:
1. 先生成 `## 精读` 骨架和 figure 标题位
2. 再补全所有空段
- 后续再次运行时(用户选择追加):
- 只补空段,不覆盖已有内容
### 精读定位
这不是综述提取,也不是信息摘录。目标是模拟高水平博士/博士后组会讲解单篇论文的学习型精读。
主线必须是:
1. 文章整体研究思路
2. 主文 figure 逐张解析
3. 关键方法补课
4. 主要发现、新意、疑点与启发
### Supplementary 规则
- 默认不逐张展开 supplementary figure/table。
- 仅在以下情况下纳入:
- 对主结论形成关键支撑
- 补足方法可信度
- 限制主文结论的解释范围
- 作者在正文中明显依赖该补充材料
### 标准骨架
```md
## 精读
**证据边界**:区分三层信息:`论文结果`、`作者解释`、`我的理解/推断`。
### Pass 1: 概览
**一句话总览**
(待补充)
**5 Cs 快速评估**
- **Category**(类型):
- **Context**(上下文):
- **Correctness**(合理性初判):
- **Contributions**(贡献):
- **Clarity**(清晰度):
**Figure 导读**
- 关键主图:
- 证据转折点:
- 需要重点展开的 supplementary
- 关键表格:
### Pass 2: 精读还原
#### Figure-by-Figure 解析
(每张 figure 下方按以下顺序填写)
- **图像定位与核心问题**:页码 + 要回答什么问题
- **方法与结果**:方法 + 结果
- **作者解释**:作者对该图的解读
- **我的理解**:自己的理解(区分于作者解释)
- **在全文中的作用**:该图在整体故事线中的位置
- **疑点 / 局限**:读图时发现的疑问
#### Table-by-Table 解析
(如有重要表格,按同样结构展开)
#### 关键方法补课
- 方法 1
- 方法 2
#### 主要发现与新意
**主要发现**
- 发现 1
- 发现 2
### Pass 3: 深度理解
#### 假设挑战与隐藏缺陷
- 隐含假设:
- 如果放宽某个假设,结论还成立吗?
- 缺少哪些关键引用?
- 实验/分析技术的潜在问题:
#### 哪些结论扎实,哪些仍存疑
**较扎实**
-
**仍存疑**
-
#### Discussion 与 Conclusion 怎么读
- 作者真正完成了什么:
- 哪些地方有拔高:
- 哪些地方是推测:
#### 对我的启发
- 研究设计上:
- figure 组织上:
- 方法组合上:
- 未来工作想法:
#### 遗留问题
**遗留问题**
-
```
### Figure 节要求
每个 figure 小节按以下顺序填写:
- **图像定位与核心问题**:页码 + 要回答什么问题
- **方法与结果**:方法 + 结果
- **作者解释**:作者对该图的解读
- **我的理解**:自己的理解(区分于作者解释)
- **在全文中的作用**:该图在整体故事线中的位置
- **疑点 / 局限**:读图时发现的疑问(可酌情用 `> [!warning]` 突出)
## See Also
- [pf-paper](pf-paper.md) — 快速摘要与问答

View file

@ -1,67 +0,0 @@
---
name: pf-end
description: Save the paper Q&A discussion record. Triggered when user says "保存" "结束" "save" or types $pf-end. Summarizes all Q&A pairs and writes them to the paper's ai/discussion directory via the discussion module.
allowed-tools: [Read, Bash]
---
# <prefix>pf-end
## Purpose
结束当前论文对话并保存讨论记录。需要用户**显式要求**时才执行,不自动触发。
1. 汇总本次对话中所有 Q&A 对
2. 通过 `discussion.record_session()` 写入论文工作区的 `ai/` 目录
3. 告知用户记录已保存
## Trigger
用户说以下任一关键词时执行(全平台通用):
- `保存` / `记录` / `保存记录`
- `结束` / `完成`
- `save discussion` / `save` / `done`
也可以显式指定 key
- `{prefix}pf-end <zotero_key>`OpenCode
- `保存 <zotero_key>`(全平台)
如果未指定 key则使用当前已加载的论文 key。
## Save Format
将会话期间的所有 Q&A 序列化为 JSON 数组:
```json
{
"question": "用户的问题",
"answer": "Agent 的回答",
"source": "user_question",
"timestamp": "2026-05-06T12:00:00+08:00"
}
```
`source``"user_question"`(用户提问)或 `"agent_analysis"`Agent 主动分析)。
## Command
```bash
python -m paperforge.worker.discussion record <ZOTERO_KEY> \
--vault "<VAULT_PATH>" \
--agent pf-paper \
--model "<MODEL_NAME>" \
--qa-pairs '<JSON_ARRAY>'
```
## Verification
CLI 返回:
```json
{"status": "ok", "json_path": "Literature/{domain}/{key} - {title}/ai/discussion.json", "md_path": "..."}
```
如果 `status``"error"`,记录错误信息并重试,不要跳过。
## See Also
- [pf-paper](pf-paper.md) — 论文 Q&A 工作台
- [pf-deep](pf-deep.md) — 完整三阶段精读

View file

@ -1,102 +0,0 @@
---
name: pf-ocr
description: Process the PDF OCR queue for formal notes marked do_ocr: true. Uploads PDFs to PaddleOCR API and extracts fulltext with figures.
allowed-tools: [Bash]
---
# /pf-ocr
## Purpose
处理正式笔记 frontmatter 中 `do_ocr: true` 的 PDF OCR 队列。
`paperforge ocr` 会自动读取 `paperforge.json` 定位 ocr 目录和 worker 脚本,运行 OCR 并自动诊断结果。
## CLI Equivalent
```bash
paperforge ocr
```
## Prerequisites
- [ ] formal note frontmatter 中 `do_ocr: true` 已设置
- [ ] PDF 附件存在(`has_pdf: true`
- [ ] PaddleOCR API Key 已配置(`.env` 中 `PADDLEOCR_API_TOKEN`
- [ ] 网络连接正常(可访问 PaddleOCR 服务)
## Arguments
| 参数 | 必需 | 说明 |
|------|------|------|
| `--diagnose` | 否 | 仅诊断配置,不上传 PDF |
| `--key <KEY>` | 否 | 仅处理指定 Zotero key 的文献 |
| `--vault <PATH>` | 否 | 指定 Vault 根目录(默认当前目录) |
| `--live` | 否 | 与 `--diagnose` 联用,执行实时 PDF 测试 |
### 诊断等级
| 等级 | 检查项 | 失败含义 |
|------|--------|---------|
| L1 | API token 存在性 | `PADDLEOCR_API_TOKEN` 缺失或为空 |
| L2 | URL 可达性 | 无法连接 PaddleOCR 服务 |
| L3 | API 格式验证 | 服务可达但响应格式异常 |
| L4 | 实时 PDF 往返 | 完整提交和结果获取失败 |
> Exit code `0` = 所有检查通过。Exit code `1` = 至少一项检查失败。
## Example
```bash
# 处理所有标记 do_ocr: true 的文献
paperforge ocr
# 仅处理指定文献
paperforge ocr --key ABCDEFG
# 诊断模式(不实际运行)
paperforge ocr --diagnose
# 完整诊断(含实时测试)
paperforge ocr --diagnose --live
# 指定 Vault 目录
paperforge ocr --vault /path/to/vault
```
## Output
OCR 完成后,每个文献生成以下文件:
```
<system_dir>/PaperForge/ocr/<key>/
├── fulltext.md # 提取的全文(含 <!-- page N --> 分页标记)
├── images/ # 自动切割的图表图片
├── meta.json # OCR 元数据(含 ocr_status
└── figure-map.json # 图表索引(自动生成)
```
`meta.json` 中的 `ocr_status` 字段:
- `pending` — 等待处理
- `processing` — 正在处理
- `done` — 完成
- `failed` — 失败
## Error Handling
### API Token 缺失L1 失败)
- `PADDLEOCR_API_TOKEN` 未设置 → 在 `.env` 文件中添加
### 服务不可达L2 失败)
- 无法连接 PaddleOCR → 检查网络连接
### PDF 上传失败L4 失败)
- PDF 提交后未返回结果 → 检查 PDF 文件是否损坏
### OCR 状态卡住
- `ocr_status` 长期显示 `processing` → 重新设置 `do_ocr: true` 后再次运行
## See Also
- [pf-sync](pf-sync.md) — 文献同步(生成正式笔记)
- [pf-deep](pf-deep.md) — 深度精读(依赖 OCR 结果)

View file

@ -1,107 +0,0 @@
---
name: pf-paper
description: Quick paper Q&A workbench. Load a paper by Zotero key, title, DOI, or PMID and answer questions. Does not require OCR.
allowed-tools: [Read, Bash]
---
# <prefix>pf-paper
## Purpose
基于 Zotero OCR 文本的单篇论文工作台入口。
1. 解析 `<prefix>pf-paper <query>` 中的查询词
2. 支持 Zotero key、标题片段、DOI、PMID、关键词
3. 优先搜索本地 Zotero解析到单篇目标论文
4. 根据 Vault 根目录的 `paperforge.json` 加载 `<system_dir>/PaperForge/ocr/<KEY>/fulltext.md` 作为主文本
5. 读取 `meta.json` 显示论文标题、作者、期刊、年份
6. 进入 Q&A 模式,用中文回答用户关于该论文的问题
7. 在当前论文上下文中,用户可再说"精读这篇文章"切换到 deep 层
## CLI Equivalent
```bash
# 准备阶段(间接)
paperforge sync # 生成正式笔记
```
> `<prefix>pf-paper`**Agent 层命令**,无直接 CLI 等效命令。
## Prerequisites
- [ ] 正式笔记已生成(`paperforge sync` 生成)
- [ ] `fulltext.md` 存在(推荐,用于基于原文回答;如不存在则基于元数据回答)
- [ ] discussion.py 模块可用(`python -m paperforge.worker.discussion --help` 可执行)
> **注意**:与 `/pf-deep` 不同,`<prefix>pf-paper` **不强制要求** OCR 完成。没有 OCR 时基于论文元数据和公开信息回答。
## Arguments
| 参数 | 必需 | 说明 |
|------|------|------|
| `<query>` | 是 | Zotero key、标题片段、DOI、PMID 或关键词 |
| `<query2> ...` | 否 | 可同时加载多篇论文 |
### 解析规则
1. 如果输入看起来像 8 位 Zotero key则直接按 key 解析。
2. 否则先在本地 Zotero 中搜索标题/摘要。
3. 若命中唯一结果或明显最佳结果,则直接载入。
4. 若存在多个合理候选,则先列候选清单再让用户选。
5. 不要强迫用户先知道 Zotero key。
## Example
```bash
<prefix>pf-paper XGT9Z257
<prefix>pf-paper Predictive findings on magnetic resonance imaging
<prefix>pf-paper 10.1016/j.jse.2018.01.001
<prefix>pf-paper XGT9Z257 PQR8KLM
```
## Output
加载成功后显示:
```
已加载论文: [title] ([year], [journal])
Zotero Key: [key]
结束对话时说"保存记录"即可保存讨论。
请问有什么问题?
```
### 回答原则
- **严格基于** `fulltext.md` 中的文本内容回答
- 引用原文时标注来源页码/章节
- 用中文(简体中文)回答
- 论文中未提及的内容,明确说明"论文中未提及该内容"
- 需要结合论文以外知识的问题,说明"该问题需要结合论文以外的知识回答"
## Error Handling
### 论文未找到
- **表现**Zotero key 无效或搜索无结果
- **解决**:确认 key 正确,或尝试用标题片段搜索
### 多个候选结果
- **表现**:搜索返回多个匹配的论文
- **处理**Agent 列出候选清单,让用户选择目标论文
### OCR 文件缺失
- **表现**`fulltext.md` 不存在
- **处理**Agent 基于元数据和公开信息回答,并告知用户"OCR 文本不可用,回答基于元数据"
## Saving Discussion Record
当用户说"保存"、"结束"、"完成"等关键词时(或显式输入 `{prefix}pf-end <key>`),必须执行记录保存。具体步骤见 [pf-end](pf-end.md)。
### 注意事项
- 仅 `/pf-paper` Q&A 需要记录。`/pf-deep` 不记录(精读内容已写入正式笔记)。
- 如果无法从 formal-library.json 找到论文 domain/title记录失败不应影响用户使用。
## See Also
- [pf-end](pf-end.md) — 结束对话并保存记录
- [pf-deep](pf-deep.md) — 完整三阶段精读

View file

@ -1,94 +0,0 @@
---
name: pf-status
description: Check PaperForge installation and runtime status. Verifies configuration, paths, and data integrity.
allowed-tools: [Bash]
---
# /pf-status
## Purpose
查看 PaperForge 当前安装与运行状态。
`paperforge status` 会检查:
- 安装完整性Python 包、依赖)
- 配置文件(`paperforge.json`、`.env`
- 路径连通性exports、ocr、literature 目录)
- Zotero 数据目录链接状态
- Better BibTeX 导出文件状态
## CLI Equivalent
```bash
paperforge status
```
## Prerequisites
无特殊前置条件。此命令用于诊断安装问题,即使在配置不完整时也会尽量输出可用信息。
## Arguments
| 参数 | 必需 | 说明 |
|------|------|------|
| `--vault <PATH>` | 否 | 指定 Vault 根目录(默认当前目录) |
## Example
```bash
# 检查当前目录的 Vault 状态
paperforge status
# 检查指定 Vault 的状态
paperforge status --vault /path/to/vault
```
## Output
典型输出示例:
```
PaperForge Lite v1.2
====================
[安装检查]
✓ Python 包: paperforge v1.2.0
✓ 依赖: requests, pymupdf, pillow
[配置检查]
✓ paperforge.json: 存在且有效
✓ .env: 存在
✓ PADDLEOCR_API_TOKEN: 已设置
[路径检查]
✓ exports: <system_dir>/PaperForge/exports/
✓ ocr: <system_dir>/PaperForge/ocr/
✓ literature: <resources_dir>/<literature_dir>/
✓ Zotero: <system_dir>/Zotero/
[数据检查]
✓ library.json: 存在,包含 150 条文献
✓ 正式笔记: 150 篇
状态: 一切正常 ✅
```
## Error Handling
### 配置缺失
- `✗ paperforge.json: 未找到` → 运行 `paperforge doctor` 或重新执行安装
### 路径错误
- `✗ Zotero: 目录不存在或不是有效链接` → 创建 junction/symlink 到 Zotero 数据目录
### 依赖缺失
- `✗ 依赖: requests 未安装``pip install requests pymupdf pillow`
### API Key 未设置
- `✗ PADDLEOCR_API_TOKEN: 未设置` → 在 `.env` 文件中添加 API token
## See Also
- [pf-sync](pf-sync.md) — 文献同步
- [pf-ocr](pf-ocr.md) — OCR 提取

View file

@ -1,85 +0,0 @@
---
name: pf-sync
description: Sync Zotero Better BibTeX JSON export and generate/update formal literature notes.
allowed-tools: [Bash]
---
# /pf-sync
## Purpose
同步 Zotero Better BibTeX JSON 导出并生成/更新正式文献笔记。
`paperforge sync` 读取 Zotero JSON 中的新条目,直接生成正式文献笔记。
自动读取 `paperforge.json` 定位 exports 目录和 literature 目录。
## CLI Equivalent
```bash
paperforge sync
```
## Prerequisites
- [ ] Zotero 已安装且 Better BibTeX 插件已启用
- [ ] Better BibTeX 已配置自动导出 JSON
- [ ] JSON 导出文件存在(`<system_dir>/PaperForge/exports/library.json`
- [ ] `paperforge.json` 配置正确Vault 根目录下)
## Arguments
| 参数 | 必需 | 说明 |
|------|------|------|
| `--dry-run` | 否 | 预览变更,不实际写入文件 |
| `--domain <DOMAIN>` | 否 | 仅同步指定领域(如 `骨科` |
| `--selection` | 否 | (已废弃)仅保留以兼容旧版 |
| `--index` | 否 | (已废弃)仅保留以兼容旧版 |
| `--vault <PATH>` | 否 | 指定 Vault 根目录(默认当前目录) |
### 选项
```bash
paperforge sync --dry-run # 预览同步结果
paperforge sync --domain 骨科 # 按领域过滤同步
```
## Example
```bash
# 同步 Zotero 并生成正式笔记
paperforge sync
# 预览模式
paperforge sync --dry-run
# 仅同步特定领域
paperforge sync --domain 骨科
# 指定 Vault 目录
paperforge sync --vault /path/to/vault
```
## Output
```
[INFO] Found 5 new items
[INFO] Created 骨科/XXXXXXX.md
[INFO] Generated 5 formal notes
[INFO] Output: <resources_dir>/<literature_dir>/骨科/XXXXXXX - Title.md
```
生成文件:`<resources_dir>/<literature_dir>/<domain>/<key> - <Title>.md`
## Error Handling
### JSON 文件不存在
- `[ERROR] library.json not found` → 检查 Better BibTeX 导出路径
### 空 JSON 导出
- `[INFO] Found 0 new items` → 确认 Zotero 中有带 citation key 的文献Better BibTeX 已启用"Keep updated"
## See Also
- [pf-ocr](pf-ocr.md) — OCR 提取(下一步操作)
- [pf-status](pf-status.md) — 检查系统状态