dsh-go-local-voice: Enable dsh-go mobile app native interface to read replies using MiMo / Doubao

Introduction

Most TTS plugins in the DSH ecosystem run on the Harness web version, relying on the browser’s Web Audio for sound. If the target is a mobile App’s Flutter native interface, these plugins simply won’t trigger—playback capability is tied to the browser.

27Synthesis/dsh-go-local-voice (Chinese name “JingJing Reading” 鲸鲸朗读) solves exactly this problem: it adds voice reading to the DeepSeek Harness / dsh-go mobile App native interface, using Xiaomi MiMo TTS or ByteDance Doubao Seed TTS to synthesize replies and playing them directly within the App. It falls under model inference and is maintained by 27Synthesis. Below, we introduce it in order of functionality, installation, configuration, and usage.

Core Capabilities

First, clarify what it manages—these are the actionable capabilities listed in the README:

  1. Long press any assistant reply; the menu appears with “Read Out” and “Save as Audio”; the former plays directly, the latter exports an MP3 to phone storage.
  2. Xiaomi MiMo uses mimo-v2.5-tts, with 8 built-in tones (Bing Tang / Jasmine / Soda / Birch / Mia / Chloe / Milo / Dean), supporting voice commands (tone, dialect, emotion).
  3. ByteDance Doubao Seed TTS uses seed-tts-2.0, Voice Cloning uses seed-icl-2.0, using HTTP v3 unidirectional stream.
  4. Long texts are read in one go: segments are prefetched in the background after a turn ends; one click delivers a whole segment of audio, not “click to read 120 characters”.
  5. The “Stop” button actually stops, relying on a fixed playback instance and a stable lease.
  6. There is a status bar above the input box (Synthesizing voice... / Reading 0:12 / 1:40) and a progress bar in the panel.
  7. Exported files are listed in the panel; click to copy the path.
  8. Markdown cleaning: code blocks, links, tables, and inline code are not read aloud; links are read as “Link”.

Installation and Enablement

The plugin directory is placed under $DSH_HOME/dsh-go/plugins/ (default ~/.dsh). The two commands below clone the directory and register the plugin, respectively.

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"

After installation, the engine in the App needs to be restarted once (Settings → Engine → Restart Engine) before the entry points are visible:

  • Long press the assistant message; the menu appears with “Read Out” and “Save as Audio”;
  • In the “Plugin” capsule above the input box, click “JingJing Reading”.

Uninstallation is the reverse:

dsh plugin --profile web remove local-voice

Then simply delete the local directory.

Configuration

On first use, click “Open Settings” in the main panel and enter the API Key. The fields are described in the table below.

Item Description
Xiaomi Key Apply on Xiaomi MiMo console, format like sk-...
Doubao Key Apply on Doubao Voice console, supports standard tones and Agent Plan
Doubao Voice ID Format like zh_female_...; fill in the cloning voice ID when using Voice Cloning
Words per segment Default 140. Approx 0.08s/word, 140 words ≈ 11s, stably within the production budget of 30s per single segment
Auto prefetch Default ON. Synthesize the whole reply in the background immediately after it ends, so clicking “Read Out” plays almost instantly

Keys are only stored locally in data/settings.json (permissions 0600), will not go into the repository, will not appear in the interface, and will not be sent to the model; data/ is already in .gitignore.

Typical Usage

After installation and configuration, there are only three steps to operate:

  1. Open the App and initiate a conversation.
  2. After the turn ends, the plugin has already segmented and synthesized concurrently (3 at a time) at 140 words per segment in the background, writing to data/cache/<sha1>/sNNN.mp3, and merging into all.mp3 when complete.
  3. Long press the latest reply, select “Read Out” to start playback, or select “Save as Audio” to export.

When using a cold cache, the plugin waits at most 15 seconds, first delivering the already synthesized early segments (approx 30–60 seconds of audio), and continues the rest in the background; clicking again within 10 minutes will resume reading from where it left off. The status bar and panel progress bar will display progress synchronously.

Implementation Highlights

For those who want to modify the code: the bridging protocol for dsh-go native plugins (v1.2) has three hard constraints that determine the implementation approach. First, the media route must produce bytes within 30 seconds (CAP_LIMITS.mediaMs); this is a production budget, not an audio duration limit. Second, the audio itself can be very long (≤16 MiB); the App gets the whole segment and plays it itself; play / stop act on the same instance. Third, the plugin cannot actively push playback effects; the return values of onCapability are not executed as effects, so “auto play the next segment after finishing” is not possible.

Based on this, the plugin splits the work into prefetch and playback: when session/event turn/end (completed), it segments and synthesizes concurrently to the cache directory; when the user clicks read, it creates a task and immediately returns {type:'audio', op:'play', instance:'read', src:'media:speech', key}. When the App comes back to GET the media route, the plugin merges the cache into a whole MP3 (MP3 raw frames can be concatenated directly, no ID3 header needed).

Regarding why the segment length is 140: the README provides empirical data (Xiaomi MiMo v2.5 TTS, non-stream MP3, mobile network): 15 words ≈ 2.4s, 60 words ≈ 4.7s, 120 words ≈ 8.3s, 240 words ≈ 38s (over budget), 400 words ≈ 32.6s, 800 words ≈ 54.9s. To ensure a single segment stably falls within the 30-second budget, the default segment length is set to 140 words.

Known Limitations

You need to know these boundaries before using them; they are listed in the “Known Limitations” section of the README:

  1. Cannot “automatically start speaking at reply end”: the protocol only allows user clicks to trigger playback.
  2. Xiaomi currently has no speed parameter; Doubao has speech_rate, but it has not been exposed in the panel.
  3. Pause / Resume: The bridged pause is tested to be unreliable and has been removed.
  4. Single playback audio limit is 16 MiB (approx 5.5 minutes of audio). Long replies will continue synthesizing in the background; the next click will resume reading.
  5. The Doubao path has not been tested with a Key (written according to the v3 unidirectional stream interface); please try short sentences first when using it for the first time.

Additionally, several implementation constraints are mentioned in the “pitfalls” section:
- The instance for playback must be fixed (here using read); changing the instance name every time causes stop to fail to find the lease.
- The panel’s select dropdown is unreliable; the tone panel has been changed to list (one per line), clicking immediately writes settings and refreshes.
- Status must use data binding ({"$": "data.info.status"}); if text is hardcoded in the view, it won’t update even if data is pushed to the interface every second.
- The main panel only uses v1 components; items requiring input boxes are placed in the “Reading Settings” panel (v2).

Troubleshooting and Self-Check

When problems occur, start from these three places:

  1. data/debug.log: records clicks, media byte counts and time, audio.ended/stopped/error, prefetch status. It does not record Keys or the body.
  2. data/settings.json: current configuration (permissions 0600).
  3. Plugin structure self-check:
node $DSH_HOME/skills/dsh-go-plugin/kit/bin/check.mjs \
  <plugin_directory> --load

Applicable Scenarios and Notes

Suitable for those who want to add voice to assistant replies in the dsh-go mobile native interface without relying on browser Web Audio; not suitable for scenarios requiring auto-read or pause/resume.

Two reminders: this plugin runs with the permissions of the current dsh process. You should read the source code (see repository address below) and confirm the license before installing. The plugin format and bridging capabilities come from the deepseek-harness dsh-go plugin protocol. Ideas and interface details reference the web project dsh-xiaomi-tts (MIT). This repository is a standalone implementation for the dsh-go native interface.

Directory page: https://www.skillhub.cn/plugins/27Synthesis/dsh-go-local-voice
Source code: https://github.com/27Synthesis/dsh-go-local-voice
License: MIT (Repository version 1.0.0, engines.dsh requires >=0.2.1-rc.2 <0.3.0-0).

Summary

The value of dsh-go-local-voice is focused: it provides a truly audible TTS path in the DSH mobile native interface, covering both Xiaomi MiMo and Doubao, and handles details like long texts, stopping, status, export, and Markdown cleaning. Installation commands and configuration fields come from the README, and functional boundaries are clearly written in the known limitations. Follow the instructions to use.

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

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

Xiaoye