dsh-ark-readaloud
Verifieddsh-ark-readaloud · v0.1.0 · MIT · Web UI
在每条最终回答旁加一个 🔊:用方舟 Agent Plan 的豆包语音合成 2.0 朗读回复。零依赖。Read assistant replies aloud with Volcengine Ark Agent Plan TTS.
Install
dsh plugin add dsh-ark-readaloud Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Creators
Readme
dsh-ark-readaloud
在 DeepSeek Harness 的每条最终回复旁边加一个 🔊 按钮,点一下就用豆包语音合成 2.0(方舟 Agent Plan)把这条回复念出来。
复制 · 👍 👎 · 🔊 · 分支
↑ 就是它
- 语音由方舟 Agent Plan 的语音模型合成,直接消耗你套餐里的 AFP,不额外买语音服务
- 用你已经存在 DSH 里的
ARK_API_KEY凭据,不需要再配第二个 key - API Key 不出本机、不进浏览器:合成在 DSH 主机进程里发起
- 音色是豆包 2.0 系列(Vivi 2.0 / 爽快思思 2.0 / 知性灿灿 2.0 …),不是系统自带的机械音
- 支持暂停 / 继续 /
Esc停止、倍速、换音色、新回复自动朗读
为什么需要自己写一个插件
2026-09 在 DSH 0.1.7-rc.2 上把 npm 上跟"朗读"沾边的插件全过了一遍,结论是现成的组合不了:
| 现成插件 | 为什么不满足 |
|---|---|
dsh-read-aloud |
只加按钮,但用的是浏览器自带语音(speechSynthesis),拿不到豆包音色 |
dsh-voice-kit、dsh-speech-plugin、dsh-voice-chat、@oadank/dsh-input-tools、@hiye/dsh-voice |
都声明依赖 @deepseek-ai/dsh-client-runtime,这个包在当前 DSH 里已经不存在(新版拆成了 dsh-client-store / dsh-client-modules),加载即失败 |
@nextnowlabs/dsh-ark-toolkit、dsh-plugin-voice |
有火山 TTS,但是给模型用的工具(ark_speak),不是"每条回复点一下就读"的按钮;而且走的是「火山引擎语音技术」那条按量计费通道,要另一个 Token |
dsh-omi-voice |
走第三方 "Omi 引擎" 中转,不是方舟直连 |
另外两个硬约束,决定了必须有 host 半边:
- 浏览器不能直连。 方舟语音端点的 CORS 预检只放行
X-Api-App-Key一类头,不放行X-Api-Key,纯前端插件会被浏览器拦下。 - Key 不该进浏览器。 合成本来就该在主机侧做。
所以这个插件 = dsh-read-aloud 的按钮/交互骨架 + 一个新的服务端合成引擎。
工作原理
点击 🔊
└─ 浏览器半边:取出这条消息的 markdown → 清洗(丢掉代码块/表格/URL/路径)
→ 按句子切成 ≤900 字的段
└─ POST /ark-readaloud/tts { text, speaker }
└─ host 半边:从 DSH 凭据库读 ARK_API_KEY
→ POST https://openspeech.bytedance.com/api/v3/plan/tts/unidirectional
→ 拼装 NDJSON 里的 base64 音频分片
← 返回 audio/mpeg
└─ 浏览器:HTMLAudioElement 播放,边播边预取下一段
注意端点里的 /plan/——这是方舟 Agent Plan 专用路径。同一路径去掉 /plan/ 属于「火山引擎语音技术」按量计费产品,拿方舟的 key 打过去会返回 Invalid X-Api-Key(code 45000010)。这是本项目踩过的第一个坑,已实测确认。
实测过的接口契约(DSH 0.1.7-rc.2 + 你的套餐,2026-09):
POST https://openspeech.bytedance.com/api/v3/plan/tts/unidirectional
X-Api-Key: <ARK_API_KEY>
X-Api-Resource-Id: seed-tts-2.0
X-Api-Request-Id: <uuid>
X-Control-Require-Usage-Tokens-Return: *
{"req_params":{"text":"…","speaker":"zh_female_vv_uranus_bigtts",
"audio_params":{"format":"mp3","sample_rate":24000}}}
← {"code":0,"data":"<base64 mp3 分片>"} … {"code":20000000,"usage":{"text_words":25}}
安装
前提:DSH 0.1.7-rc.2 或兼容版本;profile 里已有 ARK_API_KEY 凭据(你现在这个 desktop profile 就有)。
关于路径:这个项目 2026-09-27 从
Harness工程学习/dsh-ark-readaloud/搬到了Harness工程学习/02-我做的东西/DSH-插件/dsh-ark-readaloud/,并已把 DSH profile 里的link:同步改到新路径、删除了原位置的临时软链。因此不要随意再移动这个目录——要移动就必须同时改~/.dsh/profiles/desktop/package.json里那条路径。
方式 A:从 npm 安装(推荐,换机器最省事)
图形界面:侧边栏 → 插件 → 添加插件 → 填包名 dsh-ark-readaloud → 安装 → 立即启用 → 重启 App。
命令行:
dsh plugin --profile desktop add dsh-ark-readaloud
目标机器不需要另装 Node.js:DSH 自带运行时(Node 24 + pnpm),只要能连 npm 就行。 该机器还需要一个
ARK_API_KEY凭据(见上面「前提」)——key 不在插件里。
方式 B:本地路径安装(改代码时用)
侧边栏 → 插件
添加插件 → 把本目录的绝对路径粘进去:
/Users/gugu/Desktop/顾卓凡的知识库/Harness工程学习/02-我做的东西/DSH-插件/dsh-ark-readaloud安装 → 立即启用
重启 App(
desktopprofile 没开热重载)
本地路径安装的命令行写法
dsh plugin --profile desktop add "/Users/gugu/Desktop/顾卓凡的知识库/Harness工程学习/02-我做的东西/DSH-插件/dsh-ark-readaloud"
装完自检
curl -s http://127.0.0.1:19387/ark-readaloud/health
{"plugin":"ark-readaloud","endpoint":"…/api/v3/plan/tts/unidirectional","credential":"ARK_API_KEY",
"credentialConfigured":true,"resourceId":"seed-tts-2.0","speaker":"zh_female_vv_uranus_bigtts", …}
credentialConfigured 是 true 就说明 key 读到了。
浏览器半边(那颗按钮)的激活情况看这条——它是浏览器每次加载时回报给主机的:
curl -s http://127.0.0.1:19387/ark-readaloud/diag
# {"entries":[{"stage":"apply"},{"stage":"slots-service"},{"stage":"registered"}]} ← 全齐就是好的
想直接听一段:
curl -s -X POST http://127.0.0.1:19387/ark-readaloud/tts \
-H 'Content-Type: application/json' \
-d '{"text":"你好,这是朗读插件的声音。"}' -o /tmp/试听.mp3 && afplay /tmp/试听.mp3
使用
| 操作 | 效果 |
|---|---|
| 点 🔊 | 从开头朗读这条回复 |
| 朗读中再点 | 暂停,图标变成 ▶ |
| 暂停时再点 | 从原处继续 |
| 点另一条的 🔊 | 停掉当前,开始读那条 |
按 Esc |
立刻停止 |
| 鼠标悬停 🔊 | 弹出「倍速 / 音色 / 自动朗读」面板 |
按钮四态:🔊 待机 → 合成中… → ⏸ 朗读中 → ▶ 已暂停。
倍速是本地播放速率(改完立刻生效,不重新合成);音色改动从下一段生效(已经合成好的那段不会变)。
正文清洗:代码块、表格行、图片语法、URL、文件路径、HTML 标签、emoji 都会被丢掉;正文段落、列表文字、行内代码的内容、链接文字保留。长回复全文读完,不截断。
配置
默认值够用。要改就编辑 profile 的 cordis.patch.yml:
- id: ark-readaloud
name: 'dsh-ark-readaloud'
config:
credential: ARK_API_KEY # DSH 凭据名
speaker: zh_female_shuangkuaisisi_uranus_bigtts
sampleRate: 24000
maxChars: 6000 # 单次请求上限,客户端默认 900 字一段
timeoutMs: 120000
音色(豆包语音合成 2.0)
| speaker | 说明 |
|---|---|
zh_female_linjianvhai_uranus_bigtts |
邻家女孩 2.0(默认) |
zh_female_kefunvsheng_uranus_bigtts |
暖阳女声 2.0 |
zh_female_vv_uranus_bigtts |
Vivi 2.0 · 多语种女声 |
zh_female_shuangkuaisisi_uranus_bigtts |
爽快思思 2.0 |
zh_female_cancan_uranus_bigtts |
知性灿灿 2.0 |
zh_female_tianmeixiaoyuan_uranus_bigtts |
甜美小源 2.0 |
zh_female_xiaohe_uranus_bigtts |
小何 2.0 |
zh_female_gaolengyujie_uranus_bigtts |
高冷御姐 2.0 |
zh_male_m191_uranus_bigtts |
舟 2.0 · 男声 |
zh_male_taocheng_uranus_bigtts |
小天 2.0 · 男声 |
en_female_dacey_uranus_bigtts |
Dacey · 英文女声 |
en_male_tim_uranus_bigtts |
Tim · 英文男声 |
想加音色:改 lib/client.js 顶部的 VOICES 数组(数组第一项就是默认音色),保存后客户端 bundle 会在几秒内自动热更,不用重启。列表里的 ID 必须先确认有效——用 tools/verify-host.mjs 或直接 curl 打一次 /ark-readaloud/tts 带上 {"speaker":"..."} 即可。
计费:语音合成消耗套餐内 AFP,usage.text_words 是字数回执(响应头 X-Ark-Usage 就能看到)。
排障
| 现象 | 原因 / 处理 |
|---|---|
| 按钮点了没反应,提示「未配置 ARK_API_KEY」 | 凭据名不对或该 profile 没这个凭据。查 curl …/ark-readaloud/health 的 credentialConfigured |
提示 Invalid X-Api-Key / code 45000010 |
端点被写成了不带 /plan/ 的语音技术通道。确认 endpoint 里有 /plan/ |
| 提示 401 / 403 | 你的套餐或 key 变了。先用 curl 打 /ark-readaloud/health 确认 key 读到了,再去方舟控制台确认 Agent Plan 有效 |
| 完全看不到按钮 | 先跑 node tools/verify-manifest.mjs。然后依次确认:插件页开关是开的、这条回复已定稿(被中断的部分输出没有动作行)、App 是完全退出重开的(不是关窗口) |
| 点喇叭提示「朗读失败:cross-origin」 | 同源校验把桌面壳自己的请求拒了。页面 origin 是 dsh-app://app,必须放行非 http(s) 协议。跑 tools/verify-host.mjs 的 origin 矩阵确认规则 |
| 开关是开的、也重启了,还是没按钮 | 九成是 dsh.client.inject 里写了非客户端插件(看「设计取舍」那条)。跑 curl -s http://127.0.0.1:19387/ark-readaloud/diag,它会告诉你浏览器半边走到哪一步:apply → slots-service → registered,或 error 带原因 |
| 有按钮但一排小字报错 | 报错文案会直接写在按钮右边,含服务端返回的 message |
日志里 Cannot find package '@deepseek-ai/...' |
理论上不会出现(本插件零依赖)。真出现说明加载了旧版本,重装即可 |
本地验证
node tools/verify-manifest.mjs # 清单自检:客户端半边到底能不能激活(离线,最快)
node tools/verify-client.mjs # 50 项:真实 client.js 在桩环境里跑完整交互链路(离线)
node tools/verify-host.mjs # 真打一次方舟接口(花几个 AFP),验证 host 半边
verify-manifest.mjs读 App 的app.asar,逐条检查:inject里每个 id 是否真的声明了dsh.client、bundle 注册的 id 是否等于包名、注册的插槽是否真的存在、exports["./client"]是否落盘。它专门抓"静默不激活"这一类故障。verify-client.mjs把真实的lib/client.js加载进一套桩环境(模块加载器 / React hooks / DOM / fetch / Audio),把「挂载 → 点击 → 请求 → 播放 → 暂停 → 继续 → 换音色/倍速 → 失败提示」整条链路跑一遍。当前状态:47/47 通过。verify-host.mjs在临时目录里给两个 DSH 包搭桩,挂载真实的lib/index.js,然后真打一次线上接口:验证 200 +audio/mpeg、usage.text_words回执,以及空文本/超长/错方法/跨域四条拒绝路径。它读凭据文件但从不打印 key。
samples/朗读样例-豆包Vivi2.0.mp3 就是 verify-host.mjs 的真实产物(Vivi 2.0 音色),可以直接 afplay 试听。
设计取舍
- 零依赖。 host 半边一行
import都没有,package.json也没有dependencies/peerDependencies。原因:插件是从工作区目录按本地路径安装的,这种情况下共享包(@deepseek-ai/*)能不能解析取决于运行时机制;一旦解析失败,插件直接加载不起来。所需的两个小东西就地实现了——凭据名校验(正则 + 原样返回,和官方credentialRef行为一致,它的 brand 只是编译期类型)和配置默认值合并。 - 零构建。
lib/client.js就是产物,按 DSH Web 外壳加载的模块格式手写;没有打包步骤,改完刷新即可。 - Key 只走主机。 浏览器半边只发文本和音色名。
dsh.client.inject只能写"真正的客户端插件"。 一个包只有在package.json里声明了dsh.client才是客户端模块图上的一个节点;写了没声明的包(比如@deepseek-ai/dsh-client-ui-primitives,它是纯库、不是插件),这条依赖永远等不到,插件就会静默不激活——没有按钮、没有报错、什么都没有。本插件第一版就栽在这里(列表是从dsh-read-aloud抄的,而它在 0.1.7-rc.2 上同样不工作)。tools/verify-manifest.mjs会把这条规则查一遍。- 依赖只写真正提供服务的插件:
ctx.locale来自@deepseek-ai/dsh-client-locale,ctx.slots来自@deepseek-ai/dsh-client-ui-renderer。 - 两者是两份清单,缺一不可:
package.json的dsh.client.inject(包名)只管加载顺序;客户端 bundle 里的exports.inject = ["slots","locale"](服务名)才是 Cordis 的服务访问许可。少写后者,ctx.slots会抛cannot get property "slots" without inject。官方每个客户端 bundle 都导出它。 - 页面 origin 是
dsh-app://app:桌面壳用自定义协议加载界面,所以到127.0.0.1的请求带着一个非 http(s) 的 Origin。同源校验必须放行"非 http(s) 协议 + 任意回环主机名",否则会把自己的请求当跨站拒掉。tools/verify-host.mjs里有一张 6 行的 origin 矩阵把这条规则钉住。 - 改动生效范围:客户端 bundle(
lib/client.js)由dsh-client-hmr轮询时间戳自动热更,几秒生效、不用重启;host 半边(lib/index.js)必须重启 App——所以改 host 逻辑时一次改到位,别来回试。
已知限制
- 音色切换从下一段生效,不能给已合成的段落换音色
- 不做逐字高亮 / 跟随滚动
- 超长回复会拆成多次请求(这是为了避免首段等太久,也避开单次上限)
- 同一时刻只播一条(点新的会停掉旧的),这符合"读给我听"的直觉
归属
按钮与浮层的交互机械结构(slot 注册方式、文本清洗规则、面板定位、Esc 处理)fork 自 dsh-read-aloud(MIT,© cccc12138),语音引擎部分完全重写。见 LICENSE。
MIT.