dsh-novel-reader
ukkyo1/dsh-novel-reader
Reads local txt novels inside the chat dialog: chapters are detected automatically, the novel text is paginated in the assistant thinking block, a next-chapter button continues in the same reply, progress is remembered, and visible replies are fixed summaries.
설치
dsh plugin --profile web add github:ukkyo1/dsh-novel-readerREADME
小说阅读插件(dsh-novel-reader)
把本地 txt 小说变成 DSH 里的「对话」:在小说目录里放 txt,在输入框旁边选中一本,之后每发一条消息,助手就流式读出一章正文。它看起来和正常 AI 回复一样(正文藏在「思考」里),但内容全部来自你自己的小说文件,不联网、不调用模型。
适合摸鱼
使用示例
配置目录和小说别名
选择小说
阅读小说
安装
本插件是一个标准的 DSH bundle(package.json 里声明 dsh.bundle.patch 和 dsh.client,无需构建、无第三方运行时依赖)。
- 从 GitHub 安装:
git clone https://github.com/ukkyo1/dsh-novel-reader.git,然后在插件管理器(设置 → 插件 → 添加插件)里选本地目录安装;插件管理器如果支持 Git 地址,也可以直接填仓库地址。 - 本地目录安装:插件管理器选择从本地目录安装,目录填仓库根目录,安装并启用。
- 离线包:直接下载 Release 里的 tgz 作为本地包安装(取决于插件管理器版本是否支持 tgz);或在仓库根执行
pnpm pack自己打一个。 - 启用后刷新一次 Web GUI 页面(
Ctrl+R),输入框旁的「阅读模式」按钮就会出现;新会话也可以直接用。
升级时在插件管理器里禁用再启用本插件(或重启 DSH),并刷新页面以加载新的浏览器半代码。
快速开始
-
确认小说目录
默认目录是 DSH 主目录下的
novels:<DSH_HOME>\novels本机通常是
C:\Users\<你的用户名>\.dsh\novels(DSH_HOME由 DSH 实际安装位置决定,不写死用户名)。目录不存在时,插件第一次加载会自动创建;也可以在 设置 → 小说阅读 里直接改(支持手动输入或用「选择文件夹…」)。 -
把 txt 放进目录
直接复制粘贴
.txt文件进去即可。建议保存为 UTF-8 编码,避免中文乱码。目录里放多少本都行,子目录不会被扫描。 -
在会话里选中小说
打开任意会话,看输入框底部那一行工具栏:在模型选择器左边有一个小小的「阅读模式」按钮。点它,会弹出小说列表(和模型选择框长得差不多),每一行是书名和阅读进度(有章节的书显示「第 3/120 章」)。点一本,本会话就进入阅读模式;按钮上会显示「书名 · 第 3/120 章」。
-
发消息继续读
随便发一句话(「继续」「下一段」都行,内容本身不影响结果),助手就会开始"思考"并且流式输出下一章的正文。默认模式下:
- 小说正文放在**「思考」折叠块里:点一下「思考」展开后是左右点击翻页**的阅读器,不再是一大段需要滚动的文字。整章会自动按页面高度切页,点页面右侧翻到下一页、左侧上一页,新页从侧边覆盖旧页(没有滚动动画);页脚显示章节和「第 x/y 页」。同一段正文的翻页位置会记住,收起再展开仍停在原页。
- 读到本章最后一页时,下一页按钮会变成「下一章 ›」:点一下就在同一个阅读页里载入下一章(进度、章节标签随之更新),不需要再发消息、重新展开思考;读完最后一章显示「全书完」。
- 阅读页会按宿主思考区域的最大高度自动收缩页高,避免出现「每页都要滚动才能看全、按钮被顶到下面」的情况。字号、行距、字间距、段间距、页高、页边距、最大宽度、字体都可以在 设置 → 小说阅读 → 阅读样式 里调,改完立即生效(也可以在配置里设默认值)。
- 可见的助手回复是一段固定的结构化总结:默认是一份较长的报告(处理范围 / 处理结果 / 状态表 / 整体进度 / 说明 / 下一步),占据正常 AI 回答应有的页面高度,看起来就是一次带思考过程的完整答复。总结里的整体进度会随阅读推进变化。
- 翻页正文的字号、行距、字间距、段间距、页高、页边距、最大宽度、字体都可以在 设置 → 小说阅读 → 阅读样式 里调,改完立即生效(也可以在配置里设默认值)。
- 生成速度在 设置 → 小说阅读 → 生成速度 里三选一:瞬间生成 / 指定整段秒数 / 按字每秒。正文页面是固定页高、超出部分自动分页,不会出现滚动条,翻页按钮就在页面下方。
章节自动识别:插件会扫描 txt 里单独成行的章节标题——
第一章 …、第1章 …、第 12 节、第 3 回、Chapter 1,以及楔子/序章/引子/番外/尾声/后记等。读到标题就切一章,一条消息读一整章;第一个标题之前的内容(书名页、作者的话)并入第 1 章。没有识别到任何章节标题时,自动退回原来的「按字数切段」模式(chunkChars生效)。数字跳章:在阅读模式下,直接发一个数字(
1、2、3…)就会跳到对应序号的章节,并立刻把那一章读出来。序号是按标题出现顺序编的(楔子/序章算 1 号);发超出范围的数字会提示总章数,进度不变。输入框旁边的面板底部也有一行提示。不想用这个功能可以关掉numberedJump。回复走的是 DSH 正常的助手消息渲染管线:字体、行高、段落间距与真实模型回复完全一致。插件会把原文每一行整理成一个 Markdown 自然段(段间空行),并转义正文里的
*、反引号、#、-等符号,避免被渲染成标题/列表;同时按真实模型的节奏逐字出字(首字前有一小段停顿)。想让正文直接就是可见回复(不放进思考块),把配置里的
display改成answer即可。当前界面的「思考」折叠时机是「下次输入时」(DSH 全局设置
ui-chat.collapseTiming: next-input),所以回复刚结束时思考块是展开的,下一次发消息后才会收起。想让它一完成就折叠,可在cordis.patch.yml的ui-chat那行把collapseTiming改成completion——这是全局设置,会影响真实模型的思考显示。 -
退出阅读模式
再点一次那个按钮,在弹出面板最下面选「退出阅读模式」。退出后本会话恢复正常 AI 对话,之前的聊天记录不受影响。
如果目录还是空的,面板里会显示「固定目录里还没有 txt 文件」和一个「创建示例小说」按钮,点一下就会把插件自带的两篇示例写进目录,方便先试手感。
设置页
打开「设置」→「小说阅读」,可以集中管理所有小说:
- 小说目录:页面顶部是可编辑的目录输入框,支持手动填绝对路径或点「选择文件夹…」;「应用」后立即切换并重新扫描,「恢复默认」回到
<DSH_HOME>\novels。进度和显示名按目录分别保存(存在该目录下的.reader-state.json),换目录相当于换一个书库,旧目录的进度不会带过来。- 如果
cordis.patch.yml里显式写了directory,目录会被配置固定,设置页只显示不可编辑(会有一行提示)。 - 设置页选择的目录记在
<DSH_HOME>\novel-reader.settings.json,重启后仍然生效。
- 如果
- 显示名:每本小说都可以在输入框里改成任意名字(这个名字就是按钮和列表里显示的书名),点「保存」生效。改显示名不会改动磁盘上的文件名,也不会影响进度。
- 阅读样式:页面下半部分可以调翻页正文字号、行距、字间距、段间距、页高、页边距、最大宽度和字体(CSS
font-family),以及翻页效果(无动画 / 快速覆盖 140ms / 标准覆盖 280ms),保存后立即应用到所有阅读页面;「恢复默认样式」回到cordis.patch.yml里的默认值。 - 生成速度:三选一——瞬间生成(不逐字出)、指定整段秒数(按「正文 + 总结」的总片段数均摊)、按字/秒。点选/改数值后立即保存生效;「恢复默认速度」回到配置默认值(默认是瞬间生成)。
- 进度:有章节的书显示「第 3/120 章 · 已读 2% · 420 / 123456 字」,没有章节的书只显示百分比和字数。
- 重置进度:把这本书的进度归零,下次从第一段开始读。
- 创建示例小说:目录为空时会出现这个按钮,把插件自带的
samples/sample-story-1.txt、samples/sample-story-2.txt复制进固定目录(同名文件已存在则跳过)。
进度是怎么记住的
- 进度按小说保存,最小单位是「字符偏移」,也就是读到了全书第几个字符,而不是第几段或第几页。
- 章节模式下,正常读完一章后进度停在下一章开头;数字跳章会直接把进度设到目标章节开头(再倒退跳也可以)。
- 进度记录与具体会话无关:在 A 会话读到 40%,换到 B 会话选同一本书,也会从 40% 继续;反过来,两个会话同时读同一本书,进度是共享的、后读的会推进它。
- 这些数据保存在小说目录下的隐藏文件
.reader-state.json里(显示名、每本进度、会话与小说的对应关系都在里面)。删掉它等于重置全部进度和改名。 - 每读完一段就写一次磁盘;中途点「停止」时,已经显示出来的那部分也算读过了,下次会从断点接着往下读。
- 刷新页面、重启 DSH 都不会丢进度。整本书读完之后,列表里会显示「已读完」,再发消息会提示已经读完。
- 想重新读一遍,用设置页的「重置进度」。
配置(高级)
插件在 cordis.patch.yml(DSH 配置目录下的补丁层)里对应一行配置,可以调三个参数。改完后让插件重新加载(禁用/启用本插件,或重启 DSH)生效:
- id: novel-reader
name: dsh-novel-reader
config:
directory: "" # 小说目录;留空 = <DSH_HOME>/novels(设置页可改)
chunkChars: 420 # 无章节标题时的切段字数(80 - 4000)
streamMode: instant # instant=瞬间;duration=整段固定秒数;speed=固定字/秒
streamSeconds: 4 # duration 模式的秒数(0.5 - 60)
streamCharsPerSecond: 200 # speed 模式的速度(字/秒,10 - 5000)
firstTokenDelayMs: 350 # 首字之前的停顿,毫秒(0 - 3000);instant 模式忽略
display: thinking # thinking = 正文放思考里、可见固定总结;answer = 正文就是可见回复
summaryTemplate: "" # 可见总结模板;留空用内置长报告,支持 {title} {percent} {offset} {total}
chapters: true # 自动识别章节,每条消息读一整章
numberedJump: true # 纯数字消息跳转到对应序号的章节
maxChapterChars: 0 # 单条回复最多输出多少章节字数;0 = 不限
sessionTitle: "" # 会话标题;留空用内置普通工作型标题池(不出现书名)
readingFontSize: 15 # 翻页正文字号(px)
readingLineHeight: 1.8 # 行距(倍)
readingLetterSpacing: 0 # 字间距(px)
readingParagraphSpacing: 0.8 # 段间距(行)
readingPageHeight: 440 # 每页正文区高度(px)
readingPagePadding: 18 # 页面内边距(px)
readingPageMaxWidth: 0 # 页面最大宽度(px,0=自适应)
readingFontFamily: "" # CSS font-family,留空继承
readingFlip: fast # 翻页效果:none / fast(140ms) / normal(280ms)
| 配置项 | 默认值 | 说明 |
|---|---|---|
directory | <DSH_HOME>\novels(即 C:\Users\<用户名>\.dsh\novels) | 小说目录。留空(推荐)时用默认目录,并允许在设置页随时修改;填绝对路径(或相对 DSH_HOME 的路径)则目录被配置固定,设置页不能改。 |
chunkChars | 420 | 没有识别到章节标题时,每次回复「一段」的字数基准。取值范围 80 - 4000。 |
streamMode | instant | 出字方式:instant 瞬间生成;duration 整条回复(正文+总结)用固定秒数出完;speed 按固定字/秒出。 |
streamSeconds | 4 | duration 模式的秒数(0.5 - 60)。 |
streamCharsPerSecond | 200 | speed 模式的速度(字/秒,10 - 5000)。 |
firstTokenDelayMs | 350 | 开始出字之前的停顿,模拟真实模型的「思考时间」。instant 模式会忽略它。取值范围 0 - 3000。 |
display | thinking | thinking:小说正文进「思考」折叠块,可见助手回复是固定总结;answer:小说正文直接作为可见回复。 |
summaryTemplate | 空(内置较长的结构化报告) | 仅在 display: thinking 时生效。自定义可见总结的 Markdown 文案,支持 {title} {percent} {offset} {total} 占位符;留空用内置长报告。 |
chapters | true | 自动识别章节标题、每条消息读一整章。设为 false 则退回按 chunkChars 切段。 |
numberedJump | true | 阅读模式下,用户只发一个数字时跳到对应序号的章节并直接读它。 |
maxChapterChars | 0 | 单条回复最多输出多少章节字数,用于防超长章节一次读太久;0 = 不限。被截断时下一次回复继续同一章。 |
sessionTitle | 空 | 阅读模式会话的标题。留空时从内置的普通工作型标题池(「内容整理」「继续处理」「分段梳理」等)按会话 id 稳定选一个,不会出现书名。 |
readingFontSize | 15 | 翻页正文的字号(px,12-40)。 |
readingLineHeight | 1.8 | 翻页正文的行距倍数(1.2-3)。 |
readingLetterSpacing | 0 | 字间距(px,0-8)。 |
readingParagraphSpacing | 0.8 | 段间距(以行高为单位,0-3)。 |
readingPageHeight | 440 | 每页正文区高度(px,240-1200)。翻页按这个高度切分。 |
readingPagePadding | 18 | 页面内边距(px,0-64)。 |
readingPageMaxWidth | 0 | 页面最大宽度(px,0 = 跟随正文宽度)。 |
readingFontFamily | 空 | CSS font-family,例如 "Noto Serif SC", serif;留空继承界面字体。 |
readingFlip | fast | 翻页效果:none 无动画直接切换、fast 140ms 快速覆盖、normal 280ms 标准覆盖。 |
关于按字数切段:只有没有识别到章节标题的书才会用这个模式。插件先取
chunkChars个字,然后尽量对齐到原文的自然段换行处收尾(最多再多看六成篇幅),所以实际一段大约是 420 到 670 字;找不到合适的换行时会按字数硬切。
会话标题
阅读模式的会话标题不再用《书名》(那样太怪),而是从一组普通工作型标题里按会话 id 稳定选一个:「内容整理」「继续处理」「分段梳理」「续接推进」「长文处理」「整体梳理」。想固定成某个标题就配 sessionTitle。
注意:会话标题只在会话第一次生成回复时写一次。之前已经生成过《sample-story-1》这类标题的旧会话不会自动改,手动重命名一下那个会话,或者新开一个会话即可。
已知限制
- 一次只扫描一个目录:可以在设置页切换目录,但不支持同时挂多个目录、不扫描子目录。
- 只支持 .txt:epub、mobi、docx、pdf 概不支持,也不支持压缩包。txt 请用 UTF-8 编码保存。
- 目录可以换,但进度不迁移:设置页换目录后按目录重新开始(每个目录有自己的
.reader-state.json)。想保留旧目录的进度就换回去。 - 只在 Web GUI 里可用:按钮和设置页都是 Web GUI 的界面部分,没有对应的命令行入口。
- 章节识别是启发式的:只认「单独成行、长度不超过 80 字、不含句号」的标题行,比如
第一章 出发、第1章、第 12 节、Chapter 3、楔子、番外 …。标题和正文挤在同一行、或用了非常规写法时可能识别不到,此时会自动退回按字数切段;不会报错。 - 数字跳章的序号按标题出现顺序编:如果书里有楔子/序章,它们也算一个序号(例如楔子=1、第一章=2)。发数字前可以先看设置页/按钮上的「第 X/Y 章」。
- 阅读模式下没有真正的模型回答:只要会话处于阅读模式,用户发出的消息、会话标题、上下文压缩摘要全部由插件接管,回复内容只来自小说正文。此时不要期待模型真的回答了你的问题,也不要把它当检索工具——需要正常提问时,请退出阅读模式,或者另开一个会话。
- 翻页按页面高度自动切分:同一段正文会按你设置的页高/字号/行距切成若干页;如果某一段特别长(超过一整页),会优先在句号、问号、分号处断开,不会出现滚动条。
- 极长的单本 txt(几十 MB)首次扫描和计算字符数会稍慢,属正常现象。
故障排查
| 现象 | 原因与处理 |
|---|---|
| 小说目录不存在 | 不用手动创建,插件在第一次加载时会自动创建空目录。如果创建失败(权限问题),请手动新建该目录,或把 directory 指到一个可写目录。 |
| 列表里没有刚放进去的书 | 点设置页的「刷新列表」(或重新打开面板)重新扫描;确认扩展名是 .txt、文件放在目录根下而不是子目录里。 |
| 换了目录之后看不到旧进度 | 进度按目录保存,换目录等于换书库;换回原目录即可看到原来的进度。 |
| 设置页目录是灰的、提示被配置固定 | cordis.patch.yml 的 directory 写了路径;把那一项留空并重载插件后,就能在设置页改。 |
| 填了新目录点「应用」报错 | 目录不可创建或不可读;换一个可写目录,或检查路径拼写(支持绝对路径,相对路径按 DSH_HOME 解析)。 |
| 把文件改名之后 | 改名会被视为一本新小说,旧条目的进度不再跟随,新条目从 0% 开始。想找回原来的进度,把文件名改回去即可。 |
| 删除文件之后 | 对应的条目会从小说列表里消失;如果那个会话正在读这本书,会自动退出阅读模式,回到正常 AI。 |
| 一不小心读完了 / 想从头再读 | 设置页点「重置进度」,或在面板里换另一本书。 |
| 列表里出现没见过的隐藏文件 | 插件只显示 .txt,并跳过 .reader-state.json 这类以点开头的文件,所以正常不会看到。 |
| 中文显示成乱码 | 用记事本或 VS Code 把 txt 另存为 UTF-8(无 BOM 也可以)。 |
| 按钮点了没反应 / 显示「读取失败」 | 插件宿主尚未就绪或目录不可读,稍等几秒再点;仍失败就重启 DSH 并查看主进程日志。 |
| 回复太慢或太快 | 到设置页「生成速度」切换:瞬间生成 / 指定秒数 / 字每秒;也可以在配置里改 streamMode / streamSeconds / streamCharsPerSecond。每段太长或太短就调 chunkChars(仅在无章节时生效)。 |
| 章节没识别出来 / 一直是按字数在切 | 确认章节标题单独占一行(如 第一章 出发),不要和正文写在同一行;识别到后设置页/按钮会显示「第 X/Y 章」。 |
| 发数字没反应或提示总章数 | 只有识别到章节的书才支持数字跳章;序号范围是 1 到总章数;想关闭就设 numberedJump: false。 |
| 想退出阅读模式却找不到 | 点输入框旁的按钮,在弹出面板底部选「退出阅读模式」;英文界面里是最下面那一项。 |
开发与测试
npm run check:对宿主/浏览器两半做语法检查。npm test:宿主半自动化测试(用假 ctx 装载index.js,覆盖目录扫描/切换、阅读样式、生成速度、章节检测、整章阅读、数字跳章、切段、流式、中断续读、标题、压缩、改名、重置、持久化、RPC envelope)。node test/chapter-check.mjs <某本小说.txt 的绝对路径>:拿真实 txt 验证章节检测、整章阅读、数字跳章;脚本会复制到临时目录,不改动原文件。
附带文件
samples/sample-story-1.txt:《老邮局的值夜人》,悬疑短篇,14 段(无章节标题,按字数模式)。samples/sample-story-2.txt:《潮汐图书馆》,奇幻短篇,15 段(无章节标题,按字数模式)。samples/sample-story-3-chapters.txt:《渡口旧事》,4 章(有章节标题,可直接试「整章阅读」和「发 1/2/3/4 跳章」)。
两篇均为原创示例文本,用来验证「一段一段读」的体验(它们没有章节标题,所以走按字数模式),可直接当小说读,也可以删掉换成自己的书。