@goodandready/dsh-tts 0.3.23 → 0.4.1

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.md CHANGED
@@ -29,7 +29,7 @@
29
29
 
30
30
  **`dsh-tts`** provides robust, lifelike spoken voice synthesis for assistant replies in the **DeepSeek Harness** Web UI. When **Speak agent replies** is enabled, each finished assistant turn or real-time streaming chunk is synthesized on the host and streamed directly to the browser.
31
31
 
32
- API keys never reach client browsers: synthesis is executed entirely on the host backend across **independent multi-provider fallback chains**, including completely offline neural models (Kokoro-82M and F5-TTS).
32
+ API keys never reach client browsers: synthesis is executed entirely on the host backend across **independent multi-provider fallback chains**, including local system engines (Edge TTS, Piper, eSpeak). Kokoro-82M and F5-TTS weights can be downloaded for future runtime support, but neural inference is **not bundled** in this package — those providers fail honestly and the chain continues.
33
33
 
34
34
  ```mermaid
35
35
  graph LR
@@ -49,10 +49,10 @@ graph LR
49
49
 
50
50
  subgraph Fallback [TTS Provider Fallback Chain]
51
51
  LRU -->|Cache Miss| Chain{Active Chain}
52
- Chain -->|Priority 1| P1[Kokoro / F5-TTS Local Offline]
53
- Chain -.->|Cloud Neural| P2[ElevenLabs / OpenAI / CosyVoice]
54
- Chain -.->|Free Cloud / Edge| P3[EdgeTTS / SiliconFlow]
55
- Chain -.->|System Fallback| P4[Local Piper / eSpeak NG]
52
+ Chain -->|Offline| P1[Edge TTS / Piper / eSpeak]
53
+ Chain -.->|Cloud Neural| P2[OpenAI / ElevenLabs / Google / Azure / Groq]
54
+ Chain -.->|OpenAI-compatible| P3[SiliconFlow / DeepInfra / Fireworks / OpenRouter]
55
+ Chain -.->|Other| P4[MiMo / MiniMax / Custom]
56
56
  end
57
57
 
58
58
  subgraph Output [Delivery & Integrations]
@@ -75,9 +75,9 @@ graph LR
75
75
 
76
76
  ## 🚀 Key Features
77
77
 
78
- ### 1. 📴 Offline Local Neural Engines (Kokoro CPU & F5-TTS GPU)
79
- * **Kokoro-82M (CPU)**: 82M-parameter lightweight neural model running locally on CPU via ONNX Runtime. High-speed synthesis with zero cloud dependencies.
80
- * **F5-TTS (GPU)**: Zero-shot diffusion transformer voice synthesis running on NVIDIA GPUs via a local inference daemon.
78
+ ### 1. 📴 Offline system engines + optional future neural runtimes
79
+ * **Edge TTS / Piper / eSpeak**: fully offline or free local/system synthesis without cloud API keys (Edge needs the `edge-tts` CLI).
80
+ * **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.
81
81
  * **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.
82
82
 
83
83
  ### 2. ⚡ Real-Time Streaming Audio (< 300 ms Latency)
@@ -112,8 +112,8 @@ graph LR
112
112
 
113
113
  | Provider Key | Service Backend | Default Model | Default Voice | Credential Ref | Features & Notes |
114
114
  |---|---|---|---|---|---|
115
- | `kokoro` | Local Kokoro-82M ONNX | `hexgrad/Kokoro-82M` | `af_bella` | *None* | **100% offline CPU neural synthesis** |
116
- | `f5` | Local F5-TTS GPU Daemon | `F5-TTS` | Default | *None* | **High-fidelity GPU zero-shot voice synthesis** |
115
+ | `kokoro` | Local Kokoro-82M ONNX | `hexgrad/Kokoro-82M` | `af_bella` | *None* | Weights downloadable; **ONNX inference not bundled** — fails honestly |
116
+ | `f5` | Local F5-TTS GPU Daemon | `F5-TTS` | Default | *None* | Daemon ping only; **GPU inference not bundled** — fails honestly |
117
117
  | `elevenlabs` | ElevenLabs API | `eleven_multilingual_v2` | `Rachel` | `ELEVENLABS_API_KEY` | Ultra-realistic, emotional nuance |
118
118
  | `openai` | OpenAI Audio | `gpt-4o-mini-tts` / `tts-1` | `alloy` | `OPENAI_API_KEY` | High-quality industry standard |
119
119
  | `edge` | Microsoft Edge Online | `ru-RU-SvetlanaNeural` | `ru-RU-SvetlanaNeural` | *None* | **Free, high-fidelity neural TTS without API keys** |
@@ -170,8 +170,8 @@ dsh-tts:
170
170
  cacheMaxMb: 150
171
171
  autoDetect: true
172
172
  chain:
173
- - provider: kokoro
174
173
  - provider: edge
174
+ - provider: espeak
175
175
  voice: ru-RU-SvetlanaNeural
176
176
  - provider: openai
177
177
  model: tts-1
@@ -187,12 +187,22 @@ dsh-tts:
187
187
 
188
188
  ---
189
189
 
190
+ ### Settings UI coverage
191
+
192
+ The plugin settings card (**Settings → Plugins → Plugin settings**) exposes every user-facing schema field, including an **Advanced** block for `maxChars`, `sentenceChars`, `timeoutMs`, `maxQueue`, `openaiBaseUrl`, `mimoBaseUrl`, `mimoFormat`, and `minimaxBin`.
193
+
194
+ 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`.
195
+
196
+ Config-only (not in the card): `*KeyEnv` fields (`openaiKeyEnv`, `elevenlabsKeyEnv`, …). They only rename the credential slot the plugin looks up. Change them in `settings.yaml` if you must rebind a key name; the defaults match the usual environment variable names.
197
+
198
+ The sidebar `settings.section` entry is a **fallback** and is registered only when `settings.plugin.item` is missing from the build.
199
+
190
200
  ## 🤖 HTTP Endpoints Reference
191
201
 
192
202
  * `GET /dsh-tts/stream` — Real-time Server-Sent Events (SSE) audio streaming.
193
203
  * `POST /dsh-tts/speak` — `{ text, voice?, model? }` → Returns synthesized audio.
194
204
  * `POST /dsh-tts/preview` — `{ provider, model, voice, text? }` → Test voice playback in UI.
195
- * `GET /dsh-tts/models/status` — Reports local Kokoro and F5-TTS model installation states.
205
+ * `GET /dsh-tts/models/status` — Reports Kokoro/F5 weight installation states (download only; inference not bundled).
196
206
  * `POST /dsh-tts/models/install` — `{ engine: 'kokoro' | 'f5' }` → Starts HuggingFace model download.
197
207
  * `DELETE /dsh-tts/models/delete` — `{ engine: 'kokoro' | 'f5' }` → Removes local model files.
198
208
  * `GET /dsh-tts/integrations` — Status of sibling plugins (`dsh-voice`, `dsh-messenger-gateway`).
package/README.ru.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- <h3>Многопровайдерная озвучка ответов агента с локальными нейродвижками, потоковым звуком (<300 мс), IT-словарём и интеграцией с мессенджерами для DeepSeek Harness</h3>
5
+ <h3>Многопровайдерная озвучка ответов агента: облачные и системные offline-движки, потоковый звук, 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>
@@ -27,9 +27,12 @@
27
27
 
28
28
  ## ⚡ Обзор
29
29
 
30
+ > **Честно про локальные нейросети:** Kokoro/F5 — только статус и загрузка весов. Inference **не входит** в пакет; эти провайдеры падают с понятной причиной, цепочка идёт дальше. Рабочий offline: Edge TTS, Piper, eSpeak.
31
+
32
+
30
33
  **`dsh-tts`** обеспечивает качественное голосовое озвучивание ответов ассистента в веб-интерфейсе **DeepSeek Harness**. При включённой опции **«Читать ответы агента»**, каждая готовая реплика или потоковый фрагмент синтезируется на хосте и мгновенно воспроизводится в браузере.
31
34
 
32
- API-ключи никогда не передаются в браузер: синтез аудио выполняется на стороне хоста через **независимые цепочки отказоустойчивости (фолбеков)**, включая полностью локальные оффлайн-нейросети (Kokoro-82M и F5-TTS).
35
+ API-ключи никогда не передаются в браузер: синтез аудио выполняется на стороне хоста через **независимые цепочки отказоустойчивости (фолбеков)**, включая системные offline-движки (Edge TTS, Piper, eSpeak). Kokoro/F5 — только загрузка весов; inference не входит в пакет.
33
36
 
34
37
  ```mermaid
35
38
  graph LR
@@ -49,8 +52,8 @@ graph LR
49
52
 
50
53
  subgraph Fallback [Цепочка фолбеков TTS]
51
54
  LRU -->|Промах кэша| Chain{Активная цепочка}
52
- Chain -->|1-й приоритет| P1[Kokoro / F5-TTS Локально]
53
- Chain -.->|Облачные нейросети| P2[ElevenLabs / OpenAI / CosyVoice]
55
+ Chain -->|Offline| P1[Edge TTS / Piper / eSpeak]
56
+ Chain -.->|Cloud| P2[OpenAI / ElevenLabs / Google / Azure / Groq]
54
57
  Chain -.->|Бесплатные облака| P3[EdgeTTS / SiliconFlow]
55
58
  Chain -.->|Системный фолбек| P4[Локальный Piper / eSpeak NG]
56
59
  end
@@ -75,9 +78,9 @@ graph LR
75
78
 
76
79
  ## 🚀 Ключевые возможности
77
80
 
78
- ### 1. 📴 Полностью локальные оффлайн-движки (Kokoro CPU и F5-TTS GPU)
79
- * **Kokoro-82M (CPU)**: Компактная нейросетевая модель (82 млн параметров), работающая локально на CPU через ONNX Runtime. Быстрый синтез без внешних API и интернета.
80
- * **F5-TTS (GPU)**: Диффузионный трансформер нулевого выстрела (zero-shot) на видеокартах NVIDIA через выделенный локальный демон инференса.
81
+ ### 1. 📴 Offline-системные движки и честный статус нейросетей
82
+ * **Edge TTS / Piper / eSpeak**: рабочий offline/системный синтез без облачных API-ключей.
83
+ * **Kokoro-82M / F5-TTS**: только UI и загрузка весов. **Inference не входит в пакет** — провайдер падает с понятной причиной, цепочка идёт дальше.
81
84
  * **ModelManager в UI**: Ручная загрузка моделей прямо из настроек плагина с отображением процентов и прогресс-бара, проверкой SHA-256 и кнопкой удаления. Никаких скрытых или автозагрузок гигабайтных весов.
82
85
 
83
86
  ### 2. ⚡ Потоковое воспроизведение звука с задержкой < 300 мс
@@ -112,8 +115,8 @@ graph LR
112
115
 
113
116
  | Ключ провайдера | Сервис | Модель по умолчанию | Голос по умолчанию | Имя ключа (credential) | Особенности |
114
117
  |---|---|---|---|---|---|
115
- | `kokoro` | Локальный Kokoro-82M ONNX | `hexgrad/Kokoro-82M` | `af_bella` | *Не требуется* | **100% локальный нейросинтез на CPU** |
116
- | `f5` | Локальный F5-TTS GPU | `F5-TTS` | По умолчанию | *Не требуется* | **Качественный zero-shot синтез на NVIDIA GPU** |
118
+ | `kokoro` | Веса Kokoro-82M (ONNX) | `hexgrad/Kokoro-82M` | `af_bella` | *Не требуется* | Веса скачать можно; **inference не bundled** — честный fail |
119
+ | `f5` | Демон F5 (ping) | `F5-TTS` | По умолчанию | *Не требуется* | Только ping; **GPU inference не bundled** — честный fail |
117
120
  | `elevenlabs` | ElevenLabs API | `eleven_multilingual_v2` | `Rachel` | `ELEVENLABS_API_KEY` | Реалистичный эмоциональный синтез |
118
121
  | `openai` | OpenAI Audio | `gpt-4o-mini-tts` / `tts-1` | `alloy` | `OPENAI_API_KEY` | Эталонное качество индустрии |
119
122
  | `edge` | Microsoft Edge Online | `ru-RU-SvetlanaNeural` | `ru-RU-SvetlanaNeural` | *Не требуется* | **Бесплатный нейросинтез без API-ключей** |
@@ -177,12 +180,22 @@ dsh-tts:
177
180
 
178
181
  ---
179
182
 
183
+ ### Покрытие настроек в UI
184
+
185
+ Карточка плагина (**Настройки → Плагины → Настройки плагинов**) показывает все пользовательские поля схемы, включая блок **Дополнительно**: `maxChars`, `sentenceChars`, `timeoutMs`, `maxQueue`, `openaiBaseUrl`, `mimoBaseUrl`, `mimoFormat`, `minimaxBin`.
186
+
187
+ **API-ключи не хранятся в настройках плагина.** Вставляйте их в редакторе цепочки — значение уходит в credential store DSH через `PUT /dsh-tts/credential`.
188
+
189
+ Только в конфиге (нет в карточке): поля `*KeyEnv` (`openaiKeyEnv`, `elevenlabsKeyEnv`, …). Они задают **имя** credential-слота; по умолчанию совпадают с привычными именами env-переменных. Меняйте в `settings.yaml`, если нужно перевязать имя ключа.
190
+
191
+ Пункт `settings.section` в боковом меню — **fallback** и регистрируется, только если в сборке нет `settings.plugin.item`.
192
+
180
193
  ## 🤖 HTTP роуты API
181
194
 
182
195
  * `GET /dsh-tts/stream` — Потоковая передача аудио по SSE в реальном времени.
183
196
  * `POST /dsh-tts/speak` — `{ text, voice?, model? }` → Возвращает аудиофайл для воспроизведения.
184
197
  * `POST /dsh-tts/preview` — `{ provider, model, voice, text? }` → Тестовое прослушивание голоса в UI.
185
- * `GET /dsh-tts/models/status` — Статус установленных локальных моделей (Kokoro, F5-TTS).
198
+ * `GET /dsh-tts/models/status` — Статус загрузки весов Kokoro/F5 (inference не в комплекте).
186
199
  * `POST /dsh-tts/models/install` — `{ engine: 'kokoro' | 'f5' }` → Запуск загрузки модели с HuggingFace.
187
200
  * `DELETE /dsh-tts/models/delete` — `{ engine: 'kokoro' | 'f5' }` → Удаление локальной модели.
188
201
  * `GET /dsh-tts/integrations` — Проверка статуса связанных плагинов (`dsh-voice`, `dsh-messenger-gateway`).
package/README.zh.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- <h3>DeepSeek Harness 多引擎语音合成:本地离线神经网络引擎、低延迟流式音频(<300ms)、IT术语词典与即时通讯集成</h3>
5
+ <h3>DeepSeek Harness 多引擎语音合成:云端与系统离线引擎、流式音频、IT 术语词典与即时通讯集成</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>
@@ -29,7 +29,7 @@
29
29
 
30
30
  **`dsh-tts`** 为 **DeepSeek Harness** Web 界面提供高保真智能体回复语音朗读服务。开启 **朗读智能体回复** 后,每条生成的助手回复或实时流式片段均由服务端合成并即时推流至浏览器播放。
31
31
 
32
- API 密钥绝不暴露给前端:音频合成全程在服务端通过**多服务商独立备用链**执行,包括完全离线的本地神经网络模型(Kokoro-82M 和 F5-TTS)。
32
+ API 密钥绝不暴露给前端:音频合成全程在服务端通过**多服务商独立备用链**执行,包括系统离线引擎(Edge TTS、Piper、eSpeak)。Kokoro/F5 仅可下载权重,**推理未捆绑**。
33
33
 
34
34
  ```mermaid
35
35
  graph LR
@@ -49,8 +49,8 @@ graph LR
49
49
 
50
50
  subgraph Fallback [TTS 引擎备用链]
51
51
  LRU -->|未命中| Chain{生效备用链}
52
- Chain -->|首选| P1[Kokoro / F5-TTS 本地离线]
53
- Chain -.->|云端神经网络| P2[ElevenLabs / OpenAI / CosyVoice]
52
+ Chain -->|离线| P1[Edge TTS / Piper / eSpeak]
53
+ Chain -.->|云端| P2[OpenAI / ElevenLabs / Google / Azure / Groq]
54
54
  Chain -.->|免费云端| P3[EdgeTTS / SiliconFlow]
55
55
  Chain -.->|系统兜底| P4[本地 Piper / eSpeak NG]
56
56
  end
@@ -75,9 +75,9 @@ graph LR
75
75
 
76
76
  ## 🚀 核心功能
77
77
 
78
- ### 1. 📴 完全离线本地神经网络引擎(Kokoro CPU 与 F5-TTS GPU)
79
- * **Kokoro-82M (CPU)**:8200万参数轻量级神经网络模型,通过 ONNX Runtime 在 CPU 上本地运行,零云端依赖。
80
- * **F5-TTS (GPU)**:零样本扩散 Transformer 语音合成,通过本地推理守护进程运行于 NVIDIA GPU。
78
+ ### 1. 📴 离线系统引擎与诚实的神经引擎状态
79
+ * **Edge TTS / Piper / eSpeak**:可用的离线/系统合成,无需云 API Key。
80
+ * **Kokoro-82M / F5-TTS**:仅权重下载与状态展示。**推理未捆绑在本包中**,provider 会明确失败并继续 fallback。
81
81
  * **ModelManager 管理界面**:在设置中手动安装模型,实时显示下载进度条、SHA-256 校验和一键删除。无任何静默或自动下载。
82
82
 
83
83
  ### 2. ⚡ 实时流式音频播放(延迟 < 300ms)
@@ -107,8 +107,8 @@ graph LR
107
107
 
108
108
  | 服务商 Key | 对应引擎 | 默认模型 | 默认发音人 | 凭证变量名 | 说明与亮点 |
109
109
  |---|---|---|---|---|---|
110
- | `kokoro` | 本地 Kokoro-82M ONNX | `hexgrad/Kokoro-82M` | `af_bella` | *无需密钥* | **100% 离线 CPU 神经网络合成** |
111
- | `f5` | 本地 F5-TTS GPU 守护进程 | `F5-TTS` | 默认 | *无需密钥* | **高保真 GPU 零样本语音合成** |
110
+ | `kokoro` | Kokoro 权重(ONNX) | `hexgrad/Kokoro-82M` | `af_bella` | *无需密钥* | 可下载权重;**推理未捆绑** — 明确失败 |
111
+ | `f5` | F5 守护进程(ping) | `F5-TTS` | 默认 | *无需密钥* | 仅 ping;**GPU 推理未捆绑** — 明确失败 |
112
112
  | `elevenlabs` | ElevenLabs API | `eleven_multilingual_v2` | `Rachel` | `ELEVENLABS_API_KEY` | 极致拟人情感音色 |
113
113
  | `openai` | OpenAI Audio | `gpt-4o-mini-tts` / `tts-1` | `alloy` | `OPENAI_API_KEY` | 经典高清发音 |
114
114
  | `edge` | 微软 Edge 在线 | `zh-CN-XiaoxiaoNeural` | `zh-CN-XiaoxiaoNeural` | *无需密钥* | **免费免 Key 高保真神经网络语音** |
@@ -139,6 +139,14 @@ dsh plugin --profile web add @goodandready/dsh-tts
139
139
 
140
140
  ---
141
141
 
142
+ ### 设置界面覆盖
143
+
144
+ 插件设置卡片(**设置 → 插件 → 插件设置**)展示全部用户向 schema 字段,并含 **Advanced** 区:`maxChars`、`sentenceChars`、`timeoutMs`、`maxQueue`、`openaiBaseUrl`、`mimoBaseUrl`、`mimoFormat`、`minimaxBin`。
145
+
146
+ API 密钥不写入插件设置,只在链编辑器粘贴,经 `PUT /dsh-tts/credential` 写入 DSH credential store。
147
+
148
+ 仅配置文件:`*KeyEnv` 字段(凭证槽位名称)。侧边栏 `settings.section` 仅为 fallback。
149
+
142
150
  ## ⚙️ 配置示例(`settings.yaml`)
143
151
 
144
152
  ```yaml
@@ -177,7 +185,7 @@ dsh-tts:
177
185
  * `GET /dsh-tts/stream` — 实时 SSE 音频流推送。
178
186
  * `POST /dsh-tts/speak` — `{ text, voice?, model? }` → 返回合成音频。
179
187
  * `POST /dsh-tts/preview` — `{ provider, model, voice, text? }` → UI 中试听语音。
180
- * `GET /dsh-tts/models/status` — 查询本地模型安装状态(Kokoro、F5-TTS)。
188
+ * `GET /dsh-tts/models/status` — 查询 Kokoro/F5 权重下载状态(推理未捆绑)。
181
189
  * `POST /dsh-tts/models/install` — `{ engine: 'kokoro' | 'f5' }` → 启动模型下载。
182
190
  * `DELETE /dsh-tts/models/delete` — `{ engine: 'kokoro' | 'f5' }` → 删除本地模型。
183
191
  * `GET /dsh-tts/integrations` — 查询关联插件状态(`dsh-voice`、`dsh-messenger-gateway`)。
package/lib/cache.js CHANGED
@@ -1,7 +1,6 @@
1
- // Кэш синтеза на диске: ключ — хеш (текст, провайдер, модель, голос),
2
- // значение — ответ провайдера как есть (байты + MIME). Вытеснение самых
3
- // давних по времени последнего обращения; ограничение задаётся настройкой
4
- // cacheMaxMb и может меняться на живую, поэтому приходит функцией.
1
+ // Disk speech cache: key is a hash of (text, provider, model, voice),
2
+ // value is the provider response as-is (bytes + MIME). LRU eviction;
3
+ // cacheMaxMb is live config so the limit is passed as a function.
5
4
  import { createHash } from 'node:crypto'
6
5
  import fs from 'node:fs'
7
6
  import path from 'node:path'
@@ -68,7 +67,7 @@ export function createSpeechCache({ root, maxBytes }) {
68
67
  try {
69
68
  const meta = JSON.parse(await fs.promises.readFile(path.join(dir, key + '.json'), 'utf8'))
70
69
  const audio = await fs.promises.readFile(path.join(dir, key + AUDIO_EXT))
71
- // Последнее обращение двигаем асинхронно: чтение тормозить нельзя.
70
+ // Touch LRU asynchronously so reads are never blocked.
72
71
  fs.promises.writeFile(path.join(dir, key + '.json'), JSON.stringify({ mime: meta.mime, at: Date.now() })).catch(() => {})
73
72
  return { mime: meta.mime || 'audio/mpeg', audio }
74
73
  } catch (miss) {
@@ -0,0 +1,85 @@
1
+ window.__ModuleLoader__.load({
2
+ id: '@goodandready/dsh-tts',
3
+ factory: (require) => {
4
+ var module = { exports: {} }
5
+ var exports = module.exports
6
+ const React = require('react')
7
+
8
+ // Chevron comes from core UI primitives; local SVG is a fallback for
9
+ // builds without the package. Icon always points down; open state rotates.
10
+ let ChevronIcon = null
11
+ try {
12
+ const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
13
+ ChevronIcon = primitives && primitives.IconChevronDownOutline14
14
+ } catch (noPrimitives) {
15
+ ChevronIcon = null
16
+ }
17
+ const NS = 'dsh-tts'
18
+ const PROVIDERS = [
19
+ 'openai', 'elevenlabs', 'google', 'azure', 'groq', 'deepgram', 'openrouter',
20
+ 'edge', 'piper', 'espeak',
21
+ ]
22
+ const CLOUD = {
23
+ openai: 1, elevenlabs: 1, google: 1, azure: 1, groq: 1, deepgram: 1, openrouter: 1, custom: 1,
24
+ }
25
+ // Hints stay generic: concrete provider model IDs rot quickly.
26
+ const MODEL_HINT = {
27
+ openai: 'provider default',
28
+ elevenlabs: 'provider default',
29
+ google: 'provider default',
30
+ azure: 'region voice name',
31
+ groq: 'provider default',
32
+ deepgram: 'provider default (English-only voices)',
33
+ openrouter: 'provider/model id',
34
+ edge: 'BCP-47 voice, e.g. ru-RU-SvetlanaNeural',
35
+ piper: 'path to .onnx',
36
+ espeak: 'language code, e.g. ru',
37
+ kokoro: 'not bundled — offline neural runtime unavailable',
38
+ f5: 'not bundled — offline neural runtime unavailable',
39
+ }
40
+
41
+
42
+ function createErrorBoundary() {
43
+ if (!React || typeof React.Component !== 'function') {
44
+ return function NoopBoundary(props) { return (props && props.children) || null }
45
+ }
46
+ return class ErrorBoundary extends React.Component {
47
+ constructor(props) {
48
+ super(props)
49
+ this.state = { hasError: false, error: null }
50
+ }
51
+ static getDerivedStateFromError(error) {
52
+ return { hasError: true, error }
53
+ }
54
+ componentDidCatch(error, errorInfo) {
55
+ console.error('[dsh-tts] React Error:', error, errorInfo)
56
+ }
57
+ render() {
58
+ if (this.state.hasError) {
59
+ return React.createElement(
60
+ 'div',
61
+ {
62
+ className: 'dts-alert dts-alert-err',
63
+ style: { margin: '12px 0', padding: '14px', borderRadius: '8px' },
64
+ },
65
+ React.createElement('div', { style: { fontWeight: 600, marginBottom: '6px' } }, '⚠️ TTS UI Error:'),
66
+ React.createElement('div', { style: { fontSize: '12px', wordBreak: 'break-all' } }, String((this.state.error && this.state.error.message) || this.state.error)),
67
+ React.createElement(
68
+ 'button',
69
+ {
70
+ type: 'button',
71
+ className: 'dts-btn',
72
+ style: { marginTop: '10px', fontSize: '12px', padding: '4px 10px' },
73
+ onClick: () => this.setState({ hasError: false, error: null }),
74
+ },
75
+ 'Retry',
76
+ ),
77
+ )
78
+ }
79
+ return (this.props && this.props.children) || null
80
+ }
81
+ }
82
+ }
83
+ const ErrorBoundary = createErrorBoundary()
84
+ const SNAPSHOT_READY = Object.freeze({ status: 'ready', value: {} })
85
+ const SNAPSHOT_LOADING = Object.freeze({ status: 'loading', value: {} })
@@ -0,0 +1,54 @@
1
+
2
+ const SET_CSS =
3
+ '.dts-wrap{display:flex;flex-direction:column;gap:18px;padding:4px 0 24px;max-width:900px}' +
4
+ '.dts-header{display:flex;flex-direction:column;gap:8px;padding-bottom:14px;border-bottom:1px solid var(--dsw-alias-border-l2)}' +
5
+ '.dts-page-title{font-size:20px;font-weight:700;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:10px}' +
6
+ '.dts-page-sub{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5}' +
7
+ '.dts-block{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:16px 18px;display:flex;flex-direction:column;gap:12px}' +
8
+ '.dts-h{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between}' +
9
+ '.dts-sub{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4}' +
10
+ '.dts-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none}' +
11
+ '.dts-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 16px}' +
12
+ '.dts-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}' +
13
+ '.dts-title{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}' +
14
+ '.dts-chev{margin-left:auto;color:var(--dsw-alias-label-secondary);flex:none;transition:transform .16s}' +
15
+ '.dts-chevOpen{transform:rotate(180deg)}' +
16
+ '.dts-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:12px 0}' +
17
+ '.dts-field{display:flex;flex-direction:column;gap:6px;padding:6px 0;font-size:13px;color:var(--dsw-alias-label-primary)}' +
18
+ '.dts-input{height:34px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:13px;box-sizing:border-box}' +
19
+ '.dts-input:focus{outline:none;border-color:var(--dsw-alias-state-brand-primary)}' +
20
+ '.dts-foot{border-top:1px solid var(--dsw-alias-border-l2);display:flex;justify-content:flex-end;align-items:center;gap:10px;padding:14px 0 4px}' +
21
+ '.dts-save{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:6px 16px;font-size:13px;font-weight:500;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);transition:all .15s ease}' +
22
+ '.dts-save:hover:not(:disabled){opacity:0.88}' +
23
+ '.dts-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 12px;font-size:13px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:all .15s ease}' +
24
+ '.dts-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));border-color:var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2))}' +
25
+ '.dts-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:rgba(239,68,68,0.3)}' +
26
+ '.dts-btn-danger:hover:not(:disabled){background:rgba(239,68,68,0.12);border-color:rgba(239,68,68,0.5)}' +
27
+ '.dts-entry{display:flex;flex-direction:column;gap:8px;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2)}' +
28
+ '.dts-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}' +
29
+ '.dts-row .dts-model{flex:1;min-width:120px}' +
30
+ '.dts-row .dts-key{flex:1;min-width:180px}' +
31
+ '.dts-mini{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:6px;width:28px;height:28px;cursor:pointer;flex:none;display:inline-flex;align-items:center;justify-content:center;font-size:13px;transition:all .15s ease}' +
32
+ '.dts-mini:hover:not(:disabled){background:var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2))}' +
33
+ '.dts-ok{font-size:12px;color:var(--dsw-alias-state-success-primary);font-weight:500}' +
34
+ '.dts-bad{font-size:12px;color:var(--dsw-alias-state-error-primary);font-weight:500}' +
35
+ '.dts-badge{font-size:11px;padding:3px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);white-space:nowrap;display:inline-flex;align-items:center;gap:4px;font-weight:500}' +
36
+ '.dts-badge-on{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary);background:rgba(16,185,129,0.08)}' +
37
+ '.dts-badge-warn{color:var(--dsw-alias-state-warning-primary);border-color:var(--dsw-alias-state-warning-primary);background:rgba(245,158,11,0.08)}' +
38
+ '.dts-badge-bad{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);background:rgba(239,68,68,0.08)}' +
39
+ '.dts-link{background:none;border:none;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:12px;padding:0;text-decoration:underline}' +
40
+ '.dts-link:hover:not(:disabled){color:var(--dsw-alias-label-primary)}' +
41
+ '.dts-alert{padding:10px 14px;border-radius:8px;font-size:13px}' +
42
+ '.dts-alert-err{background:rgba(239,68,68,0.1);color:var(--dsw-alias-state-error-primary);border:1px solid rgba(239,68,68,0.2)}' +
43
+ '.dts-alert-warn{background:rgba(245,158,11,0.1);color:var(--dsw-alias-state-warning-primary);border:1px solid rgba(245,158,11,0.2)}' +
44
+ '.dts-alert-ok{background:rgba(16,185,129,0.1);color:var(--dsw-alias-state-success-primary);border:1px solid rgba(16,185,129,0.2)}' +
45
+ '.dts-grow{flex:1;min-width:100px}'
46
+
47
+ const setCssId = 'dsh-tts/settings.module.css'
48
+ if (typeof document !== 'undefined' && !document.querySelector('style[data-dsh-plugin="dsh-tts"]')) {
49
+ const tag = document.createElement('style')
50
+ tag.textContent = SET_CSS
51
+ tag.setAttribute('data-dsh-plugin', 'dsh-tts')
52
+ tag.dataset.pluginCss = setCssId
53
+ document.head.appendChild(tag)
54
+ }