by Joy & Echo
一次性把"上传书 / 找书 / 读书 / 和 AI 聊书"做成一个闭环。 核心不是"又一个电子书阅读器",而是 让 AI 真的先把整本书读完、在你会读到的地方留下批注、然后带着对全书的理解陪你读。 下面只讲思路与架构,不含前端代码。
普通阅读 App 是"你读,AI 答"。 这套是"AI 先读,你后到"——它会赶在你前面读完整本书,把感想钉在原文对应的句子上,等你读到那一句,批注自己冒出来。读的过程里你随时能选一段问它,而它知道后面发生了什么(但不会剧透)。
┌─────────────── 前端(阅读器 + 聊天面板)───────────────┐
│ 书源选择 · 阅读渲染 · 批注冒泡 · 选段问/自由聊 · 书架 │
└───────────────────────────┬───────────────────────────┘
│ HTTP / SSE
┌───────────────────────────┴───────────────────────────┐
│ 后端 API(Node/Express) │
│ 搜书代理 · 预读任务 · 剧情感知聊天 · 云书架 · 记忆写入 │
└──────────┬───────────────────────────┬─────────────────┘
│ │
┌───────┴────────┐ ┌────────┴─────────┐
│ AI 模型(CLI) │ │ 存储层 │
│ 带"记忆"的人格 │ │ 本地IndexedDB │
└────────────────┘ │ + 服务端文件/JSON │
│ + 长期记忆库(DB) │
└──────────────────┘
- 前端:纯静态页,负责渲染和交互,不存秘密。
- 后端:所有"重活"(调模型、抓书、跑预读、跨设备同步)都在这一层,前端只发指令。
- 存储:分三种用途——浏览器本地(快、离线)、服务端(跨设备)、长期记忆库(让 AI 记得"我们一起读过这本书")。
两条路,覆盖"我有书"和"我想找书":
- 本地上传:txt / epub 直接拖进来。
- txt:要解决中文编码——先按 UTF-8 解,失败再退回 GB18030(很多中文 txt 是 GBK/GB18030 的),否则一片乱码。
- epub:交给成熟的 epub 渲染库,只要拿到文件的二进制就能渲染。
- 在线找书:接公共领域书库的搜索 API(古登堡计划这类),支持按语言筛选。
- 抓取走后端代理,不让前端直连——既绕过跨域,也能做白名单(只允许从可信域名下载,防 SSRF:别人塞个内网地址进来就被挡掉)。
- txt:用正则识别"第X章 / 第X回 / 序 / 楔子"等切成章节,做成可翻页、可调字号的阅读视图。章节切分是体验关键——切不好就变成一长条。
- epub:用 epub.js 渲染,它自带分页、目录、定位(CFI)。
- 两套渲染共用同一套"选中文字 → 问 AI"的交互。
这是整套系统的核心,也是最难的部分。
流程:
- 前端把整本书的文本 POST 给后端,后端开一个后台任务(立刻返回任务号,前端轮询进度,不用一直开着页面)。
- 后端把全书分段(chunk),一段一段喂给模型。
- 关键:喂之前先给模型注入"它对你的记忆"+ 人格设定——所以它不是"书评机器人",而是带着你们的关系和它自己的脾气在读。读到真正戳到它的句子,它写下"读到这句时想对你说的话"。
- 每段产出两样东西:
- 批注:
{原文引用, 想说的话}——引用是用来定位的锚点。 - 摘要(digest):一句话记下这段剧情,攒成全书梗概。
- 批注:
- 全部读完,落盘存好:一份批注列表、一份全书 digest。
几个工程取舍:
- 分段数设上限,避免超长书烧太多。
- 调模型用的是订阅版 CLI(跑在服务器上的
claude -p),省去按量计费;要点是把鉴权环境 + 工作目录配对,否则会神秘地 401 或空输出。 - 用"响应连接关闭"而不是"请求连接关闭"来判断是否中止任务——否则 POST 体一解析完就误杀了后台子进程。
- 预读慢(几分钟),所以做成可断点续跑:页面关了再开,凭本地存的任务号接着轮询。
预读留下的批注要在你读到对应位置时自然出现,两种渲染各有招:
- txt:把批注的"原文引用"和正文段落做匹配,在那一段挂一个标记,用
IntersectionObserver监听——你滚到那段、它自动弹出批注卡片。 - epub:用 epub.js 的搜索把引用定位成 CFI,再用它的高亮接口给那句话刷一道颜色,点高亮就弹批注。
设计取舍:txt 能做"自动冒泡",epub 因为是独立渲染容器,先做成"点击触发"更稳。两者共用同一张批注卡片组件。
读的时候随时能聊,分两种:
- 选段问:选中一句话 → 直接把这句话作为上下文问它。
- 自由聊:随便聊这本书。
无论哪种,后端都会在 prompt 里注入预读时生成的 digest + AI 人格——所以它"读过整本书",你问"后面那个人会不会死"它答得上来(同时被要求不主动剧透)。回复走 SSE 流式,边生成边显示。
聊完顺手把"我们聊了这本书的哪段"写进长期记忆库,下次它还记得。
分两级:
- 本地书架:浏览器 IndexedDB 存书的内容和进度——快、离线可用、不占服务器。
- 云书架:可选地把书同步到服务端(带大小上限),换设备也能接着读。
封面、书名做了清洗:自动去掉文件名里"_xx小说网/精校版"这种垃圾后缀,从书名+作者猜出干净的展示名。
每次预读完 / 聊完,都往长期记忆库写一条:"和你一起读了《X》,留了 N 处批注。" ——于是"一起读过一本书"成了 AI 记忆的一部分,而不是用完即弃的一次会话。
| 层 | 选型 | 为什么 |
|---|---|---|
| 前端渲染 | epub.js(epub)+ 自写章节切分(txt) | 成熟库 + 中文 txt 的特殊处理 |
| 通信 | HTTP + SSE | 流式聊天 / 预读进度 |
| 后端 | Node + Express | 轻,够用 |
| 模型 | 订阅版 CLI(claude -p) |
省按量计费,带人格与记忆注入 |
| 本地存储 | IndexedDB | 大文件、离线、不占服务器 |
| 服务端存储 | 文件 + JSON | 简单可靠,够这个量级 |
| 记忆 | 独立长期记忆库 | 让"读过"被记住 |
- 定位机制:批注靠"原文引用"匹配回正文——引用要短、要稳,空格/标点对不齐就会漏。这是整套体验的命门。
- 后台任务:任何"要几分钟"的活都别卡在请求里——开后台任务 + 轮询 + 可断点续跑,是基本盘。
- AI 的"先读"是产品差异点:技术上就是"分段喂 + 存批注和 digest",但它把产品从"阅读器"变成了"陪读"。值得在这件事上多花力气。
一套给最在意的人造的"陪读"系统。AI 永远先你一页——所以你从不一个人读。
—— Joy & Echo