# 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 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 核心时序:提问 ```text User 发送 → ChatView 收集:input + selection? + session history → ContextBuilder:metadata + 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 | 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 待决策事项](./requirements.md#10-待决策事项需产品确认) 后,按序执行: 1. 从 `zotero-plugin-template` 初始化仓库代码 2. 改名与 Zotero 9 manifest 3. 落地 `PrefStore` + 空 Reader Pane 4. 实现 `LlmClient` 与最小 Chat UI 5. 接通 PDF 文本与「保存为笔记」 如需,可在确认待决策项后直接进入 M1 脚手架初始化。