15 KiB
ChatPapers 开发文档
版本:v0.2(草案)
更新日期:2026-07-20
关联需求: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 + 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 开发注意点
根据社区插件迁移经验,落地时需特别注意:
-
bootstrap.js导出方式
Zotero 9 通过scope[method]解析生命周期函数。bootstrap入口应产出顶层function install/startup/shutdown/uninstall/...,避免被 esbuild IIFE 包死导致钩子找不到。常见做法:对bootstrap.tstranspile 不 bundle,或显式配置 esbuild 保留全局函数。 -
废弃
ChromeUtils.import
Firefox 128+ / Zotero 9 路径下优先使用 ESM / 官方推荐的模块加载方式,避免遗留 JSM import。 -
UI 注册
优先使用 Zotero 原生 PreferencePane、菜单注入点;Toolkit 大版本升级后核对 Breaking Changes。 -
无重启启停
shutdown/onMainWindowUnload必须移除 DOM、监听、定时器、未完成的 fetch AbortController。
1.4 参考文档
- Zotero 7 for Developers(基础仍适用,9 在此之上演进)
- Zotero Plugin Dev Docs
- Zotero Plugin Scaffold
- 官方示例:make-it-red
2. 工程结构(目标)
以 template 为起点,建议收敛为以下结构:
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 分层
┌─────────────────────────────────────────┐
│ UI Layer(Reader 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 核心时序:提问
User 发送
→ ChatView 收集:input + selection? + session history
→ ContextBuilder:metadata + pdfText(截断) + messages
→ LlmClient.chatStream(messages, AbortSignal)
→ 逐 chunk 更新 UI
→ 完成:写入 SessionStore
3.3 核心时序:保存笔记
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 文本提取
实现顺序建议:
- 优先:使用 Zotero 已索引全文 / Reader 内可获取的文本 API(若可用)
- 次选:通过 PDF.js / 附件本地路径做页面级提取
- 失败:UI 提示「无文本层」,不发起无效大请求
ContextBuilder 策略(MVP):
- 若
len(text) <= maxContextChars:全文注入 - 否则:标题页元数据 + 前 N 字 + 后 M 字,或「先让模型做分段摘要」二选一(由需求待决策项决定)
4.3 LLM Client
MVP 以 单一 OpenAI 兼容客户端 覆盖 Ollama、LM Studio、OpenRouter 及各类云端;差异用 Provider 预设表 表达,而不是为每个厂商写一套协议。
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 |
| 自定义 | 用户输入 | 按需 | 无额外头 |
建议代码结构:
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 初始化(计划命令)
需求确认后,推荐从官方模板生成:
# 示例:使用 zotero-plugin-template 克隆后改名
npx tiged windingwind/zotero-plugin-template chatpapers
cd chatpapers
npm install
随后修改:
package.json→addonName/addonID/addonRefaddon/manifest.json→strict_*_version设为 Zotero 9- 清理 demo 代码,按本文结构迁入模块
5.3 本地调试
典型脚本(以 scaffold 为准):
npm run start # 启动 Zotero + 加载未打包插件 + 热重载
npm run build # 产出 build/ 与 xpi
npm run lint
调试技巧:
- 在 Zotero 中打开错误控制台(查看
[ChatPapers]日志) - 对 Reader 窗口使用独立 DevTools(若可用)检查侧边栏 DOM
- 用错误 Base URL 验证错误提示路径
- 用本地 Ollama(
http://127.0.0.1:11434/v1)验证兼容协议
5.4 环境变量
Scaffold 通常需要本机 Zotero 可执行文件路径,例如:
# Windows 示例(按实际安装路径调整)
set ZOTERO_PLUGIN_ZOTERO_CMD=C:\Program Files\Zotero\zotero.exe
具体变量名以当前 zotero-plugin-scaffold 文档为准。
6. 编码规范
- 只改任务相关代码;不顺手大重构模板无关文件
- TypeScript
strict;避免无必要any - 异步:统一
async/await;外部请求必须可 Abort - UI 字符串全部走 Fluent,禁止硬编码中文/英文(开发期临时除外,合并前清掉)
- 公共工具放
utils/;业务不互相深层耦合 - 每个模块顶部保持短注释(「做什么」),避免长篇叙述
6.1 建议的 npm scripts
{
"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)
- 未配置 API Key → 发送时友好提示
- 错误 Key → 401 可读错误
- 正常 PDF → 流式回复(分别用 Ollama / OpenRouter 各测一次)
- 选中文本提问 → 上下文包含选区
- 超长 PDF → 截断提示仍可回答
- 无文本 PDF → 明确失败原因
- Ollama 未启动 →
LocalServerDown友好提示 - 保存笔记 → 条目下可见 Note
- 禁用插件 → 侧边栏与菜单消失,无报错
8. 发布流程
- 更新版本号(
package.json/manifest.json) npm run build生成.xpi- GitHub Release 上传
.xpi - 配置
update_url指向updates.json(Scaffold 可生成) - 在干净 Zotero 9 Profile 中验证安装与更新
版本号遵循 SemVer:MAJOR.MINOR.PATCH。
9. 实施计划(与需求里程碑对齐)
| 阶段 | 工程任务 | 预估 |
|---|---|---|
| M0 | 确认需求待决策项;冻结插件 ID / 协议 | 0.5d |
| M1 | 从 template 初始化;偏好页;Reader 占位 Pane | 2–3d |
| M2 | PDF 提取 + 非流式/流式 Client + 单轮对话 | 3–5d |
| M3 | 多轮 UI、总结模板、存 Note、Abort | 3–4d |
| M4 | 会话持久化、i18n、日志脱敏、打包与 README 使用说明 | 2–3d |
以上为单人全职粗估,随 Reader API 熟悉度波动。
10. 目录外约定
- 密钥与真实 API Key 不得提交仓库;提供
.env.example(若需要)仅含路径类配置 docs/变更随功能演进;重大行为变更先更新需求再改代码- Issue / PR 标题建议前缀:
feat:/fix:/docs:/chore:
11. 下一步(工程开工清单)
需求方确认 requirements.md §10 待决策事项 后,按序执行:
- 从
zotero-plugin-template初始化仓库代码 - 改名与 Zotero 9 manifest
- 落地
PrefStore+ 空 Reader Pane - 实现
LlmClient与最小 Chat UI - 接通 PDF 文本与「保存为笔记」
如需,可在确认待决策项后直接进入 M1 脚手架初始化。