dsh-ark-readaloud
Đã xác minhdsh-ark-readaloud · v0.1.0 · MIT · Giao diện web
在每条最终回答旁加一个 🔊:用方舟 Agent Plan 的豆包语音合成 2.0 朗读回复。零依赖。Read assistant replies aloud with Volcengine Ark Agent Plan TTS.
Cài đặt
dsh plugin add dsh-ark-readaloud Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.
Mã nguồn
Phát hành lên npm mà không có repository công khai. Hãy kiểm tra nội dung package trước khi cài.
Thẻ
Tác giả
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.