Files
chatpapers/docs/newidea/DUAL_MODE_ARCHITECTURE.md
2026-08-29 22:13:55 +08:00

35 KiB
Raw Permalink Blame History

ChatPapers 双模式集成架构设计

字段 内容
产品名称 ChatPapers for Zotero
文档名称 双模式集成架构(方案 A
文档版本 v0.1
状态 草案
目标平台 Zotero 9macOS / Windows
关联文档 双模式 PRD开发文档BanDu 架构参考

本文档定位:描述 ChatPapers 插件在保留文字对话能力的基础上,扩展语音伴读(备课 → 听课 → 语音问答)的架构设计。
产品统一品牌为 ChatPapers;两种模式是同一条「与论文对话」产品线的不同交互形态——文字即时聊 vs 语音系统讲。


1. 架构概述

1.1 架构演进

v0.2(当前)                         v1.0(目标)
─────────────────                   ─────────────────────────────────
ChatPapers 插件                      ChatPapers 插件(单一 .xpi
  └── 文字对话 pane                    ├── 文字对话 pane(已有)
      PDFWorker 纯文本                   │     即时问答 / 总结 / JSON 会话
      LLM 流式                           │
      sessions/*.json                    └── 语音伴读 pane(新增)
                                             MinerU 段落树
                                             Lecture 备课 + TTS + 播放器
                                             SQLite + mp3 缓存
                                         共享:LLM · PDF 附件 · 笔记 · BibTeX

1.2 架构风格

Local-first 双模式插件架构:核心计算与缓存均在用户机器完成;仅将段落文本与用户问题发送至用户配置的 LLM / TTS 服务。无自建后端。

1.3 设计原则

原则 说明
单一插件、两种对话形态 文字对话与语音伴读并存,共享 LLM 与 Zotero 集成
模块隔离 语音模块不侵入文字对话代码路径
备课一次、缓存复用 Lecture 持久化;attachment ID + 文件哈希为缓存键
增量交付 摘要 → beats → 逐段 → TTS 分阶段就绪
UI / 领域 / 集成 分离 降低 Zotero API 变动影响
Grounding 优先 生成内容必须可追溯到段落 ID
优雅降级 refs 无效、无 timestamps、无 BBT 均有 fallback

1.4 系统上下文

                         ┌─────────────────┐
                         │      用户        │
                         └────────┬────────┘
                                  │
                   ┌──────────────▼──────────────┐
                   │     Zotero 9(宿主)         │
                   │  ┌────────────────────────┐ │
                   │  │   ChatPapers 插件       │ │
                   │  │ ┌──────────┬─────────┐ │ │
                   │  │ │文字对话   │语音伴读  │ │ │
                   │  │ │ ChatView │LectureUI│ │ │
                   │  │ └────┬─────┴────┬────┘ │ │
                   │  │      │  Shared   │      │ │
                   │  │      └─────┬─────┘      │ │
                   │  └────────────┼────────────┘ │
                   └───────────────┼──────────────┘
                                   │
         ┌─────────────────────────┼─────────────────────────┐
         │                         │                         │
         ▼                         ▼                         ▼
  ┌─────────────┐          ┌─────────────┐          ┌─────────────┐
  │ Zotero      │          │ LLM API     │          │ TTS API     │
  │ PDFWorker   │          │ / Ollama    │          │ 火山/Azure等 │
  │ MinerU      │          │ (共享)     │          │(语音模式)  │
  └─────────────┘          └─────────────┘          └─────────────┘
         │
         ▼
  ┌─────────────────────────────────────┐
  │ {Profile}/chatpapers/               │
  │   sessions/*.json    ← 文字对话      │
  │   lecture/lecture.db ← 语音伴读      │
  │   lecture/audio/                     │
  └─────────────────────────────────────┘

1.5 两种对话形态对比

维度 文字对话(已有) 语音伴读(新增)
用户意图 「我现在想问什么」 「请 AI 先读完再给我讲」
交互 打字 → 文字流式回复 听课 → 打字提问 → 文字 + 语音回复
PDF 输入 PDFWorker 整篇纯文本 MinerU 结构化段落树
LLM 用法 每轮即时组装 context 一次性备课 + 按需问答
持久化 sessions/{itemKey}.json lecture/lecture.db + mp3
TTS 讲解 + 问答回答均支持
笔记 ChatPapers: 前缀 ChatPapers 伴读报告: 前缀

2. 整体分层架构

┌──────────────────────────────────────────────────────────────────┐
│                    Presentation Layer(表现层)                    │
│  【文字】chatView.ts · multiChatView.ts · readerPane (Chat sec)   │
│  【语音】lecturePane.ts · player/ · libraryWindow.tsP1         │
│  【共享】prefs.ts · preferences.xhtml                            │
└───────────────────────────────┬──────────────────────────────────┘
                                │
┌───────────────────────────────▼──────────────────────────────────┐
│              Application Layer(应用层 / 用例)                    │
│  【文字】buildChatMessages · buildMultiChatMessages(已有)        │
│  【语音】PrepareLectureUseCase · PlayUseCase                      │
│         AskQuestionUseCase · GenerateReportUseCase               │
└───────────────────────────────┬──────────────────────────────────┘
                                │
┌───────────────────────────────▼──────────────────────────────────┐
│                   Domain Layer(领域层)                            │
│  【文字】ChatMessage · ExtractResult(已有)                        │
│  【语音】PaperCache · Paragraph · Chapter · Lecture · Beat         │
│         ParagraphExplanation · QARecord · Progress · Highlight     │
│         LecturePreparationState · PlayerState                      │
└───────────────────────────────┬──────────────────────────────────┘
                                │
┌───────────────────────────────▼──────────────────────────────────┐
│              Infrastructure Layer(基础设施层)                    │
│  ┌─ 共享 ─────────────────────────────────────────────────────┐  │
│  │ llm/client · llm/providers · zotero/notes · zotero/citeExport│  │
│  │ pdf/extractor (附件定位) · pdf/selection · utils/*          │  │
│  └────────────────────────────────────────────────────────────┘  │
│  ┌─ 文字专属 ─────────────────────────────────────────────────┐  │
│  │ pdf/context · pdf/multiContext · storage/sessions           │  │
│  └────────────────────────────────────────────────────────────┘  │
│  ┌─ 语音专属 ─────────────────────────────────────────────────┐  │
│  │ lecture/infrastructure/pdf (MinerU) · tts/ · storage/sqlite │  │
│  │ lecture/infrastructure/rag · audioStorage · promptRegistry  │  │
│  └────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────┘

3. 现有代码映射

3.1 共享层(已实现,语音模式直接复用)

模块路径 职责 文字对话 语音伴读
src/hooks.ts 生命周期、注册入口 扩展注册
src/modules/ui/readerPane.ts Item pane section 注册 扩展第二 section
src/modules/llm/client.ts LLM 流式请求 ✓ 备课 + 问答
src/modules/llm/providers.ts Provider 预设 ✓ 只读共享 prefs
src/modules/llm/types.ts ChatMessage 等类型 问答层可复用类型
src/modules/pdf/extractor.ts PDF 附件定位 ✓ 路径 + 扩展 hash
src/modules/pdf/selection.ts Reader 选区 ✓ 「解释这句」
src/modules/zotero/notes.ts 子笔记创建 ✓ 伴读报告
src/modules/zotero/citeExport.ts BibTeX 导出 ✓ 参考文献卡片
src/utils/markdown.ts Markdown 渲染 ✓ 报告预览
src/utils/prefs.ts Preference 读写 ✓ 扩展 TTS prefs
src/utils/abort.ts 请求取消

3.2 文字对话专属(已实现,语音模式不得引用)

模块路径 职责
src/modules/ui/chatView.ts 文字对话 UI
src/modules/ui/multiChatView.ts 多 PDF 联合对话
src/modules/pdf/context.ts 单篇 context 组装
src/modules/pdf/multiContext.ts 多篇 context 组装
src/modules/llm/prompts.ts 文字对话 system / summary prompt
src/modules/llm/history.ts 论文材料持久化到消息历史
src/modules/storage/sessions.ts JSON 会话文件

3.3 语音伴读新建(src/modules/lecture/

模块 职责
lecture/ui/lecturePane.ts Item pane「语音伴读」section
lecture/ui/playerController.ts 播放状态机 + <audio>
lecture/ui/unitList.ts beats / 段落列表
lecture/ui/qaPanel.ts 锚定提问 + 流式 + 重播
lecture/application/prepareLecture.ts 备课流水线编排
lecture/application/playLecture.ts 播放 / 跳播 / 断点
lecture/application/askQuestion.ts 锚定问答 + RAG
lecture/application/generateReport.ts 报告组装 + Note 回写
lecture/domain/* 领域实体与状态机
lecture/infrastructure/pdf/parser.ts MinerU 封装
lecture/infrastructure/pdf/fileHash.ts SHA-256
lecture/infrastructure/tts/client.ts TTS 多提供商
lecture/infrastructure/tts/providers.ts TTS 预设
lecture/infrastructure/storage/repository.ts SQLite CRUD
lecture/infrastructure/storage/audioStorage.ts mp3 路径管理
lecture/infrastructure/rag/index.ts FTS5 段落检索
lecture/infrastructure/llm/lecturePrompts.ts 备课 / beats / 三层讲解 prompt
lecture/infrastructure/events.ts EventBus

4. 依赖规则

                    ┌─────────────┐
                    │   utils/    │
                    └──────┬──────┘
                           │
          ┌────────────────┼────────────────┐
          │                │                │
          ▼                ▼                ▼
    ┌──────────┐    ┌──────────┐    ┌──────────┐
    │   llm/   │    │ pdf/ext  │    │ zotero/  │
    └────┬─────┘    └────┬─────┘    └────┬─────┘
         │               │               │
    ┌────┴───────┐       │          ┌────┴─────┐
    │            │       │          │          │
    ▼            ▼       ▼          ▼          ▼
 chatView   multiChat  selection  notes   citeExport
 (文字 UI)   (文字 UI)              (共享)

 lecture/* ──可引用──► llm/, pdf/extractor, pdf/selection, zotero/, utils/

 chatView / multiChat / context / sessions ──禁止引用──► lecture/*
 lecture/* ──禁止引用──► chatView, context, sessions, multiContext

运行时隔离:两个 pane 的 onAsyncRender 各自实例化 View,异常在 View 边界捕获,不向上冒泡至 hooks.ts 导致整个插件失效。


5. 启动与注册流程

// hooks.ts(目标形态,伪代码)
async function onStartup() {
  await Promise.all([Zotero.initializationPromise, ...]);

  initLocale();
  registerPrefs();           // 扩展 TTS / 教师风格分组
  registerChatPane();        // 已有:文字对话 section
  registerLecturePane();     // 新增:语音伴读 section
  registerReaderSelectionHook();

  addon.data.initialized = true;
}
// readerPane.ts 扩展策略
export function registerChatPane() { /* 现有,不动 */ }

export function registerLecturePane() {
  Zotero.ItemPaneManager.registerSection({
    paneID: "chatpapers-lecture",
    pluginID: config.addonID,
    header: { l10nID: "chatpapers-item-section-lecture-head", ... },
    onItemChange: ({ item, setEnabled }) => {
      setEnabled(Boolean(item && findPdfAttachment(item)));
    },
    onAsyncRender: async ({ body, item }) => {
      const view = new LecturePaneView(doc, body, item);
      await view.mount();
    },
    // ...
  });
}

6. 语音伴读模块详细设计

6.1 Zotero 集成适配(复用 + 薄封装)

语音模块不新建独立 Adapter 文件树,而是在 lecture/infrastructure/zotero/ 做薄封装,内部调用已有模块:

接口 实现
getPdfAttachment(item) 委托 findPdfAttachment
computeFileHash(path) 新增:IOUtils.read + SubtleCrypto / nsICryptoHash
openPdfAtPage(itemId, page) 新增:Zotero.Reader API
createLectureNote(item, markdown) 委托 createChildNote,标题 ChatPapers 伴读报告: {title}
exportBibtex(item) 委托 citeExport.formatCiteText

6.2 PdfParserMinerU

与文字对话 PDF 路径分离:文字对话继续 PDFWorker.getFullText;语音伴读走 MinerU。

输入PDF 本地路径(来自 attachment
输出

interface ParseResult {
  chapters: Chapter[];
  paragraphs: Paragraph[];
  metadata: { pageCount: number; detectedLanguage?: "en" | "zh" };
}

interface Paragraph {
  id: string;           // "p-0017"
  chapterId: string;
  orderIndex: number;
  text: string;
  page: number;
  language?: "en" | "zh";
  bulletPoints?: string[];
}

失败策略

错误类型 UI 文案
ScannedPdfError 扫描版 PDF,请先 OCR
ParseTimeoutError 解析超时,请重试
MinerUNotFoundError 未检测到 MinerU,见设置页安装说明

6.3 LLM 扩展(共享 client,专属 prompt

llm/client.ts 保持通用 chatStream;语音备课在 lecture/infrastructure/llm/lecturePrompts.ts 定义模板,通过 client 调用:

方法 位置 用途
chatStream llm/client.ts(已有) 文字对话 + 语音问答
generateSummary lecture/.../lectureLlm.ts 全文摘要四字段
generateBeats 同上 815 beats + refs[]
generateParagraphExplanation 同上 三层讲解
generateSentenceExplanation 同上 逐句讲解
answerWithRag lecture/application/askQuestion.ts 锚定问答

配置:语音备课 temperature 0.20.4;与文字对话共用 getRuntimeConfig(),不新增 LLM prefs 键。

6.4 TtsClient

interface TtsResult {
  audioPath: string;
  timestamps: WordTimestamp[];
  durationMs: number;
}

interface WordTimestamp {
  text: string;
  startMs: number;
  endMs: number;
}

interface TtsProvider {
  name: string;
  synthesize(text: string, options: TtsOptions): Promise<TtsResult>;
  supportsTimestamps: boolean;
}

高亮降级链WORDSENTENCEPARAGRAPHNONE(仅进度条)

Prefs 键(建议)extensions.zotero.chatpapers.ttsProvider / ttsApiKey / ttsVoice

6.5 RagIndex

interface RagIndex {
  build(paperId: string, paragraphs: Paragraph[]): Promise<void>;
  search(paperId: string, query: string, topK: number): Paragraph[];
}

一期 SQLite FTS5,不引入 embedding。索引在解析完成后构建,与 paragraph_fts 表同步。

6.6 PrepareLectureUseCase

职责:编排备课流水线;发出增量就绪事件;保证同一 paper 互斥(备课锁)。

伪流程

start(paperId):
  if lecture_status != idle && != failed: return  // 防重入
  set status → parsing
  paragraphs ← PdfParser.parse(path)
  persist paragraphs + FTS
  set status → summarizing
  summary ← Llm.generateSummary(paragraphs)
  persist lecture.summary
  set status → beats_generating
  beats ← Llm.generateBeats(summary, paragraphIndex)
  beats ← validateRefs(beats, paragraphIds)
  persist beats
  emit lecture:beats_ready          // UI 可开整体讲解
  set status → paragraphs_generating
  for batch in paragraphs.chunk(5):
    explanations ← Llm.generateParagraphExplanation(batch)
    persist + emit lecture:paragraph_ready
  set status → tts_generating
  for unit in beats + explanations:
    audio ← Tts.synthesize(unit.script)
    persist audio_path + emit tts:unit_ready
  set status → ready

6.7 PlayUseCase + PlayerController

interface PlayerState {
  mode: "overview" | "paragraph";
  currentUnitId: string;
  isPlaying: boolean;
  playbackRate: 1 | 1.5 | 2;
  highlightCursorMs: number;
}

跳播currentUnitId 变更 → 加载 mp3 → audio.currentTime = 0 → 更新 progress 表。

断点续播pane 打开时读 progress.last_unit_id + last_position_ms

6.8 AskQuestionUseCase

与文字对话的差异:

文字对话 语音伴读问答
上下文 整篇 PDF 文本(可能截断) 锚点 + 摘要 + RAG topK=5
存储 sessions JSON qa_record 表
输出 仅文字流 文字流 + TTS mp3
锚点 选区字符串 beat / paragraph / sentence / global
interface AskContext {
  question: string;
  anchor: {
    type: "beat" | "paragraph" | "sentence" | "global";
    id?: string;
    text?: string;
    explanation?: string;
  };
  summary: SummaryJson;
  ragParagraphs: Paragraph[];
  paperTitle: string;
}

6.9 GenerateReportUseCase

ReportBuilder.build(paperId):
  summary      ← lecture.summary_json
  progress     ← progress.listened_units_json
  qas          ← qa_record ORDER BY created_at
  highlights   ← highlight JOIN paragraph
  bibtex       ← citeExport

MarkdownRenderer → createLectureNote(parentItem, markdown)

Note 策略:同名 ChatPapers 伴读报告: {title} 存在则更新,避免笔记爆炸。


7. 数据模型

7.1 存储目录

{ZoteroProfile}/chatpapers/
├── sessions/                         # 文字对话(已有)
│   └── {itemKey}-{attachmentKey}.json
└── lecture/                          # 语音伴读(新增)
    ├── lecture.db                    # SQLite
    └── audio/
        └── {cacheKey}/
            ├── beat-{id}.mp3
            ├── para-{id}.mp3
            └── qa-{id}.mp3

7.2 缓存键

cacheKey = SHA256(attachmentId + "|" + fileHash)

失效条件:
  • fileHash 变化
  • 用户强制重新备课
  • lecture prompt_version major 升级(可选)

7.3 ER 关系

PaperCache 1──* Paragraph
PaperCache 1──1 Lecture
Lecture    1──* Beat
Lecture    1──* ParagraphExplanation
PaperCache 1──* QARecord
PaperCache 1──1 Progress
PaperCache 1──* Highlight

7.4 SQLite 表结构

-- 论文缓存根表
CREATE TABLE paper_cache (
  id              TEXT PRIMARY KEY,
  attachment_id   TEXT NOT NULL,
  item_id         TEXT NOT NULL,
  file_hash       TEXT NOT NULL,
  file_path       TEXT NOT NULL,
  title           TEXT,
  language        TEXT,
  parse_status    TEXT NOT NULL,
  lecture_status  TEXT NOT NULL,
  created_at      INTEGER NOT NULL,
  updated_at      INTEGER NOT NULL,
  UNIQUE(attachment_id, file_hash)
);

CREATE TABLE chapter (
  id              TEXT PRIMARY KEY,
  paper_id        TEXT NOT NULL REFERENCES paper_cache(id),
  title           TEXT NOT NULL,
  order_index     INTEGER NOT NULL
);

CREATE TABLE paragraph (
  id              TEXT PRIMARY KEY,
  paper_id        TEXT NOT NULL REFERENCES paper_cache(id),
  chapter_id      TEXT REFERENCES chapter(id),
  order_index     INTEGER NOT NULL,
  text            TEXT NOT NULL,
  page            INTEGER NOT NULL
);

CREATE TABLE lecture (
  paper_id        TEXT PRIMARY KEY REFERENCES paper_cache(id),
  summary_json    TEXT NOT NULL,
  prompt_version  TEXT NOT NULL,
  created_at      INTEGER NOT NULL
);

CREATE TABLE beat (
  id              TEXT PRIMARY KEY,
  paper_id        TEXT NOT NULL REFERENCES paper_cache(id),
  order_index     INTEGER NOT NULL,
  title           TEXT,
  type            TEXT NOT NULL,
  script          TEXT NOT NULL,
  refs_json       TEXT NOT NULL,
  audio_path      TEXT,
  timestamps_json TEXT,
  tts_status      TEXT NOT NULL
);

CREATE TABLE paragraph_explanation (
  paragraph_id    TEXT PRIMARY KEY REFERENCES paragraph(id),
  paper_id        TEXT NOT NULL REFERENCES paper_cache(id),
  layer_what      TEXT NOT NULL,
  layer_how       TEXT NOT NULL,
  layer_where     TEXT NOT NULL,
  audio_path      TEXT,
  timestamps_json TEXT,
  gen_status      TEXT NOT NULL,
  tts_status      TEXT NOT NULL
);

CREATE TABLE progress (
  paper_id        TEXT PRIMARY KEY REFERENCES paper_cache(id),
  last_mode       TEXT,
  last_unit_id    TEXT,
  last_position_ms INTEGER DEFAULT 0,
  listened_units_json TEXT,
  updated_at      INTEGER NOT NULL
);

CREATE TABLE qa_record (
  id              TEXT PRIMARY KEY,
  paper_id        TEXT NOT NULL REFERENCES paper_cache(id),
  anchor_type     TEXT NOT NULL,
  anchor_id       TEXT,
  question        TEXT NOT NULL,
  answer          TEXT NOT NULL,
  audio_path      TEXT,
  created_at      INTEGER NOT NULL
);

CREATE TABLE highlight (
  paper_id        TEXT NOT NULL REFERENCES paper_cache(id),
  paragraph_id    TEXT NOT NULL REFERENCES paragraph(id),
  note            TEXT,
  created_at      INTEGER NOT NULL,
  PRIMARY KEY (paper_id, paragraph_id)
);

CREATE VIRTUAL TABLE paragraph_fts USING fts5(
  paragraph_id,
  paper_id,
  text,
  tokenize='unicode61'
);

7.5 Beat refs 校验

输入: beats[], validParagraphIds (Set)
对每个 beat:
  beat.refs = beat.refs.filter(id => validParagraphIds.has(id))
  if beat.refs.isEmpty:
    beat.degraded = true
输出: beats[]

7.6 摘要 JSON

{
  "problem": "…",
  "method": "…",
  "result": "…",
  "limitation": "…"
}

8. 核心流水线

8.1 语音伴读端到端

PDF attachment path
  → findPdfAttachment + computeFileHash
  → [MinerU PdfParser] → paragraphs[] → SQLite + FTS
  → [generateSummary] → lecture.summary
  → [generateBeats] → refs 校验 → SQLite
  → emit lecture:beats_ready ──► 整体讲解可播(文本)
  → [generateParagraphExplanation] × N → SQLite
  → emit lecture:paragraph_ready
  → [Tts.synthesize] × units → mp3 → SQLite
  → emit tts:unit_ready ──► 播放器加载音频
  → 用户提问 → [AskQuestion + RAG] → 文字流 + TTS
  → [GenerateReport] → Zotero Note

8.2 增量备课时序

时间 ──────────────────────────────────────────────►

解析 ████████
摘要      ███
beats         ██████          ← beats_ready~1-2min,可先听文本/TTS排队)
逐段讲解           ████████████████████
TTS(beats)              ████████
TTS(段落)                      █████████████████

8.3 文字对话路径(不变)

Item → findPdfAttachment → PDFWorker.getFullText
     → buildChatMessages → chatStream → ChatView 渲染
     → saveSession (JSON)
     → createChildNote(可选)

9. 状态机

9.1 备课状态(lecture_status

idle → parsing → parsed → summarizing → beats_generating
  → paragraphs_generating (batch loop) → tts_generating → ready
任何阶段 ──error──► failed(可从 failed 或任意已完成阶段 resume

可恢复:每阶段结束写库;重启后读 lecture_status,跳过已完成步骤。

9.2 播放器状态

见 §6.7。pane onDestroy 时持久化 progress,释放 <audio> 引用。


10. 内部事件(EventBus

事件 载荷 订阅者
parse:progress { paperId, percent, message } LecturePane
lecture:beats_ready { paperId, beatCount } LecturePane, Player
lecture:paragraph_ready { paperId, paragraphId } LecturePane
tts:unit_ready { paperId, unitId, audioPath } Player
lecture:failed { paperId, error } LecturePane
qa:chunk { textDelta } QAPanel
qa:complete { qaRecordId, audioPath? } QAPanel

EventBus 实例按 paperId 或全局单例均可;MVP 用模块级 EventTarget 即可。

长时间任务

  • MinerU:子进程(不阻塞 UI
  • LLM / TTSasync fetch + AbortController
  • SQLite 写:单线程队列,避免并发锁

11. 设置与 Preference 设计

11.1 分组结构(preferences.xhtml

ChatPapers 设置
├── 通用 · LLM              ← 两模式共享
│     Provider / Base URL / API Key / Model / Temperature
├── 文字对话                ← 已有
│     System Prompt / Summary Prompt / Answer Language
└── 语音伴读                ← 新增
      TTS Provider / API Key / Voice
      教师风格(简洁 / 详细)
      MinerU 路径(若需)
      数据流向说明(静态文案)

11.2 Prefs 键命名

沿用 extensions.zotero.chatpapers.* 前缀,不新增 addon ID

模式 说明
provider 共享 已有
apiKey 共享 已有
systemPrompt 文字 已有
ttsProvider 语音 新增
ttsApiKey 语音 新增
ttsVoice 语音 新增
lectureTeacherStyle 语音 新增
mineruPath 语音 新增,可选

12. 目标工程结构

development.md 现有结构基础上扩展:

src/
├── hooks.ts
├── modules/
│   ├── ui/
│   │   ├── readerPane.ts          # 注册 Chat + Lecture 两个 section
│   │   ├── chatView.ts            # 文字对话(不动)
│   │   ├── multiChatView.ts       # 文字多 PDF(不动)
│   │   └── prefs.ts
│   ├── pdf/
│   │   ├── extractor.ts           # 共享:附件定位
│   │   ├── context.ts             # 文字专属
│   │   ├── multiContext.ts        # 文字专属
│   │   └── selection.ts           # 共享
│   ├── llm/                       # 共享 client + providers
│   ├── storage/
│   │   └── sessions.ts            # 文字专属
│   ├── zotero/                    # 共享
│   └── lecture/                   # ★ 语音伴读(新建)
│       ├── ui/
│       │   ├── lecturePane.ts
│       │   ├── playerController.ts
│       │   ├── unitList.ts
│       │   └── qaPanel.ts
│       ├── application/
│       │   ├── prepareLecture.ts
│       │   ├── playLecture.ts
│       │   ├── askQuestion.ts
│       │   └── generateReport.ts
│       ├── domain/
│       │   ├── types.ts
│       │   ├── lectureState.ts
│       │   └── playerState.ts
│       └── infrastructure/
│           ├── pdf/
│           │   ├── parser.ts
│           │   └── fileHash.ts
│           ├── tts/
│           │   ├── client.ts
│           │   └── providers.ts
│           ├── storage/
│           │   ├── repository.ts
│           │   ├── schema.ts
│           │   └── audioStorage.ts
│           ├── rag/
│           │   └── index.ts
│           ├── llm/
│           │   ├── lectureLlm.ts
│           │   └── lecturePrompts.ts
│           ├── zotero/
│           │   └── adapter.ts
│           └── events.ts

样式addon/content/chatpapers.css 增加 .chatpapers-lecture-* 前缀类,与文字对话样式并列。

i18naddon/locale/*/chatpapers.ftl 增加 item-section-lecture-* 等键。


13. 外部集成

13.1 LLM(共享)

提供商 协议 文字对话 语音伴读
OpenAI 兼容 HTTPS SSE ✓ 备课 + 问答
Ollama localhost

13.2 TTS(语音专属)

提供商 选型维度
火山 / Azure / MiniMax / ElevenLabs 中文自然度、价格、word timestamps

13.3 MinerU(语音专属)

  • 集成:子进程 CLIstdout JSON
  • 分发:Phase 0 决定「随插件文档安装」vs「用户自装 Python 环境」

13.4 Better BibTeX(共享,可选)

语音报告参考文献卡片复用 citeExport;逻辑已在 Chat 多 PDF 模式验证。


14. 安全与隐私

文字对话 语音伴读
PDF 文件 不上传 不上传
发往 LLM 正文 text(可能截断) 段落 text + 摘要 + 问题
发往 TTS 讲解/回答中文文本
API Key Zotero prefs LLM 共享;TTS 独立 pref
日志 不记录 Key 不记录 Key;段落正文可截断

设置页「数据流向」分组须同时覆盖两种模式。


15. Phase 0 技术验证(Go / No-Go

# 验证项 通过标准 阻塞 MVP
P0-1 第二 Item pane 与 Chat 并存 切换 tab 互不影响
P0-2 MinerU 解析 3 篇论文 段落可用率 ≥ 80%
P0-3 Zotero 9 插件内 SQLite CRUD lecture.db 读写正常
P0-4 <audio> 10 min+ 播放 无泄漏 / 崩溃
P0-5 TTS 中文样本 自然度可接受
P0-6 TTS timestamps 有则记录;无则句子级降级
P0-7 Chat 模式回归 对话 / 总结 / 存笔记不受影响

P0-1、P0-7 验证双模式隔离P0-2 ~ P0-6 验证语音链路


16. 性能与扩展

16.1 长论文策略

页数 策略
≤ 30 全量逐段备课
3080 逐段 lazy:按章节或用户触发
> 80 默认仅整体讲解;逐段需用户确认

16.2 并发控制

  • 备课 LLM:beats 小批量并行 ≤ 3
  • TTS:队列化,beats 优先
  • 同一 paper 备课互斥锁
  • SQLite 写串行队列

16.3 伴读库窗口(P1

libraryWindow.ts 扫描 paper_cache 表 + Zotero Items>5000 items 时仅索引「有过备课记录」的 attachment。


17. 可观测性

类型 内容
结构化日志 mode: "lecture", paperId, stage, durationMs, errorCode
用户可见进度 解析 %、beats n/total、TTS n/total
调试导出 单篇 metrics JSON(开发者菜单,可选)

日志前缀建议 [ChatPapers:Lecture],与 [ChatPapers:Chat] 区分。


18. 风险与对策

风险 对策
语音模块影响文字对话稳定性 依赖隔离 + pane 级 try/catch + P0-7 回归
MinerU 打包复杂 Phase 0 验证;文档化安装
refs 幻觉 校验器 + degraded beat
TTS 无 timestamps 高亮降级链
SQLite 在插件环境不可用 Phase 0 备选 sql.js / JSON 分文件
双 pane 内存占用 onDestroy 释放 audio、取消进行中的 fetch

19. 开放问题

# 问题 影响
1 SQLitebetter-sqlite3 vs sql.js vs 纯 JSON 存储层选型
2 MinerU 分发方式 安装体验
3 问答 TTS:流式 vs 整段合成 AskQuestion 复杂度
4 文字对话是否未来支持 TTS 朗读回复 v1.1+ 可统一 TtsClient
5 Chat 会话 → 语音问答上下文导入 v1.1 产品决策

20. 文档关系

docs/requirements.md              ChatPapers 文字对话需求(不变)
docs/development.md               现有工程与工具链(不变)
docs/newidea/
  ├── PLAN.md / PRD.md            BanDu 原始策划(参考)
  ├── ARCHITECTURE.md             独立插件架构(参考)
  ├── DUAL_MODE_PRD.md            ★ 双模式集成需求
  └── DUAL_MODE_ARCHITECTURE.md   ★ 本文:双模式集成架构

实施顺序:需求看 DUAL_MODE_PRD.md → 架构看本文 → 领域细节参考 ARCHITECTURE.md §4–§6。


21. 文档修订记录

版本 日期 说明
v0.1 2026-08-29 初稿:ChatPapers 双模式集成架构;语音模块命名 lecture/