dsh-go-local-voice:让 dsh-go 手机 App 原生界面用 MiMo / 豆包念回复

前言

DSH 生态里的 TTS 插件大多跑在 Harness 网页版,声音靠浏览器的 Web Audio 播。如果目标是手机 App 的 Flutter 原生界面,这些插件根本不会触发——播放能力和浏览器绑在一起了。

27Synthesis/dsh-go-local-voice(中文名「鲸鲸朗读」)解决的就是这一件事:给 DeepSeek Harness / dsh-go 手机 App 原生界面加语音朗读,用小米 MiMo TTS 或字节豆包 Seed TTS 合成回复内容,直接在 App 里播放。分类属于模型推理,由 27Synthesis 维护。下面按功能、安装、配置、用法的顺序介绍。

核心能力

先明确它管哪些事,都是 README 里写明的可落地能力:

  1. 长按任意一条助手回复,菜单出现「读出来」和「存成音频」;前者直接播放,后者导出 MP3 到手机存储。
  2. 小米 MiMo 走 mimo-v2.5-tts,内置 8 个音色(冰糖 / 茉莉 / 苏打 / 白桦 / Mia / Chloe / Milo / Dean),支持语音指令(语气、方言、情绪)。
  3. 豆包 Seed TTS 走 seed-tts-2.0,声音复刻走 seed-icl-2.0,使用 HTTP v3 单向流。
  4. 长文一次念完:回合结束后台分段预取,点一次交出去的是一整段音频,不是「点一次念 120 字」。
  5. 「停止」按钮真的停得住,靠固定播放实例加稳定的 lease。
  6. 输入框上方有状态条(正在合成语音… / 朗读中 0:12 / 1:40),面板里有进度条。
  7. 导出过的文件在面板里列出,点一下复制路径。
  8. 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 中。

典型用法

装好并配好 Key 后,操作顺序只有三步:

  1. 打开 App,发起一次对话。
  2. 回合结束后,插件已在后台按 140 字切段、并发 3 合成,写入 data/cache/<sha1>/sNNN.mp3,全齐后拼成 all.mp3。
  3. 长按最新那条回复,选「读出来」开始播放,或选「存成音频」导出。

冷缓存时插件最多等 15 秒,先交已经合成好的前几段(约 30~60 秒语音),剩下的后台继续;10 分钟内再点一次会从上次停的地方接着念。状态条和面板进度条会同步显示进度。

实现要点

给想改代码的人:dsh-go 原生插件的桥接协议(v1.2)有三条硬约束决定了实现方式。一是 media 路由必须在 30 秒内产出字节(CAP_LIMITS.mediaMs),这是生产预算,不是音频时长上限;二是音频本身可以很长(≤16 MiB),App 拿到整段自己播,play / stop 作用在同一个 instance 上;三是插件不能主动推播放效果,onCapability 的返回值不会被执行成效果,所以「播完自动接下一段」做不到。

据此,插件把工作拆成预取和播放两段:session/event 的 turn/end(completed)时切段并发合成到缓存目录;用户点按后 read 只建任务并立刻返回 {type:'audio', op:'play', instance:'read', src:'media:speech', key},App 回来 GET 媒体路由时,插件把缓存拼成一个整段 MP3(MP3 裸帧直拼合法,不需要 ID3 头)。

关于段长取 140:README 给出实测数据(小米 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。为保证单段稳定落在 30 秒预算内,才把默认段长定为 140 字。

已知限制

使用前需要知道这些边界,都是 README「已知限制」列出的:

  1. 不能「回复结束自动开口」:协议只允许用户点按触发播放。
  2. 小米目前没有语速参数;豆包有 speech_rate,但面板还没暴露。
  3. 暂停 / 继续:桥接的 pause 实测不可靠,已移除。
  4. 单次播放音频上限 16 MiB(约 5.5 分钟语音),超长回复会在后台继续合成,下次点按接着念。
  5. 豆包那一路没有 Key 实测过(按 v3 单向流接口写的),首次使用请先短句试听。

另外插件在踩坑里提到几处实现约束:播放的 instance 必须固定(这里用 read),每次换实例名会让 stop 找不到 lease;面板的 select 下拉框不可靠,音色面板改成 list 一行一个,点一下立刻写设置并刷新;状态必须走数据绑定({"$": "data.info.status"}),视图里写死文字的话数据每秒推界面也不变;主面板只用 v1 组件,需要输入框的项放在「朗读设置」面板(v2)。

排查与自检

出问题时从这三处入手:

  1. data/debug.log:记录点按、媒体字节数与耗时、audio.ended/stopped/error、预取情况,不记 Key、不记正文。
  2. data/settings.json:当前配置(权限 0600)。
  3. 插件结构自检:
node $DSH_HOME/skills/dsh-go-plugin/kit/bin/check.mjs \
  <插件目录> --load

适用场景与注意

适合想在 dsh-go 手机原生界面里给助手回复加语音、不希望依赖浏览器 Web Audio 的人;不适合需要自动朗读或暂停继续的场景。

两点提醒:这个插件会以当前 dsh 进程的权限运行,安装前应先读一遍源码(仓库地址见下)并确认许可证;插件格式与桥接能力来自 deepseek-harness 的 dsh-go 插件协议,思路和小米/豆包接口细节参考了网页版项目 dsh-xiaomi-tts(MIT),本仓库是面向 dsh-go 原生界面的独立实现。

目录页:https://www.skillhub.cn/plugins/27Synthesis/dsh-go-local-voice
源码:https://github.com/27Synthesis/dsh-go-local-voice
许可证:MIT(仓库版本 1.0.0,engines.dsh 要求 >=0.2.1-rc.2 <0.3.0-0)。

小结

dsh-go-local-voice 的价值很集中:在 DSH 手机原生界面里提供一条真正出声的 TTS 路径,覆盖小米 MiMo 和豆包两路,且把长文、停止、状态、导出和 Markdown 清洗这些细节都处理掉了。安装命令和配置字段都来自 README,功能边界也在已知限制里写清楚了,照着做就能用。

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜