@goodandready/dsh-tts 0.4.27 → 0.4.29
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/CHANGELOG.md +68 -0
- package/README.md +18 -13
- package/README.ru.md +11 -10
- package/README.zh.md +5 -5
- package/lib/cache.js +70 -40
- package/lib/client.js +338 -166
- package/lib/engines/manager.js +137 -75
- package/lib/index.js +345 -64
- package/lib/local.js +1 -1
- package/lib/providers/cloud.js +44 -5
- package/lib/providers/constants.js +4 -4
- package/lib/providers/local.js +1 -1
- package/lib/providers.js +11 -2
- package/lib/routes.js +107 -36
- package/lib/stream-hub.js +55 -8
- package/lib/text.js +25 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,73 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.29
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Settings & Revision Management** (#151, #152):
|
|
7
|
+
- Added robust `extractRevision` supporting nested Schemastery namespaces and entries array.
|
|
8
|
+
- Return revision in `GET /dsh-tts/config` and send it in `PUT /dsh-tts/config`.
|
|
9
|
+
- Eliminated stale `savedConfig` caching in `live()`, enabling direct live Schemastery volatile box evaluation.
|
|
10
|
+
- **Audio Pipeline, Tool Schema & Dedup** (#156, #157, #166, #173, #175, #190):
|
|
11
|
+
- Enforced speech preparation, maxChars, and summarizeReply before sentence splitting in `speakAsItGoes`.
|
|
12
|
+
- Added `id: { type: 'string' }` to `speak_text` tool output schema under `additionalProperties: false` preventing core ToolOutputError.
|
|
13
|
+
- Included credential slot reference in cache and flight fingerprints avoiding cross-credential collisions.
|
|
14
|
+
- In-memory index for synthesis cache avoiding O(N) fs scans.
|
|
15
|
+
- **Stream Hub & Web Player** (#158, #159, #160, #161, #174, #176, #186):
|
|
16
|
+
- `skipCurrent()` now cancels active browser SpeechSynthesis and clears busy state.
|
|
17
|
+
- `playAudio` resolves promise on NotAllowedError and detaches gesture listeners cleanly.
|
|
18
|
+
- Disconnect slow SSE subscribers exceeding backpressure buffer threshold.
|
|
19
|
+
- Registered updater routes inside ctx.effect with clean disposers.
|
|
20
|
+
- **Providers & Local Engines** (#165, #167, #168, #169, #170, #171, #172, #177, #178, #179, #180):
|
|
21
|
+
- Nested integrations parser in UI and gated /speak on disabled channels.
|
|
22
|
+
- Canonical EN defaults for Edge and eSpeak.
|
|
23
|
+
- 100% key parity for EN/ZH model hints.
|
|
24
|
+
- Google TTS validates MIME and wraps raw PCM in valid WAV container.
|
|
25
|
+
- Azure SSML includes required xmlns/mstts attributes.
|
|
26
|
+
- Robust model download locking, cancellation awaiting writer cleanup, and HTML error page rejection.
|
|
27
|
+
- **Release Hygiene & Sanitization** (#183, #184, #185):
|
|
28
|
+
- Removed outdated marketing claims from READMEs.
|
|
29
|
+
- Restored internal DEV docs while verifying exclusion from npm pack and GitHub mirror.
|
|
30
|
+
- Cleaned up publish-github script to handle negation patterns.
|
|
31
|
+
|
|
32
|
+
## 0.4.28
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- **Settings Contracts & Schemastery Validation** (#151, #152, #153, #155, #187, #188, #189):
|
|
36
|
+
- Made all 9 previously static fields volatile and ensured living Volatile boxes are unwrapped dynamically in live() without stale snapshots.
|
|
37
|
+
- Constrained maxQueue with .step(1).min(0) to reject non-integer and negative queue limits.
|
|
38
|
+
- Added optimistic revision validation and rollback on conflict (HTTP 409) in settings saving.
|
|
39
|
+
- Enforced isTrustedSettingsRequest auth guards on GET /dsh-tts/config and GET /dsh-tts/status.
|
|
40
|
+
- Enforced contiguous sequence ordering in SSE synthesis chunks.
|
|
41
|
+
|
|
42
|
+
- **Audio Pipeline, Cache & Concurrency** (#154, #156, #157, #166, #173, #174, #175, #186, #190):
|
|
43
|
+
- Sanitized and normalized text once in prepareSpeechText() before character limits or summary generation, eliminating double-clean pronunciation distortions.
|
|
44
|
+
- Fixed subagent voice override gating with autoDetectSubagent !== false.
|
|
45
|
+
- Extended cache and dedup fingerprints with endpoint, models, voices, role, and language.
|
|
46
|
+
- Added admission concurrency throttle and semaphore for maxQueue synthesis reservations.
|
|
47
|
+
- Implemented SSE subscriber backpressure tracking and dropped slow lagging subscribers (>512 KB buffer) with drain handling.
|
|
48
|
+
- Switched cache index to an in-memory Map to eliminate disk scans on eviction.
|
|
49
|
+
- Ensured speak_text tool returns metadata and delivers audio to pending queue/SSE without dumping base64 into LLM context.
|
|
50
|
+
|
|
51
|
+
- **Client Player & UI Parity** (#158, #159, #160, #161, #162, #163, #164, #165, #167, #168, #169, #176):
|
|
52
|
+
- Unified EventSource listener connection in client, preventing duplicate audio channels.
|
|
53
|
+
- Ensured skipCurrent() halts active playback immediately, and preview/replay route through the unified player.
|
|
54
|
+
- Gracefully handled browser autoplay restrictions (NotAllowedError), pausing the queue until user interaction.
|
|
55
|
+
- Kept compact recent/favorites history control visible in idle state.
|
|
56
|
+
- Registered updater routes inside ctx.effect() to properly unbind on plugin disposal.
|
|
57
|
+
- Enabled all 16 active providers in client UI, with secure key management and read-only indicators when settings scope is missing.
|
|
58
|
+
- Fixed sibling integration detection using Cordis services without relying on obsolete hasRoute.
|
|
59
|
+
- Re-anchored canonical defaults to English (en, en-US-AriaNeural) and achieved 100% parity between EN and ZH dictionaries.
|
|
60
|
+
|
|
61
|
+
- **Model Weights Manager & System Hygiene** (#177, #178, #179, #180, #181, #182, #183, #184, #185):
|
|
62
|
+
- Stream chunk downloads now properly manage EventEmitter listeners without leaking error or drain handlers.
|
|
63
|
+
- Added synchronous concurrency lock per engine to share in-flight downloads and used unique nonce temporary file paths.
|
|
64
|
+
- Added minimum file size validation (minBytes / 1024 bytes) to reject truncated or error HTML downloads.
|
|
65
|
+
- Validated engine parameter in /dsh-tts/models/install and /dsh-tts/models/delete, returning HTTP 400 for unknown engines.
|
|
66
|
+
- Replaced tautological regex and source inspection tests with true runtime behavior tests.
|
|
67
|
+
- Isolated cache tests in temporary directories with automated cleanup.
|
|
68
|
+
- Synchronized README documentation with reality and strictly excluded internal dev docs (AGENTS.md, index.md, plans) from package distributions.
|
|
69
|
+
- Closes #151, #152, #153, #154, #155, #156, #157, #158, #159, #160, #161, #162, #163, #164, #165, #166, #167, #168, #169, #170, #171, #172, #173, #174, #175, #176, #177, #178, #179, #180, #181, #182, #183, #184, #185, #186, #187, #188, #189, #190, #191.
|
|
70
|
+
|
|
3
71
|
## 0.4.27
|
|
4
72
|
|
|
5
73
|
### Fixed
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-
<h3>Multi-Provider Text-to-Speech Voice Synthesis with Local Neural Engines,
|
|
5
|
+
<h3>Multi-Provider Text-to-Speech Voice Synthesis with Local Neural Engines, Real-time Streaming, IT Dictionary & Messenger Integration for DeepSeek Harness</h3>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<a href="https://www.npmjs.com/package/@goodandready/dsh-tts"><img src="https://img.shields.io/npm/v/@goodandready/dsh-tts.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
|
@@ -49,7 +49,7 @@ graph LR
|
|
|
49
49
|
|
|
50
50
|
subgraph Stream [Low-Latency Streaming]
|
|
51
51
|
Scrub --> SSE[SSE /dsh-tts/stream]
|
|
52
|
-
SSE -->
|
|
52
|
+
SSE --> Player[Web Audio & HTML Audio Player]
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
subgraph Cache [Performance Layer]
|
|
@@ -59,7 +59,7 @@ graph LR
|
|
|
59
59
|
|
|
60
60
|
subgraph Fallback [TTS Provider Fallback Chain]
|
|
61
61
|
LRU -->|Cache Miss| Chain{Active Chain}
|
|
62
|
-
Chain -->|
|
|
62
|
+
Chain -->|Online / Local| P1[Edge TTS / Piper / eSpeak]
|
|
63
63
|
Chain -.->|Cloud Neural| P2[OpenAI / ElevenLabs / Google / Azure / Groq]
|
|
64
64
|
Chain -.->|OpenAI-compatible| P3[SiliconFlow / DeepInfra / Fireworks / OpenRouter]
|
|
65
65
|
Chain -.->|Other| P4[MiMo / MiniMax / Custom]
|
|
@@ -85,13 +85,14 @@ graph LR
|
|
|
85
85
|
|
|
86
86
|
## 🚀 Key Features
|
|
87
87
|
|
|
88
|
-
### 1. 📴
|
|
89
|
-
* **
|
|
88
|
+
### 1. 📴 Local and system engines + optional future neural runtimes
|
|
89
|
+
* **Piper / eSpeak**: fully offline local synthesis without cloud API keys.
|
|
90
|
+
* **Edge TTS**: free online cloud synthesis without API keys (requires network connection and the `edge-tts` CLI).
|
|
90
91
|
* **Kokoro-82M / F5-TTS**: weight download and status UI only. **Neural inference is not bundled** in this package — those providers fail with a clear reason and the fallback chain continues. Do not enable them expecting speech until a supported runtime is wired.
|
|
91
92
|
* **ModelManager UI**: Direct manual installation in settings with real-time download progress bar, SHA-256 validation, and deletion. No silent or automatic multi-gigabyte downloads.
|
|
92
93
|
|
|
93
94
|
### 2. ⚡ Real-Time Streaming Audio (< 300 ms Latency)
|
|
94
|
-
* **
|
|
95
|
+
* **HTML Audio & Web Audio Player**: Seamless queued audio playback with prebuffering, barge-in support, playback rate control, and instant browser fallback.
|
|
95
96
|
* **Server-Sent Events (SSE)**: Dedicated `/dsh-tts/stream` route delivering synthesized chunks to connected browsers instantly.
|
|
96
97
|
|
|
97
98
|
### 3. 🎙️ Voice Duplex & VAD Barge-In (with `@goodandready/dsh-voice`)
|
|
@@ -101,10 +102,10 @@ graph LR
|
|
|
101
102
|
|
|
102
103
|
### 4. 📚 Built-in IT Terminology Pronunciation Dictionary
|
|
103
104
|
* **Pre-configured Lexicon**: Correct phonetic pronunciation for common technical abbreviations and developer terms:
|
|
104
|
-
- `SQL` $\rightarrow$ "
|
|
105
|
-
- `Nginx` $\rightarrow$ "
|
|
106
|
-
- `Kubernetes` / `K8s` $\rightarrow$ "
|
|
107
|
-
- `Docker` $\rightarrow$ "
|
|
105
|
+
- `SQL` $\rightarrow$ "sequel"
|
|
106
|
+
- `Nginx` $\rightarrow$ "engine-x"
|
|
107
|
+
- `Kubernetes` / `K8s` $\rightarrow$ "coo-ber-net-ees" / "kates"
|
|
108
|
+
- `Docker` $\rightarrow$ "docker", `API` $\rightarrow$ "A-P-I", `JSON` $\rightarrow$ "jason", `YAML` $\rightarrow$ "yaml"
|
|
108
109
|
- `GUI`, `CLI`, `CI/CD`, `PR`, `Regex`, `OAuth`, `HTTP`, `HTTPS`, `CPU`, `GPU`, `RAM`
|
|
109
110
|
* **Interactive UI Editor**: Edit rules, preview phonetic substitutions with the **▶ Listen** button, and populate standard IT terms with one click.
|
|
110
111
|
|
|
@@ -135,7 +136,7 @@ graph LR
|
|
|
135
136
|
| `f5` | Local F5-TTS GPU Daemon | `F5-TTS` | Default | *None* | Daemon ping only; **GPU inference not bundled** — fails honestly |
|
|
136
137
|
| `elevenlabs` | ElevenLabs API | `eleven_multilingual_v2` | `Rachel` | `ELEVENLABS_API_KEY` | Ultra-realistic, emotional nuance |
|
|
137
138
|
| `openai` | OpenAI Audio | `gpt-4o-mini-tts` / `tts-1` | `alloy` | `OPENAI_API_KEY` | High-quality industry standard |
|
|
138
|
-
| `edge` | Microsoft Edge Online | `
|
|
139
|
+
| `edge` | Microsoft Edge Online | `en-US-AriaNeural` | `en-US-AriaNeural` | *None* | **Free, high-fidelity neural TTS without API keys** |
|
|
139
140
|
| `siliconflow` | SiliconFlow CosyVoice | `FunAudioLLM/CosyVoice2-0.5B` | Default | `SILICONFLOW_API_KEY` | State-of-the-art CosyVoice2 neural engine |
|
|
140
141
|
| `deepinfra` | DeepInfra Kokoro | `hexgrad/Kokoro-82M` | Default | `DEEPINFRA_API_KEY` | Fast open-weights Kokoro synthesis |
|
|
141
142
|
| `fireworks` | Fireworks AI | `kokoro` | Default | `FIREWORKS_API_KEY` | Ultra-low latency Kokoro inference |
|
|
@@ -173,7 +174,7 @@ dsh plugin --profile web add @goodandready/dsh-tts
|
|
|
173
174
|
|
|
174
175
|
---
|
|
175
176
|
|
|
176
|
-
## ⚙️ Configuration Recipes (
|
|
177
|
+
## ⚙️ Configuration Recipes (DSH Settings UI)
|
|
177
178
|
|
|
178
179
|
```yaml
|
|
179
180
|
dsh-tts:
|
|
@@ -212,7 +213,7 @@ The plugin settings card (**Settings → Plugins → Plugin settings**) exposes
|
|
|
212
213
|
|
|
213
214
|
Provider **API keys are never stored in plugin settings**. Paste them in the chain editor; values go to the DSH credential store via `PUT /dsh-tts/credential`.
|
|
214
215
|
|
|
215
|
-
Config-only (not in the card): `*KeyEnv` fields (`openaiKeyEnv`, `elevenlabsKeyEnv`, …). They only rename the credential slot the plugin looks up. Change them in
|
|
216
|
+
Config-only (not in the card): `*KeyEnv` fields (`openaiKeyEnv`, `elevenlabsKeyEnv`, …). They only rename the credential slot the plugin looks up. Change them in the profile settings if you must rebind a key name; the defaults match the usual environment variable names.
|
|
216
217
|
|
|
217
218
|
|
|
218
219
|
## 🤖 HTTP Endpoints Reference
|
|
@@ -225,6 +226,10 @@ Config-only (not in the card): `*KeyEnv` fields (`openaiKeyEnv`, `elevenlabsKeyE
|
|
|
225
226
|
* `DELETE /dsh-tts/models/delete` — `{ engine: 'kokoro' | 'f5' }` → Removes local model files.
|
|
226
227
|
* `GET /dsh-tts/integrations` — Status of sibling plugins (`dsh-voice`, `dsh-messenger-gateway`).
|
|
227
228
|
* `GET /dsh-tts/status` — Returns active chain state, cache statistics, and engine readiness.
|
|
229
|
+
* `DELETE /dsh-tts/cache` — Clears cached audio files.
|
|
230
|
+
* `GET /dsh-tts/config` & `PUT /dsh-tts/config` — Retrieve and update settings revision.
|
|
231
|
+
* `GET /dsh-tts/stats` & `DELETE /dsh-tts/stats` — Performance statistics and metrics reset.
|
|
232
|
+
* `PUT /dsh-tts/credential` & `DELETE /dsh-tts/credential` — Securely store or remove cloud provider API keys.
|
|
228
233
|
|
|
229
234
|
---
|
|
230
235
|
|
package/README.ru.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-
<h3>Многопровайдерная озвучка ответов агента:
|
|
5
|
+
<h3>Многопровайдерная озвучка ответов агента: облачные, локальные и системные движки, потоковый звук, IT-словарь и интеграция с мессенджерами для DeepSeek Harness</h3>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<a href="https://www.npmjs.com/package/@goodandready/dsh-tts"><img src="https://img.shields.io/npm/v/@goodandready/dsh-tts.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
|
|
38
38
|
## ⚡ Обзор
|
|
39
39
|
|
|
40
|
-
> **Честно про локальные нейросети:** Kokoro/F5 — только статус и загрузка весов. Inference **не входит** в пакет; эти провайдеры падают с понятной причиной, цепочка идёт дальше. Рабочий offline: Edge TTS
|
|
40
|
+
> **Честно про локальные нейросети:** Kokoro/F5 — только статус и загрузка весов. Inference **не входит** в пакет; эти провайдеры падают с понятной причиной, цепочка идёт дальше. Рабочий offline: Piper, eSpeak (Edge TTS — бесплатный онлайн-сервис без ключей).
|
|
41
41
|
|
|
42
42
|
**`dsh-tts`** обеспечивает качественное голосовое озвучивание ответов ассистента в веб-интерфейсе **DeepSeek Harness**. При включённой опции **«Читать ответы агента»**, каждая готовая реплика или потоковый фрагмент синтезируется на хосте и мгновенно воспроизводится в браузере.
|
|
43
43
|
|
|
44
|
-
API-ключи никогда не передаются в браузер: синтез аудио выполняется на стороне хоста через **независимые цепочки отказоустойчивости (фолбеков)**, включая системные
|
|
44
|
+
API-ключи никогда не передаются в браузер: синтез аудио выполняется на стороне хоста через **независимые цепочки отказоустойчивости (фолбеков)**, включая локальные и системные движки (Edge TTS — онлайн без ключей, Piper и eSpeak — offline). Kokoro/F5 — только загрузка весов; inference не входит в пакет.
|
|
45
45
|
|
|
46
46
|
```mermaid
|
|
47
47
|
graph LR
|
|
@@ -51,7 +51,7 @@ graph LR
|
|
|
51
51
|
|
|
52
52
|
subgraph Stream [Потоковый звук < 300 мс]
|
|
53
53
|
Scrub --> SSE[SSE /dsh-tts/stream]
|
|
54
|
-
SSE -->
|
|
54
|
+
SSE --> Player[Web Audio & HTML Audio плеер]
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
subgraph Cache [Слой производительности]
|
|
@@ -61,7 +61,7 @@ graph LR
|
|
|
61
61
|
|
|
62
62
|
subgraph Fallback [Цепочка фолбеков TTS]
|
|
63
63
|
LRU -->|Промах кэша| Chain{Активная цепочка}
|
|
64
|
-
Chain -->|
|
|
64
|
+
Chain -->|Online / Local| P1[Edge TTS / Piper / eSpeak]
|
|
65
65
|
Chain -.->|Cloud| P2[OpenAI / ElevenLabs / Google / Azure / Groq]
|
|
66
66
|
Chain -.->|Бесплатные облака| P3[EdgeTTS / SiliconFlow]
|
|
67
67
|
Chain -.->|Системный фолбек| P4[Локальный Piper / eSpeak NG]
|
|
@@ -87,13 +87,14 @@ graph LR
|
|
|
87
87
|
|
|
88
88
|
## 🚀 Ключевые возможности
|
|
89
89
|
|
|
90
|
-
### 1. 📴
|
|
91
|
-
* **
|
|
90
|
+
### 1. 📴 Локальные движки и честный статус нейросетей
|
|
91
|
+
* **Piper / eSpeak**: полностью автономный локальный оффлайн-синтез.
|
|
92
|
+
* **Edge TTS**: бесплатный онлайн-синтез через Microsoft без API-ключей (требует интернет и CLI `edge-tts`).
|
|
92
93
|
* **Kokoro-82M / F5-TTS**: только UI и загрузка весов. **Inference не входит в пакет** — провайдер падает с понятной причиной, цепочка идёт дальше.
|
|
93
94
|
* **ModelManager в UI**: Ручная загрузка моделей прямо из настроек плагина с отображением процентов и прогресс-бара, проверкой SHA-256 и кнопкой удаления. Никаких скрытых или автозагрузок гигабайтных весов.
|
|
94
95
|
|
|
95
96
|
### 2. ⚡ Потоковое воспроизведение звука с задержкой < 300 мс
|
|
96
|
-
* **
|
|
97
|
+
* **HTML Audio & Web Audio плеер**: Бесшовное воспроизведение аудио-очереди с предбуферизацией, поддержкой перебивания речи (barge-in), регулировкой скорости и отказоустойчивым fallback на синтез браузера.
|
|
97
98
|
* **Server-Sent Events (SSE)**: Роут `/dsh-tts/stream` передаёт синтезированные куски речи сразу в браузер, исключая задержки традиционного полинга.
|
|
98
99
|
|
|
99
100
|
### 3. 🎙️ Голосовой диалог и VAD Barge-In (в связке с `@goodandready/dsh-voice`)
|
|
@@ -156,7 +157,7 @@ dsh plugin --profile web add @goodandready/dsh-tts
|
|
|
156
157
|
|
|
157
158
|
---
|
|
158
159
|
|
|
159
|
-
## ⚙️ Пример конфигурации (
|
|
160
|
+
## ⚙️ Пример конфигурации (Интерфейс настроек DSH)
|
|
160
161
|
|
|
161
162
|
```yaml
|
|
162
163
|
dsh-tts:
|
|
@@ -195,7 +196,7 @@ dsh-tts:
|
|
|
195
196
|
|
|
196
197
|
**API-ключи не хранятся в настройках плагина.** Вставляйте их в редакторе цепочки — значение уходит в credential store DSH через `PUT /dsh-tts/credential`.
|
|
197
198
|
|
|
198
|
-
Только в конфиге (нет в карточке): поля `*KeyEnv` (`openaiKeyEnv`, `elevenlabsKeyEnv`, …). Они задают **имя** credential-слота; по умолчанию совпадают с привычными именами env-переменных. Меняйте в
|
|
199
|
+
Только в конфиге (нет в карточке): поля `*KeyEnv` (`openaiKeyEnv`, `elevenlabsKeyEnv`, …). Они задают **имя** credential-слота; по умолчанию совпадают с привычными именами env-переменных. Меняйте в настройках профиля, если нужно перевязать имя ключа.
|
|
199
200
|
|
|
200
201
|
|
|
201
202
|
## 🤖 HTTP роуты API
|
package/README.zh.md
CHANGED
|
@@ -47,9 +47,9 @@ graph LR
|
|
|
47
47
|
Reply[💬 智能体生成文本] --> Scrub[智能过滤 & IT术语词典]
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
subgraph Stream [
|
|
50
|
+
subgraph Stream [实时流式音频播放]
|
|
51
51
|
Scrub --> SSE[SSE /dsh-tts/stream]
|
|
52
|
-
SSE -->
|
|
52
|
+
SSE --> Player[Web Audio & HTML Audio 播放器]
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
subgraph Cache [性能缓存层]
|
|
@@ -90,8 +90,8 @@ graph LR
|
|
|
90
90
|
* **Kokoro-82M / F5-TTS**:仅权重下载与状态展示。**推理未捆绑在本包中**,provider 会明确失败并继续 fallback。
|
|
91
91
|
* **ModelManager 管理界面**:在设置中手动安装模型,实时显示下载进度条、SHA-256 校验和一键删除。无任何静默或自动下载。
|
|
92
92
|
|
|
93
|
-
### 2. ⚡
|
|
94
|
-
* **
|
|
93
|
+
### 2. ⚡ 实时流式音频播放
|
|
94
|
+
* **HTML Audio & Web Audio 播放器**:无缝队列音频播放,支持预缓冲、麦克风打断响应 (barge-in)、播放倍速控制以及浏览器原生语音故障回退。
|
|
95
95
|
* **Server-Sent Events (SSE)**:专用 `/dsh-tts/stream` 路由将合成音频片段即时推送至浏览器。
|
|
96
96
|
|
|
97
97
|
### 3. 🎙️ 语音双工对话与 VAD 打断(配合 `@goodandready/dsh-voice`)
|
|
@@ -165,7 +165,7 @@ dsh plugin --profile web add @goodandready/dsh-tts
|
|
|
165
165
|
API 密钥不写入插件设置,只在链编辑器粘贴,经 `PUT /dsh-tts/credential` 写入 DSH credential store。
|
|
166
166
|
|
|
167
167
|
|
|
168
|
-
## ⚙️
|
|
168
|
+
## ⚙️ 配置说明(DSH 设置面板)
|
|
169
169
|
|
|
170
170
|
```yaml
|
|
171
171
|
dsh-tts:
|
package/lib/cache.js
CHANGED
|
@@ -41,56 +41,78 @@ export function createSpeechCache({ root, maxBytes }) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
// In-memory index of disk cache entries to avoid O(N) readdir/stat/readFile per write (#190)
|
|
45
|
+
const diskEntries = new Map() // key -> { size, at }
|
|
46
|
+
let diskTotalBytes = 0
|
|
47
|
+
let indexLoaded = false
|
|
48
|
+
|
|
49
|
+
async function ensureIndex() {
|
|
45
50
|
await ensure()
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
if (indexLoaded) return
|
|
52
|
+
diskEntries.clear()
|
|
53
|
+
diskTotalBytes = 0
|
|
54
|
+
let files = []
|
|
55
|
+
try {
|
|
56
|
+
files = await fs.promises.readdir(dir)
|
|
57
|
+
} catch {
|
|
58
|
+
files = []
|
|
59
|
+
}
|
|
60
|
+
for (const name of files) {
|
|
48
61
|
if (!name.endsWith(AUDIO_EXT)) continue
|
|
62
|
+
const key = name.slice(0, -AUDIO_EXT.length)
|
|
49
63
|
const full = path.join(dir, name)
|
|
50
|
-
let st
|
|
51
|
-
try {
|
|
52
|
-
st = await fs.promises.stat(full)
|
|
53
|
-
} catch {
|
|
54
|
-
continue
|
|
55
|
-
}
|
|
56
|
-
let at = st.mtimeMs
|
|
57
64
|
try {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
const st = await fs.promises.stat(full)
|
|
66
|
+
let at = st.mtimeMs
|
|
67
|
+
try {
|
|
68
|
+
const meta = JSON.parse(await fs.promises.readFile(path.join(dir, key + '.json'), 'utf8'))
|
|
69
|
+
if (meta && typeof meta.at === 'number') at = meta.at
|
|
70
|
+
} catch { /* no meta */ }
|
|
71
|
+
diskEntries.set(key, { size: st.size, at })
|
|
72
|
+
diskTotalBytes += st.size
|
|
73
|
+
} catch { /* skip unreadable */ }
|
|
62
74
|
}
|
|
63
|
-
|
|
75
|
+
indexLoaded = true
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
async function evict() {
|
|
79
|
+
await ensureIndex()
|
|
67
80
|
const limit = typeof maxBytes === 'function' ? maxBytes() : Number(maxBytes)
|
|
68
|
-
if (!(limit > 0)) return
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
for (const
|
|
78
|
-
if (
|
|
79
|
-
await fs.promises.rm(
|
|
80
|
-
await fs.promises.rm(path.join(dir,
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
if (!(limit > 0) || diskTotalBytes <= limit) return
|
|
82
|
+
|
|
83
|
+
// Sort in-memory entries by timestamp ascending (oldest first)
|
|
84
|
+
const sorted = Array.from(diskEntries.entries()).sort((a, b) => {
|
|
85
|
+
const atA = l1Map.get(a[0])?.at ?? a[1].at
|
|
86
|
+
const atB = l1Map.get(b[0])?.at ?? b[1].at
|
|
87
|
+
return atA - atB
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
for (const [key, meta] of sorted) {
|
|
91
|
+
if (diskTotalBytes <= limit) break
|
|
92
|
+
await fs.promises.rm(path.join(dir, key + AUDIO_EXT), { force: true }).catch(() => {})
|
|
93
|
+
await fs.promises.rm(path.join(dir, key + '.json'), { force: true }).catch(() => {})
|
|
94
|
+
diskEntries.delete(key)
|
|
95
|
+
diskTotalBytes -= meta.size
|
|
96
|
+
l1Map.delete(key)
|
|
83
97
|
}
|
|
84
98
|
}
|
|
85
99
|
|
|
86
100
|
return {
|
|
87
101
|
async put(key, mime, audio) {
|
|
88
|
-
await
|
|
102
|
+
await ensureIndex()
|
|
89
103
|
const buf = Buffer.isBuffer(audio) ? audio : Buffer.from(audio)
|
|
90
104
|
putL1(key, mime, buf)
|
|
91
105
|
await fs.promises.writeFile(path.join(dir, key + AUDIO_EXT), buf)
|
|
92
|
-
|
|
93
|
-
await
|
|
106
|
+
const now = Date.now()
|
|
107
|
+
await fs.promises.writeFile(path.join(dir, key + '.json'), JSON.stringify({ mime, at: now }))
|
|
108
|
+
const prev = diskEntries.get(key)
|
|
109
|
+
if (prev) diskTotalBytes -= prev.size
|
|
110
|
+
diskEntries.set(key, { size: buf.length, at: now })
|
|
111
|
+
diskTotalBytes += buf.length
|
|
112
|
+
const limit = typeof maxBytes === 'function' ? maxBytes() : Number(maxBytes)
|
|
113
|
+
if (limit > 0 && diskTotalBytes > limit) {
|
|
114
|
+
await evict()
|
|
115
|
+
}
|
|
94
116
|
},
|
|
95
117
|
|
|
96
118
|
async get(key) {
|
|
@@ -100,17 +122,22 @@ export function createSpeechCache({ root, maxBytes }) {
|
|
|
100
122
|
const now = Date.now()
|
|
101
123
|
l1Map.delete(key) // refresh LRU order
|
|
102
124
|
l1Map.set(key, { ...hit, at: now })
|
|
125
|
+
const d = diskEntries.get(key)
|
|
126
|
+
if (d) d.at = now
|
|
103
127
|
fs.promises.writeFile(path.join(dir, key + '.json'), JSON.stringify({ mime: hit.mime, at: now })).catch(() => {})
|
|
104
128
|
return { mime: hit.mime || 'audio/mpeg', audio: hit.audio }
|
|
105
129
|
}
|
|
106
130
|
|
|
107
131
|
// 2. L2 Disk Hit
|
|
132
|
+
await ensureIndex()
|
|
108
133
|
try {
|
|
109
134
|
const meta = JSON.parse(await fs.promises.readFile(path.join(dir, key + '.json'), 'utf8'))
|
|
110
135
|
const audio = await fs.promises.readFile(path.join(dir, key + AUDIO_EXT))
|
|
111
136
|
putL1(key, meta.mime, audio)
|
|
112
|
-
|
|
113
|
-
|
|
137
|
+
const now = Date.now()
|
|
138
|
+
const d = diskEntries.get(key)
|
|
139
|
+
if (d) d.at = now
|
|
140
|
+
fs.promises.writeFile(path.join(dir, key + '.json'), JSON.stringify({ mime: meta.mime, at: now })).catch(() => {})
|
|
114
141
|
return { mime: meta.mime || 'audio/mpeg', audio }
|
|
115
142
|
} catch (miss) {
|
|
116
143
|
return null
|
|
@@ -119,12 +146,15 @@ export function createSpeechCache({ root, maxBytes }) {
|
|
|
119
146
|
|
|
120
147
|
async clear() {
|
|
121
148
|
l1Map.clear()
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
await fs.promises.rm(path.join(dir,
|
|
149
|
+
await ensureIndex()
|
|
150
|
+
const count = diskEntries.size
|
|
151
|
+
for (const key of diskEntries.keys()) {
|
|
152
|
+
await fs.promises.rm(path.join(dir, key + AUDIO_EXT), { force: true }).catch(() => {})
|
|
153
|
+
await fs.promises.rm(path.join(dir, key + '.json'), { force: true }).catch(() => {})
|
|
126
154
|
}
|
|
127
|
-
|
|
155
|
+
diskEntries.clear()
|
|
156
|
+
diskTotalBytes = 0
|
|
157
|
+
return count
|
|
128
158
|
},
|
|
129
159
|
}
|
|
130
160
|
}
|