- Главная
- Плагины
- Зрение, голос и мультимодальность
- dsh-go-local-voice
dsh-go-local-voice
27synthesis/dsh-go-local-voice
Text-to-speech for the dsh-go native phone UI: long-press an assistant reply to read it aloud through Xiaomi MiMo TTS or ByteDance Doubao Seed TTS. The reply is segmented and pre-synthesized in the background after each turn, so one tap hands the app a single continuous MP3 instead of one short chunk per tap; playback has a stable lease so stop works, a status strip and progress bar give live feedback, and audio can be exported to phone storage. Keys stay on the device (0600) and are never rendered, committed, or sent to the model.
Установка
dsh plugin --profile web add github:27synthesis/dsh-go-local-voiceREADME
dsh-go-local-voice · 鲸鲸朗读

English · 中文
面向 dsh-go 手机 App 原生界面的 TTS 插件(不是网页版)
给 DeepSeek Harness / dsh-go 手机 App 原生界面用的语音朗读插件: 长按任意一条回复 →「读出来」,用小米 MiMo TTS(或字节 豆包 Seed TTS)合成,直接在 App 里播放。
为什么需要它:DSH 生态里的 TTS 插件大多只跑在 Harness 网页版——它们的声音靠浏览器的 Web Audio 播, Flutter 原生界面里根本不会触发。这个插件走的是 dsh-go 桥接的
audio能力,所以能真的出声。
功能
- 长按回复 → 读出来;也可以「存成音频」导出到手机存储(MP3)
- 小米 MiMo:
mimo-v2.5-tts内置 8 个音色(冰糖 / 茉莉 / 苏打 / 白桦 / Mia / Chloe / Milo / Dean),可加语音指令(语气、方言、情绪) - 豆包 Seed TTS:
seed-tts-2.0/ 声音复刻seed-icl-2.0(走 HTTP v3 单向流) - 长文一次念完:回合结束后台分段预取,点一次交出去的是一整段音频,不是"点一次念 120 字"
- 可停止:
停止按钮真的停得住(固定的播放实例 + 稳定的 lease) - 实时状态:输入框上方状态条显示
正在合成语音…/朗读中 0:12 / 1:40;面板里有进度条 - 音频历史:导出过的文件在面板里列出来,点一下复制路径
- Markdown 清洗:代码块、链接、表格、行内代码都不会被念出来(链接读作"链接")
安装
插件目录放在 $DSH_HOME/dsh-go/plugins/ 下(DSH_HOME 默认 ~/.dsh):
git clone https://github.com/27Synthesis/dsh-go-local-voice.git \
"$HOME/.dsh/dsh-go/plugins/local-voice"
dsh plugin --profile web add "$HOME/.dsh/dsh-go/plugins/local-voice"
装完在 App 里重启一次引擎(设置 → 引擎 → 重启引擎),然后:
- 长按助手消息 → 菜单里出现「读出来」「存成音频」
- 输入框上方「插件」胶囊 →「鲸鲸朗读」
卸载:dsh plugin --profile web remove local-voice,再删目录。
配置
第一次用先在主面板里点「打开设置」,填 API Key:
| 项 | 说明 |
|---|---|
| 小米 Key | 在 小米 MiMo 控制台 申请,形如 sk-... |
| 豆包 Key | 在豆包语音控制台申请(标准音色与 Agent Plan 都支持) |
| 豆包音色 ID | 形如 zh_female_...;用声音复刻时填复刻音色 ID |
| 每段字数 | 默认 140。约 0.08 秒/字,140 字≈11 秒,稳在单次 30 秒的生产预算内 |
| 自动预取 | 默认开。回复一结束就在后台把整条合成好,之后点「读出来」几乎秒播 |
Key 只存在本机 data/settings.json(权限 0600),不会进仓库、不会出现在界面里、不会发给模型。
data/ 已在 .gitignore 中。
它是怎么做到的(给想改代码的人)
dsh-go 原生插件的桥接协议(v1.2)有三条硬约束决定了实现方式:
media路由必须在 30 秒内产出字节(CAP_LIMITS.mediaMs)——这是生产预算,不是音频时长上限。- 音频本身可以很长(≤16 MiB),App 拿到整段自己播;
play/stop作用在同一个instance上。 - 插件不能主动推播放效果:
onCapability的返回值不会被执行成效果,所以"播完自动接下一段"做不到。
于是:
- 预取:
session/event的turn/end(completed)时,把回复按 140 字切段、并发 3 合成到data/cache/<sha1>/sNNN.mp3,全齐后拼成all.mp3。 - 播放:
read只建任务并立刻返回{type:'audio', op:'play', instance:'read', src:'media:speech', key};App 回来GET媒体路由,插件把缓存拼成一个整段 MP3(MP3 裸帧直拼是合法的,没有 ID3 头)。 - 冷缓存:最多等 15 秒,先交已经合成好的前几段(约 30~60 秒语音),剩下的后台继续;10 分钟内再点一次从上次停的地方接着念。
实测(小米 MiMo v2.5 TTS,非流式 mp3,手机网络):15 字≈2.4 s,60 字≈4.7 s,120 字≈8.3 s, 240 字≈38 s(超预算),400 字≈32.6 s,800 字≈54.9 s → 这就是为什么段长取 140。
踩过的坑
instance必须固定(这里用read)。每次播放换新实例名,stop就找不到 lease,"停止"永远按不动——不是接口不支持。- 面板的
select下拉框不可靠:音色改成list一行一个、点一下立刻写设置 + toast +refresh。 - 状态必须走数据绑定(
{"$": "data.info.status"}):视图里写死文字的话,每秒推数据界面也不会变。 - 主面板只用 v1 组件(stack/text/callout/progress/list/keyValue/actions),需要输入框的项放「朗读设置」面板(v2)。
已知限制
- 不能"回复结束自动开口":协议只允许用户点按触发播放。
- 小米目前没有语速参数(豆包有
speech_rate,面板还没暴露)。 - 暂停 / 继续:桥接的
pause实测不可靠,已移除。 - 单次播放音频上限 16 MiB(约 5.5 分钟语音),超长回复会在后台继续合成、下次点按接着念。
- 豆包那一路没有 Key 实测过(按 v3 单向流接口写的),首次使用请先短句试听。
排查
data/debug.log:点按、媒体字节数与耗时、audio.ended/stopped/error、预取情况(不记 Key、不记正文)data/settings.json:当前配置(0600)- 自查:
node $DSH_HOME/skills/dsh-go-plugin/kit/bin/check.mjs <本目录> --load
致谢
- 思路与小米/豆包接口细节参考了 dsh-xiaomi-tts(MIT)——它是网页版实现,播放侧用浏览器 Web Audio,本仓库是面向 dsh-go 原生界面的独立实现。
- 插件格式与桥接能力来自 deepseek-harness 的 dsh-go 插件协议。
License
Похожие плагины
dsh-web-ui (dsh-tool-describe-image)
zhu1090093659/dsh-web-ui
modlens
liustack/modlens
ipollowork
devin-axis/ipollowork
dsh-vision-router
ysr666/dsh-vision-router