dsh-voice-mode 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -22,12 +22,10 @@
22
22
  - **Long segments**: hold up to 10 min (pauses don't split), continuous sentences up to 3 min, 700 ms silence split by default;
23
23
  - **Hardening**: session-existence check, loopback + Origin guards, per-endpoint rate limits, model SHA256 pinning, download-host allowlist.
24
24
 
25
- ![Voice mode: live captions and status bar](https://raw.githubusercontent.com/qishuilalala/dsh-voice-mode/HEAD/assets/screenshot-voice.png)
25
+ > ⚠️ The screenshots below (and `assets/demo.gif`) show the **upstream legacy single-button UI**; the current UI adds a mode-switch button next to the mic.
26
26
 
27
27
  ## Features
28
28
 
29
- ![Voice mode: live captions and status bar](https://raw.githubusercontent.com/qishuilalala/dsh-voice-mode/HEAD/assets/screenshot-voice.png)
30
-
31
29
 
32
30
  - **Voice mode**: toggle with the microphone button in the input toolbar or the global shortcut `Ctrl+Shift+V`; globally single-active (only one session is in voice mode at a time; switching sessions yields automatically)
33
31
  - **Two interaction modes (switchable in settings, plus a mode-switch button beside the mic)**:
@@ -46,7 +44,7 @@
46
44
  | Gesture | Behaviour |
47
45
  | --- | --- |
48
46
  | Click the mic button / `Ctrl+Shift+V` | Enter / exit voice mode |
49
- | Just speak, pause 2 s (toggle) | Auto sentence split and send |
47
+ | Just speak, pause 700 ms (toggle) | Auto sentence split and send |
50
48
  | Hold `Ctrl` (toggle, ≥250 ms speech) | Force-send the current segment immediately |
51
49
  | **Hold the mic button (hold)** | Hold to talk, release to send; swipe up / `Esc` / blur abandons the segment; <250 ms tap exits the mode |
52
50
  | Hold `Ctrl` (hold, ≥600 ms) | Keyboard push-to-talk, release to send |
@@ -88,7 +86,7 @@ npm run prefetch # run inside the plugin dir; writes to the platform ca
88
86
  ## Usage
89
87
 
90
88
  1. Click the mic button in the input toolbar (or press `Ctrl+Shift+V`) to enter voice mode; a status bar appears above the input box
91
- 2. Choose how to speak: just talk and let the 2 s pause auto-send (toggle); or hold the mic button and release to send (hold)
89
+ 2. Choose how to speak: just talk and let the 700 ms pause auto-send (toggle); or hold the mic button and release to send (hold)
92
90
  3. The AI answer is read sentence-by-sentence with a caption overlay at the bottom-right; click "Skip" or just start speaking to interrupt
93
91
  4. Click "Exit" in the status bar (or press `Ctrl+Shift+V` again) to leave voice mode
94
92
 
@@ -183,7 +181,7 @@ You can also edit the `voice-mode:` section of `~/.dsh/settings.yaml` directly (
183
181
  ![architecture](https://raw.githubusercontent.com/qishuilalala/dsh-voice-mode/HEAD/plugin/dsh-voice-mode/assets/architecture.svg)
184
182
 
185
183
  ```
186
- input: mic ──RMS VAD (5s silence split)──▶ POST /voice-mode/asr (f32 PCM, 16k, incremental)
184
+ input: mic ──RMS VAD (700 ms silence split)──▶ POST /voice-mode/asr (f32 PCM, 16k, incremental)
187
185
  │ zipformer2 streaming ASR (host-side WASM)
188
186
 
189
187
  composer draft ──autoSend──▶ model stream ──llm/stream tap (active voice session only)
@@ -1 +1,79 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" font-family="Segoe UI, system-ui, sans-serif" font-size="14" viewBox="0 0 960 640"><rect width="960" height="640" fill="#0d1117"/><text x="480" y="30" fill="#e6edf3" font-size="20" font-weight="700" text-anchor="middle">dsh-voice-mode — 语音双工数据流</text><rect width="200" height="64" x="30" y="60" fill="#161b22" stroke="#58a6ff" stroke-width="1.5" rx="10"/><text x="130" y="86" fill="#58a6ff" font-weight="700" text-anchor="middle">麦克风 Mic</text><text x="130" y="108" fill="#8b949e" font-size="12" text-anchor="middle">16kHz 单声道 · AEC 回声消除</text><rect width="200" height="76" x="30" y="150" fill="#161b22" stroke="#58a6ff" stroke-width="1.5" rx="10"/><text x="130" y="178" fill="#58a6ff" font-weight="700" text-anchor="middle">浏览器 VAD 分段</text><text x="130" y="198" fill="#8b949e" font-size="12" text-anchor="middle">静音 700ms 断句 / 按住说门控</text><text x="130" y="216" fill="#8b949e" font-size="12" text-anchor="middle">增量传输(每 0.9s 只传新增)</text><rect width="200" height="64" x="30" y="280" fill="#161b22" stroke="#3fb950" stroke-width="1.5" rx="10"/><text x="130" y="306" fill="#3fb950" font-weight="700" text-anchor="middle">草稿 → 自动发送</text><text x="130" y="328" fill="#8b949e" font-size="12" text-anchor="middle">autoSend / 按住说松手发送</text><rect width="200" height="76" x="30" y="410" fill="#161b22" stroke="#3fb950" stroke-width="1.5" rx="10"/><text x="130" y="438" fill="#3fb950" font-weight="700" text-anchor="middle">播放 + 字幕浮层</text><text x="130" y="458" fill="#8b949e" font-size="12" text-anchor="middle">SSE 音频帧(WAV/MP3)</text><text x="130" y="476" fill="#8b949e" font-size="12" text-anchor="middle">逐段顺序 · 世代链防重播</text><rect width="290" height="64" x="330" y="60" fill="#161b22" stroke="#58a6ff" stroke-width="1.5" rx="10"/><text x="475" y="86" fill="#58a6ff" font-weight="700" text-anchor="middle">zipformer2 流式识别</text><text x="475" y="108" fill="#8b949e" font-size="12" text-anchor="middle">host 本地 WASM · 启动预热 · SHA256 校验</text><rect width="290" height="56" x="330" y="150" fill="#161b22" stroke="#d29922" stroke-width="1.5" rx="10"/><text x="475" y="173" fill="#d29922" font-weight="700" text-anchor="middle">SenseVoice 定稿重译</text><text x="475" y="193" fill="#8b949e" font-size="12" text-anchor="middle">带标点 · 数字归一化(中英日韩粤)</text><rect width="290" height="64" x="330" y="232" fill="#161b22" stroke="#a371f7" stroke-width="1.5" rx="10"/><text x="475" y="258" fill="#a371f7" font-weight="700" text-anchor="middle">llm/stream 无损 tap → 分句</text><text x="475" y="280" fill="#8b949e" font-size="12" text-anchor="middle">text-delta 过滤 · markdown 剥离消毒</text><rect width="290" height="64" x="330" y="322" fill="#161b22" stroke="#a371f7" stroke-width="1.5" rx="10"/><text x="475" y="348" fill="#a371f7" font-weight="700" text-anchor="middle">TTS 队列(epoch 打断)</text><text x="475" y="370" fill="#8b949e" font-size="12" text-anchor="middle">打断即杀在途合成 · 积压作废</text><rect width="290" height="96" x="330" y="412" fill="#161b22" stroke="#3fb950" stroke-width="1.5" rx="10"/><text x="475" y="438" fill="#3fb950" font-weight="700" text-anchor="middle">本地 TTS 引擎(子进程 fork)</text><text x="475" y="460" fill="#8b949e" font-size="12" text-anchor="middle">VITS 纯中文(WASM,可选)</text><text x="475" y="478" fill="#8b949e" font-size="12" text-anchor="middle">Kokoro int8 中英(原生,109MB)</text><text x="475" y="496" fill="#8b949e" font-size="12" text-anchor="middle">Edge 云端(默认,322 音色)</text><rect width="240" height="200" x="690" y="60" fill="#161b22" stroke="#30363d" stroke-width="1" rx="10"/><text x="810" y="88" fill="#e6edf3" font-weight="700" text-anchor="middle">安全面(四层)</text><text x="710" y="116" fill="#8b949e" font-size="12">① 会话存在性校验</text><text x="710" y="136" fill="#8b949e" font-size="12">② 回环默认 + Origin 校验</text><text x="710" y="156" fill="#8b949e" font-size="12">③ 全端点限流</text><text x="710" y="176" fill="#8b949e" font-size="12">④ 模型 SHA256 + 域名白名单</text><text x="710" y="208" fill="#8b949e" font-size="12">打断:VAD 帧级检测 + 回声门控</text><text x="710" y="228" fill="#8b949e" font-size="12">空闲:听与说同权</text><text x="710" y="248" fill="#8b949e" font-size="12">模式:持续聆听 ⇄ 按住说</text><defs><marker id="arr" markerHeight="8" markerWidth="8" orient="auto" refX="7" refY="4"><path fill="#8b949e" d="M0,0 L8,4 L0,8 z"/></marker><marker id="arrR" markerHeight="8" markerWidth="8" orient="auto" refX="7" refY="4"><path fill="#f85149" d="M0,0 L8,4 L0,8 z"/></marker></defs><line x1="230" x2="326" y1="92" y2="92" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/><line x1="230" x2="326" y1="188" y2="178" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/><line x1="475" x2="475" y1="124" y2="146" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/><line x1="230" x2="326" y1="312" y2="264" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/><line x1="475" x2="475" y1="296" y2="318" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/><line x1="475" x2="475" y1="386" y2="408" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/><line x1="620" x2="720" y1="460" y2="460" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/><line x1="720" x2="720" y1="460" y2="446" stroke="#8b949e" stroke-width="1.5"/><line x1="130" x2="130" y1="410" y2="348" stroke="#8b949e" stroke-width="1.5"/><line x1="330" x2="234" y1="348" y2="348" stroke="#8b949e" stroke-width="1.5"/><line x1="234" x2="234" y1="348" y2="406" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/><line x1="130" x2="130" y1="486" y2="560" stroke="#f85149" stroke-width="1.5" marker-end="url(#arrR)"/><text x="300" y="556" fill="#f85149" font-size="12">说话打断:本地静音 + 队列作废 + 回合取消(二级 1.5s)</text></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" font-family="Segoe UI, system-ui, sans-serif" font-size="14" viewBox="0 0 960 640">
2
+ <defs>
3
+ <marker id="arr" markerHeight="8" markerWidth="8" orient="auto" refX="7" refY="4">
4
+ <path fill="#8b949e" d="M0,0 L8,4 L0,8 z"/>
5
+ </marker>
6
+ <marker id="arrR" markerHeight="8" markerWidth="8" orient="auto" refX="7" refY="4">
7
+ <path fill="#f85149" d="M0,0 L8,4 L0,8 z"/>
8
+ </marker>
9
+ </defs>
10
+ <rect width="960" height="640" fill="#0d1117"/>
11
+ <text x="480" y="30" fill="#e6edf3" font-size="20" font-weight="700" text-anchor="middle">dsh-voice-mode — 语音双工数据流</text>
12
+
13
+ <!-- 左列:浏览器侧 -->
14
+ <rect width="200" height="56" x="30" y="70" fill="#161b22" stroke="#58a6ff" stroke-width="1.5" rx="10"/>
15
+ <text x="130" y="92" fill="#58a6ff" font-weight="700" text-anchor="middle">麦克风 Mic</text>
16
+ <text x="130" y="112" fill="#8b949e" font-size="12" text-anchor="middle">16kHz 单声道 · AEC 回声消除</text>
17
+
18
+ <rect width="200" height="70" x="30" y="165" fill="#161b22" stroke="#58a6ff" stroke-width="1.5" rx="10"/>
19
+ <text x="130" y="189" fill="#58a6ff" font-weight="700" text-anchor="middle">浏览器 VAD 分段</text>
20
+ <text x="130" y="209" fill="#8b949e" font-size="12" text-anchor="middle">静音 700ms 断句 / 按住说门控</text>
21
+ <text x="130" y="227" fill="#8b949e" font-size="12" text-anchor="middle">增量传输(每 0.9s 只传新增)</text>
22
+
23
+ <rect width="200" height="56" x="30" y="285" fill="#161b22" stroke="#3fb950" stroke-width="1.5" rx="10"/>
24
+ <text x="130" y="309" fill="#3fb950" font-weight="700" text-anchor="middle">草稿 → 自动发送</text>
25
+ <text x="130" y="329" fill="#8b949e" font-size="12" text-anchor="middle">autoSend / 按住说松手发送</text>
26
+
27
+ <rect width="200" height="70" x="30" y="405" fill="#161b22" stroke="#3fb950" stroke-width="1.5" rx="10"/>
28
+ <text x="130" y="429" fill="#3fb950" font-weight="700" text-anchor="middle">播放 + 字幕浮层</text>
29
+ <text x="130" y="449" fill="#8b949e" font-size="12" text-anchor="middle">SSE 音频帧(WAV/MP3)</text>
30
+ <text x="130" y="467" fill="#8b949e" font-size="12" text-anchor="middle">逐段顺序 · 世代链防重播</text>
31
+
32
+ <!-- 中列:host 侧 -->
33
+ <rect width="290" height="60" x="330" y="70" fill="#161b22" stroke="#58a6ff" stroke-width="1.5" rx="10"/>
34
+ <text x="475" y="94" fill="#58a6ff" font-weight="700" text-anchor="middle">zipformer2 流式识别</text>
35
+ <text x="475" y="114" fill="#8b949e" font-size="12" text-anchor="middle">host 本地 WASM · 启动预热 · SHA256 校验</text>
36
+
37
+ <rect width="290" height="56" x="330" y="165" fill="#161b22" stroke="#d29922" stroke-width="1.5" rx="10"/>
38
+ <text x="475" y="189" fill="#d29922" font-weight="700" text-anchor="middle">SenseVoice 定稿</text>
39
+ <text x="475" y="207" fill="#8b949e" font-size="12" text-anchor="middle">带标点 · 数字归一化</text>
40
+
41
+ <rect width="290" height="56" x="330" y="255" fill="#161b22" stroke="#a371f7" stroke-width="1.5" rx="10"/>
42
+ <text x="475" y="279" fill="#a371f7" font-weight="700" text-anchor="middle">llm/stream 无损 tap → 分句</text>
43
+ <text x="475" y="297" fill="#8b949e" font-size="12" text-anchor="middle">text-delta 过滤 · markdown 剥离</text>
44
+
45
+ <rect width="290" height="56" x="330" y="345" fill="#161b22" stroke="#a371f7" stroke-width="1.5" rx="10"/>
46
+ <text x="475" y="369" fill="#a371f7" font-weight="700" text-anchor="middle">TTS 队列(epoch 打断)</text>
47
+ <text x="475" y="387" fill="#8b949e" font-size="12" text-anchor="middle">打断即杀在途合成 · 积压作废</text>
48
+
49
+ <rect width="290" height="100" x="330" y="435" fill="#161b22" stroke="#3fb950" stroke-width="1.5" rx="10"/>
50
+ <text x="475" y="459" fill="#3fb950" font-weight="700" text-anchor="middle">朗读引擎 TTS</text>
51
+ <text x="475" y="481" fill="#8b949e" font-size="12" text-anchor="middle">Edge 云端(默认,322 音色)</text>
52
+ <text x="475" y="501" fill="#8b949e" font-size="12" text-anchor="middle">VITS 本地 · 纯中文(WASM)</text>
53
+ <text x="475" y="521" fill="#8b949e" font-size="12" text-anchor="middle">Kokoro 本地 · 中英(int8 109MB / fp32 311MB)</text>
54
+
55
+ <!-- 右列:安全面 -->
56
+ <rect width="240" height="200" x="690" y="70" fill="#161b22" stroke="#30363d" stroke-width="1" rx="10"/>
57
+ <text x="810" y="98" fill="#e6edf3" font-weight="700" text-anchor="middle">安全面(四层)</text>
58
+ <text x="710" y="126" fill="#8b949e" font-size="12">① 会话存在性校验</text>
59
+ <text x="710" y="146" fill="#8b949e" font-size="12">② 回环 + Origin 校验</text>
60
+ <text x="710" y="166" fill="#8b949e" font-size="12">③ 全端点限流</text>
61
+ <text x="710" y="186" fill="#8b949e" font-size="12">④ 模型 SHA256 + 域名白名单</text>
62
+ <text x="710" y="214" fill="#8b949e" font-size="12">打断:VAD 帧级检测 + 回声门控</text>
63
+ <text x="710" y="234" fill="#8b949e" font-size="12">模式:持续聆听 ⇄ 按住说</text>
64
+ <text x="710" y="254" fill="#8b949e" font-size="12">空闲:朗读计为活动</text>
65
+
66
+ <!-- 数据流连线 -->
67
+ <line x1="130" y1="126" x2="130" y2="163" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
68
+ <line x1="230" y1="190" x2="326" y2="100" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
69
+ <line x1="475" y1="130" x2="475" y2="163" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
70
+ <line x1="330" y1="193" x2="232" y2="313" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
71
+ <line x1="230" y1="313" x2="326" y2="283" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
72
+ <line x1="475" y1="311" x2="475" y2="343" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
73
+ <line x1="475" y1="401" x2="475" y2="433" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
74
+ <line x1="330" y1="485" x2="232" y2="440" stroke="#8b949e" stroke-width="1.5" marker-end="url(#arr)"/>
75
+
76
+ <!-- 打断链路(红色) -->
77
+ <path d="M130,475 L130,600 L475,600 L475,403" fill="none" stroke="#f85149" stroke-width="1.5" marker-end="url(#arrR)"/>
78
+ <text x="300" y="592" fill="#f85149" font-size="12" text-anchor="middle">说话打断:本地静音 + 队列作废 + 回合取消</text>
79
+ </svg>
package/assets/demo.gif CHANGED
Binary file
package/lib/client.js CHANGED
@@ -2485,7 +2485,7 @@ var TELEMETRY_VIEW = [
2485
2485
  { stage: "first-tts-chunk", key: "telFirstChunk" },
2486
2486
  { stage: "first-audio-played", key: "telFirstPlayed" }
2487
2487
  ];
2488
- var BUILD_TAG = "054e486";
2488
+ var BUILD_TAG = "56e7e10";
2489
2489
  var TELEMETRY_FLAG = "dsh-voice-mode.telemetry";
2490
2490
  var telemetryEnabled = typeof localStorage !== "undefined" && localStorage.getItem(TELEMETRY_FLAG) === "1";
2491
2491
  console.log("[dsh-voice] build=" + BUILD_TAG);
@@ -4040,7 +4040,7 @@ function VoiceStatusBar({ bus, sessionId }) {
4040
4040
  if (b.ui.interruptConfirmMs !== void 0) {
4041
4041
  telParts.push(`${t("interruptConfirm")} ${fmt(b.ui.interruptConfirmMs)}`);
4042
4042
  }
4043
- if (b.ui.echoLevels) {
4043
+ if (telemetryEnabled && b.ui.echoLevels) {
4044
4044
  const el = b.ui.echoLevels;
4045
4045
  telParts.push(
4046
4046
  `AEC delay=${Math.round(b.ui.echoDelayMs ?? 0)}ms floor=${el.floorRms.toFixed(4)} resid=${el.residualRms.toFixed(4)}`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-voice-mode",
3
- "description": "Full-duplex voice mode for DeepSeek Harness: zipformer2 streaming ASR → editable draft, Edge TTS (default, fast) or local (VITS / Kokoro int8, privacy-first) read-aloud with live captions, true barge-in; hardened HTTP surface (loopback/origin/rate-limit) + model SHA256 pinning. · DSH 语音双工对话:流式识别入草稿、Edge 默认朗读或本地(VITS / Kokoro int8)朗读+实时字幕、开口即打断;HTTP 面安全加固。",
4
- "version": "0.7.0",
3
+ "description": "Full-duplex voice plugin for DeepSeek Harness: local zipformer2 streaming ASR (no API key) → editable draft; Edge TTS or local VITS / Kokoro read-aloud with live captions; true barge-in; hardened HTTP surface + model SHA256 pinning; compatible with dsh 0.1.1 → 0.1.2. · DSH 语音双工插件:流式识别入草稿、按句朗读+实时字幕、开口即打断;免 API Key、安全加固、新旧版本兼容。",
4
+ "version": "0.7.2",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "repository": {