Files
2026-07-20 14:40:17 +08:00

412 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ChatPapers 开发文档
> 版本:v0.2(草案)
> 更新日期:2026-07-20
> 关联需求:[requirements.md](./requirements.md)
---
## 1. 技术选型
### 1.1 平台与形态
| 项 | 选择 | 理由 |
| --- | --- | --- |
| 宿主 | Zotero 9 Desktop | 需求明确;2026 年已有多款插件 bump 至 `9.*` |
| 扩展形态 | Bootstrapped Extension | Zotero 官方推荐路径(非 WebExtension 受限 API |
| 清单 | `manifest.json` + `bootstrap.js` | Zotero 7+ 标准;9 需注意 bootstrap 导出方式 |
| 目标版本字段 | `strict_min_version: "9.0"``strict_max_version: "9.*"` | 与需求一致;若兼容 8 需额外回归 |
### 1.2 语言与工具链
| 项 | 选择 | 说明 |
| --- | --- | --- |
| 语言 | TypeScript | 类型安全;配合 `zotero-types` |
| 构建 | esbuild(经 `zotero-plugin-scaffold` | 社区主流;热重载友好 |
| 脚手架 | [zotero-plugin-template](https://github.com/windingwind/zotero-plugin-template) + [zotero-plugin-scaffold](https://github.com/zotero-plugin-dev/zotero-plugin-scaffold) | 一键 build / start / release |
| UI Toolkit | `zotero-plugin-toolkit`(注意 Z9 API 迁移) | 菜单、快捷键、进度等;部分旧 API 已迁至原生 |
| 本地化 | Fluent`.ftl`) | Zotero 未来方向;避免 DTD |
| 包管理 | pnpm 或 npm | 团队自定,文档示例用 npm |
### 1.3 Zotero 9 开发注意点
根据社区插件迁移经验,落地时需特别注意:
1. **`bootstrap.js` 导出方式**
Zotero 9 通过 `scope[method]` 解析生命周期函数。`bootstrap` 入口应产出**顶层** `function install/startup/shutdown/uninstall/...`,避免被 esbuild IIFE 包死导致钩子找不到。常见做法:对 `bootstrap.ts` **transpile 不 bundle**,或显式配置 esbuild 保留全局函数。
2. **废弃 `ChromeUtils.import`**
Firefox 128+ / Zotero 9 路径下优先使用 ESM / 官方推荐的模块加载方式,避免遗留 JSM import。
3. **UI 注册**
优先使用 Zotero 原生 PreferencePane、菜单注入点;Toolkit 大版本升级后核对 Breaking Changes。
4. **无重启启停**
`shutdown` / `onMainWindowUnload` 必须移除 DOM、监听、定时器、未完成的 fetch AbortController。
### 1.4 参考文档
- [Zotero 7 for Developers](https://www.zotero.org/support/dev/zotero_7_for_developers)(基础仍适用,9 在此之上演进)
- [Zotero Plugin Dev Docs](https://windingwind.github.io/doc-for-zotero-plugin-dev/)
- [Zotero Plugin Scaffold](https://zotero-plugin.dev/zotero-plugin-scaffold/)
- 官方示例:[make-it-red](https://github.com/zotero/make-it-red)
---
## 2. 工程结构(目标)
以 template 为起点,建议收敛为以下结构:
```text
chatpapers/
├── addon/ # 打包进 xpi 的静态资源
│ ├── bootstrap.js # 由 bootstrap.ts 转译生成(顶层函数)
│ ├── manifest.json
│ ├── prefs.js # 默认偏好
│ ├── locale/
│ │ ├── en-US/chatpapers.ftl
│ │ └── zh-CN/chatpapers.ftl
│ └── content/ # 图标、样式、偏好 XHTML 等
├── src/
│ ├── index.ts # 插件主入口(bundle
│ ├── addon.ts # Addon 单例、生命周期编排
│ ├── hooks.ts # startup/shutdown/window hooks 业务
│ ├── modules/
│ │ ├── ui/
│ │ │ ├── readerPane.ts # PDF Reader 侧边栏注入
│ │ │ ├── chatView.ts # 对话 UI
│ │ │ └── prefs.ts # 偏好面板
│ │ ├── pdf/
│ │ │ ├── extractor.ts # 文本提取适配层
│ │ │ └── context.ts # 上下文裁剪 / 分块
│ │ ├── llm/
│ │ │ ├── client.ts # OpenAI 兼容客户端(stream / models
│ │ │ ├── providers.ts # Ollama / LM Studio / OpenRouter 等预设
│ │ │ ├── prompts.ts # 系统提示与总结模板
│ │ │ └── types.ts
│ │ ├── storage/
│ │ │ ├── prefs.ts # Preference 读写封装
│ │ │ └── sessions.ts # 会话持久化
│ │ └── zotero/
│ │ ├── notes.ts # 创建/追加 Note
│ │ └── items.ts # 元数据读取
│ └── utils/
│ ├── logger.ts
│ └── abort.ts
├── docs/
│ ├── requirements.md
│ └── development.md
├── zotero-plugin.config.ts
├── package.json
├── tsconfig.json
└── readme.md
```
**命名约定**
- 插件 ID`yehongyu@njau.edu.cn`
- Pref 前缀:`extensions.chatpapers.*`
- Fluent 文件:`chatpapers.ftl`(避免与其它插件冲突)
- 日志前缀:`[ChatPapers]`
---
## 3. 架构设计
### 3.1 分层
```text
┌─────────────────────────────────────────┐
│ UI LayerReader Pane / Prefs / Menus
├─────────────────────────────────────────┤
│ Application Services │
│ ChatSession · Summarize · SaveNote │
├─────────────────────────────────────────┤
│ Adapters │
│ PdfExtractor · LlmClient · PrefStore │
├─────────────────────────────────────────┤
│ Zotero Platform APIs │
└─────────────────────────────────────────┘
```
原则:
- UI 不直接 `fetch`;统一经 `LlmClient`
- PDF 提取细节隔离在 `PdfExtractor`,便于 Z9 API 变更时单点替换
- 所有偏好读写经 `PrefStore`,禁止散落魔法字符串
### 3.2 核心时序:提问
```text
User 发送
→ ChatView 收集:input + selection? + session history
→ ContextBuildermetadata + pdfText(截断) + messages
→ LlmClient.chatStream(messages, AbortSignal)
→ 逐 chunk 更新 UI
→ 完成:写入 SessionStore
```
### 3.3 核心时序:保存笔记
```text
User 点击「保存为笔记」
→ 取当前 Assistant 消息(或总结全文)
→ Notes.createChildNote(item, { title, bodyHtmlOrMd })
→ Toast / 状态提示成功
```
Markdown → Zotero Note:优先转为简单 HTML(标题、段落、列表),保持可读。
---
## 4. 关键模块设计要点
### 4.1 Reader 侧边栏
- 在 Reader 窗口加载后注册侧边栏 Tab(具体 DOM/API 以 Zotero 9 Reader 为准,实现前用 DevTools 探测现网结构)
- Pane 与 `itemID` / `attachmentKey` 绑定;切换 PDF 时重置或切换会话
- 卸载时移除 Tab 与事件,防止重复注入
### 4.2 PDF 文本提取
实现顺序建议:
1. **优先**:使用 Zotero 已索引全文 / Reader 内可获取的文本 API(若可用)
2. **次选**:通过 PDF.js / 附件本地路径做页面级提取
3. **失败**:UI 提示「无文本层」,不发起无效大请求
`ContextBuilder` 策略(MVP):
-`len(text) <= maxContextChars`:全文注入
- 否则:标题页元数据 + 前 N 字 + 后 M 字,或「先让模型做分段摘要」二选一(由需求待决策项决定)
### 4.3 LLM Client
MVP 以 **单一 OpenAI 兼容客户端** 覆盖 Ollama、LM Studio、OpenRouter 及各类云端;差异用 **Provider 预设表** 表达,而不是为每个厂商写一套协议。
```http
POST {baseUrl}/chat/completions
Authorization: Bearer {apiKey} # 本地预设可省略
Content-Type: application/json
{
"model": "...",
"messages": [...],
"stream": true,
"temperature": 0.3,
"max_tokens": 2048
}
```
#### Provider 预设(与需求 §4.4 对齐)
| 预设 | 默认 Base URL | Key | 额外行为 |
| --- | --- | --- | --- |
| Ollama | `http://127.0.0.1:11434/v1` | 可空 | 可选 `GET /v1/models` 刷新列表;连接失败提示启动 Ollama |
| LM Studio | `http://127.0.0.1:1234/v1` | 可空 | 可选拉取 models;提示开启 Local Server |
| OpenRouter | `https://openrouter.ai/api/v1` | 必填 | 附加可选头 `HTTP-Referer``X-Title` |
| OpenAI / DeepSeek / 硅基流动 | 各官方 `/v1` | 必填 | 标准 Bearer |
| 自定义 | 用户输入 | 按需 | 无额外头 |
建议代码结构:
```text
src/modules/llm/
├── client.ts # 统一 chatStream / listModels
├── providers.ts # 预设常量:id → { baseUrl, needsKey, headers? }
├── url.ts # Base URL 规范化(补 /v1、去尾斜杠)
├── prompts.ts
└── types.ts
```
要求:
- 解析 SSE `data: {...}`;处理 `[DONE]`
- `AbortController` 支持停止生成
- 统一错误类型:`AuthError` / `RateLimitError` / `NetworkError` / `LocalServerDown` / `ProviderError`
- **禁止**将 API Key 打进日志;verbose 模式下对 Header 脱敏
- Base URL 规范化:兼容 `http://127.0.0.1:11434``.../v1`
- 切换预设时:写入默认 Base URL,**不覆盖**用户已改过的自定义 URL(可用「重置为预设默认」按钮)
本地联调建议:
- Ollama`ollama pull qwen2.5` → Base URL 默认 → model 填实际名
- LM Studio:加载模型并 Start Server → 端口若非 1234 则改 URL
- OpenRouter:填 Key + 模型如 `openai/gpt-4o-mini`
### 4.4 会话存储
MVP 可选方案:
| 方案 | 优点 | 缺点 |
| --- | --- | --- |
| A. Preferences 存 JSON | 实现快 | 容量与性能差 |
| B. 插件数据目录 JSON 文件 | 清晰、易清理 | 需处理路径与并发 |
| C. IndexedDB / SQLite | 可扩展 | 实现成本高 |
**建议 MVP 用 B**,路径形如:`{profile}/chatpapers/sessions/{itemKey}-{attachmentKey}.json`
### 4.5 偏好面板
使用 `Zotero.PreferencePanes.register`Z9 推荐原生 API)注册:
- **Provider**:下拉预设(Ollama / LM Studio / OpenRouter / … / 自定义)+「重置为预设默认」
- 基本:Base URL、Key(本地可空)、Model、「刷新模型列表」
- 生成:temperature、maxTokens、timeout、maxContextChars
- OpenRouter 高级:Referer / Title(可折叠)
- 提示词:system / summary 可编辑文本框
- 隐私说明:明确当前预设是本地还是云端
---
## 5. 开发环境搭建
### 5.1 前置依赖
- Node.js 20+LTS
- 已安装 **Zotero 9** 桌面版
- Git
### 5.2 初始化(计划命令)
需求确认后,推荐从官方模板生成:
```bash
# 示例:使用 zotero-plugin-template 克隆后改名
npx tiged windingwind/zotero-plugin-template chatpapers
cd chatpapers
npm install
```
随后修改:
- `package.json``addonName` / `addonID` / `addonRef`
- `addon/manifest.json``strict_*_version` 设为 Zotero 9
- 清理 demo 代码,按本文结构迁入模块
### 5.3 本地调试
典型脚本(以 scaffold 为准):
```bash
npm run start # 启动 Zotero + 加载未打包插件 + 热重载
npm run build # 产出 build/ 与 xpi
npm run lint
```
调试技巧:
1. 在 Zotero 中打开错误控制台(查看 `[ChatPapers]` 日志)
2. 对 Reader 窗口使用独立 DevTools(若可用)检查侧边栏 DOM
3. 用错误 Base URL 验证错误提示路径
4. 用本地 Ollama`http://127.0.0.1:11434/v1`)验证兼容协议
### 5.4 环境变量
Scaffold 通常需要本机 Zotero 可执行文件路径,例如:
```bash
# Windows 示例(按实际安装路径调整)
set ZOTERO_PLUGIN_ZOTERO_CMD=C:\Program Files\Zotero\zotero.exe
```
具体变量名以当前 `zotero-plugin-scaffold` 文档为准。
---
## 6. 编码规范
1. **只改任务相关代码**;不顺手大重构模板无关文件
2. TypeScript `strict`;避免无必要 `any`
3. 异步:统一 `async/await`;外部请求必须可 Abort
4. UI 字符串全部走 Fluent,禁止硬编码中文/英文(开发期临时除外,合并前清掉)
5. 公共工具放 `utils/`;业务不互相深层耦合
6. 每个模块顶部保持短注释(「做什么」),避免长篇叙述
### 6.1 建议的 npm scripts
```json
{
"scripts": {
"start": "zotero-plugin serve",
"build": "zotero-plugin build",
"lint": "eslint .",
"release": "zotero-plugin release"
}
}
```
(实际命令以模板生成结果为准。)
---
## 7. 测试策略
| 层级 | 内容 | 时机 |
| --- | --- | --- |
| 手工冒烟 | 安装、侧边栏、提问、总结、存笔记、禁用清理 | 每个里程碑 |
| 单元测试 | `ContextBuilder` 截断、URL 规范化、错误映射 | M2+ |
| 集成(可选) | scaffold + Mocha 在真实 Zotero 中跑 | M4 |
最低验收清单见需求文档里程碑 M1–M4。
**手工测试用例(MVP**
1. 未配置 API Key → 发送时友好提示
2. 错误 Key → 401 可读错误
3. 正常 PDF → 流式回复(分别用 Ollama / OpenRouter 各测一次)
4. 选中文本提问 → 上下文包含选区
5. 超长 PDF → 截断提示仍可回答
6. 无文本 PDF → 明确失败原因
7. Ollama 未启动 → `LocalServerDown` 友好提示
8. 保存笔记 → 条目下可见 Note
9. 禁用插件 → 侧边栏与菜单消失,无报错
---
## 8. 发布流程
1. 更新版本号(`package.json` / `manifest.json`
2. `npm run build` 生成 `.xpi`
3. GitHub Release 上传 `.xpi`
4. 配置 `update_url` 指向 `updates.json`Scaffold 可生成)
5. 在干净 Zotero 9 Profile 中验证安装与更新
版本号遵循 SemVer`MAJOR.MINOR.PATCH`
---
## 9. 实施计划(与需求里程碑对齐)
| 阶段 | 工程任务 | 预估 |
| --- | --- | --- |
| M0 | 确认需求待决策项;冻结插件 ID / 协议 | 0.5d |
| M1 | 从 template 初始化;偏好页;Reader 占位 Pane | 23d |
| M2 | PDF 提取 + 非流式/流式 Client + 单轮对话 | 35d |
| M3 | 多轮 UI、总结模板、存 Note、Abort | 34d |
| M4 | 会话持久化、i18n、日志脱敏、打包与 README 使用说明 | 23d |
以上为单人全职粗估,随 Reader API 熟悉度波动。
---
## 10. 目录外约定
- 密钥与真实 API Key **不得**提交仓库;提供 `.env.example`(若需要)仅含路径类配置
- `docs/` 变更随功能演进;重大行为变更先更新需求再改代码
- Issue / PR 标题建议前缀:`feat:` / `fix:` / `docs:` / `chore:`
---
## 11. 下一步(工程开工清单)
需求方确认 [requirements.md §10 待决策事项](./requirements.md#10-待决策事项需产品确认) 后,按序执行:
1.`zotero-plugin-template` 初始化仓库代码
2. 改名与 Zotero 9 manifest
3. 落地 `PrefStore` + 空 Reader Pane
4. 实现 `LlmClient` 与最小 Chat UI
5. 接通 PDF 文本与「保存为笔记」
如需,可在确认待决策项后直接进入 M1 脚手架初始化。