opencode-voice-dictation 0.6.0 → 0.6.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.md CHANGED
@@ -15,6 +15,8 @@ escritorio:
15
15
  cursor, sin robar el foco ni simular `Ctrl+V`.
16
16
  - **Lectura natural**: voces neuronales de Edge (45 en español), con respaldo
17
17
  a las voces del sistema si no hay internet.
18
+ - **WhisperX local (avanzado)**: dictado con WhisperX en WSL y GPU NVIDIA. Ver
19
+ [la guía](https://github.com/evious/opencode-voice-dictation/blob/main/docs/whisperx.md).
18
20
  - **Solo Windows**: la app declara `os: ["windows"]`; en otros sistemas aparece
19
21
  como no disponible en vez de fallar.
20
22
 
@@ -151,17 +153,38 @@ dictado, para que el micrófono no capture la voz del altavoz.
151
153
  El texto se inserta en la posición del cursor. Si ya hay texto y no termina en
152
154
  espacio, se añade uno de separación (configurable).
153
155
 
156
+ ## Motores de dictado
157
+
158
+ | Motor | Requisitos | Estado |
159
+ |---|---|---|
160
+ | Windows Voice Typing (Win+H) | Windows 11, internet | Por defecto |
161
+ | System.Speech | Windows, offline | Disponible |
162
+ | WhisperX local (WSL) | WSL2 + Python + CUDA + NVIDIA | Avanzado · [guía](https://github.com/evious/opencode-voice-dictation/blob/main/docs/whisperx.md) |
163
+ | `whisper.cpp` nativo | Binario + modelo | Roadmap |
164
+ | Google Cloud Speech-to-Text | API key, internet | Roadmap |
165
+ | OpenAI (`gpt-4o-transcribe`) | API key, internet | Roadmap |
166
+ | Deepgram / AssemblyAI | API key, internet | Roadmap |
167
+
168
+ El motor se elige en **Ajustes → Dictado por voz → Motor de dictado**.
169
+
170
+ WhisperX es el único que requiere instalación manual: WSL2, un venv de Python
171
+ con `whisperx`/`torch` CUDA y una GPU NVIDIA. El resto funciona sin setup local.
172
+
154
173
  ## Ajustes
155
174
 
156
175
  | Ajuste | Por defecto | Qué hace |
157
176
  |---|---|---|
158
- | Motor de dictado | Windows Voice Typing (Win+H) | Usa el dictado de Windows 11, más preciso. `System.Speech` es la alternativa offline. |
177
+ | Motor de dictado | Windows Voice Typing (Win+H) | Win+H, `System.Speech` offline o `WhisperX local (WSL)`. |
159
178
  | Probar Win+H | — | Envía el atajo y comprueba que Windows abre su barra de dictado. |
160
179
  | Idioma | `es-ES` | Solo para `System.Speech`. Reconocedor instalado en Windows. Si no existe el exacto, usa el mismo idioma base. |
161
180
  | Confianza mínima | 0,4 | Solo para `System.Speech`. Descarta resultados con menor confianza. |
162
181
  | Espacio antes del texto | Sí | Solo para `System.Speech`. Separa el dictado de lo que ya hay escrito. |
163
182
  | Enfocar el prompt al terminar | Sí | Solo para `System.Speech`. Mueve el cursor al prompt tras cada frase. |
164
183
  | Probar micrófono | — | Solo para `System.Speech`. Lista los idiomas instalados y comprueba el micrófono. |
184
+ | Python del entorno | Ruta del venv de WhisperX | Solo WhisperX. Python del venv con `whisperx`. |
185
+ | Modelo (WhisperX) | `large-v3-turbo` | Solo WhisperX. `large-v3` o `medium` si necesitas otra cosa. |
186
+ | Idioma (WhisperX) | Automático | Solo WhisperX. Autodetección o `es`, `en`, `pt`. |
187
+ | Comprobar WhisperX | — | Arranca el worker y valida GPU/modelo. |
165
188
 
166
189
  ### Lectura en voz alta
167
190
 
@@ -187,7 +210,14 @@ PowerShell (SAPI) no hay pausa real: el menú muestra solo «Leer de nuevo» y
187
210
 
188
211
  ## Permisos
189
212
 
190
- El plugin es local y no hace peticiones de red. Para funcionar:
213
+ El plugin funciona en local salvo lo que elijas y use red:
214
+
215
+ - **WhisperX**: escribe el audio en `%TEMP%\opencode-voice-dictation`, lo pasa a
216
+ WSL y ejecuta el worker con tu venv. El audio no sale del equipo.
217
+ - **Edge**: envía el texto a Microsoft para sintetizar la voz (online).
218
+ - **Futuros motores en la nube**: enviarán el audio al proveedor que configures.
219
+
220
+ Para el dictado/lectura con Windows:
191
221
 
192
222
  - Escribe un script `dictation-<hash>.ps1` o `speak-<hash>.ps1` en
193
223
  `%TEMP%\opencode-voice-dictation`.
@@ -214,6 +244,9 @@ bloqueas el micrófono, verás el aviso «No hay ningún micrófono disponible»
214
244
  | «Windows no tiene voces instaladas para la lectura» | Instala una voz en Configuración → Hora e idioma → Voz, o revisa que `System.Speech` esté disponible. El plugin usa primero las voces de Chromium y cae a SAPI si no hay. |
215
245
  | La lectura se corta o no suena | Prueba «Probar voz» en Ajustes; si suena ahí, el problema es el dispositivo de salida de la app. |
216
246
  | «Abre una conversación para leer su último mensaje» | En Home o en un chat nuevo sin mensajes no hay respuesta que leer. |
247
+ | WhisperX se queda en «Transcribiendo…» | El worker no arrancó. Mira el toast: ruta del Python, CUDA o token de Hugging Face. Guía: <https://github.com/evious/opencode-voice-dictation/blob/main/docs/whisperx.md>. |
248
+ | El RPC devuelve 400 o no cargan las voces de Edge | El plugin server no cargó: Ajustes → Extensiones → Plugins → **Reload**, o reinicia el servicio. |
249
+ | WhisperX pide token de Hugging Face | `huggingface-cli login` y acepta los términos del modelo de VAD de `pyannote`. |
217
250
 
218
251
  Los errores del plugin se registran en el log de OpenCode Desktop; los `toast`
219
252
  de la app muestran los más importantes.
@@ -224,6 +257,10 @@ de la app muestran los más importantes.
224
257
  cambiar entre versiones de OpenCode.
225
258
  - `System.Speech` no añade puntuación ni mayúsculas automáticamente y su
226
259
  precisión es inferior a Whisper o al dictado nativo de Windows 11.
260
+ - **WhisperX local** requiere WSL2, Python, CUDA y una GPU NVIDIA. No es
261
+ portable: es un motor avanzado para equipos ya preparados.
262
+ - El primer dictado con WhisperX tarda 10–30 s porque carga el modelo en la GPU;
263
+ los siguientes son rápidos.
227
264
  - El modo Win+H envía la combinación con `keybd_event`. Kaspersky o la
228
265
  protección UIPI de Windows pueden bloquearla; en ese caso usa `System.Speech`.
229
266
  - Con Win+H el texto lo escribe Windows en el campo enfocado: el plugin no
@@ -239,22 +276,56 @@ de la app muestran los más importantes.
239
276
  - Si cambias de sesión durante el dictado, el texto se descarta con un aviso
240
277
  para no escribirlo en la sesión equivocada.
241
278
 
242
- El motor está aislado en `src/gui/engines/`: para cambiar a Whisper local o a
243
- una API en la nube basta con respetar el mismo contrato (`startEngine` y los
244
- eventos `ready` / `final` / `error`).
279
+ El dictado vive en `src/gui` (Win+H y System.Speech) y en el servidor
280
+ (`src/whisper` para WhisperX y `src/edge.ts` para la lectura). Para añadir un
281
+ motor nuevo, consulta la sección siguiente.
282
+
283
+ ## Añadir un motor de dictado nuevo
284
+
285
+ La ventana ya graba el audio (`getUserMedia` + `MediaRecorder`) y lo envía por
286
+ RPC al servidor. Eso es reutilizable para cualquier motor: local o en la nube.
287
+
288
+ 1. **Contrato RPC** (`src/rpc.ts`): añade un método
289
+ `transcribe({ audio, mime, language, ... })` o uno por motor.
290
+ 2. **Servidor** (`src/index.ts`): implementa el método. Para la nube, llama al
291
+ proveedor desde el servidor, nunca desde la ventana.
292
+ 3. **Ajustes** (`src/gui/settings.tsx`): añade el motor al selector y sus campos
293
+ (API key, idioma, modelo).
294
+ 4. **Ventana** (`src/gui/window.tsx`): enruta el modo del motor a la grabación y
295
+ al RPC; inserta con `composer.insert()` o copia con `ctx.system.copy()` si
296
+ quieres dictar hacia otra app.
297
+
298
+ Proveedores previstos:
299
+
300
+ - **Google Cloud Speech-to-Text**: cuenta de servicio o API key,
301
+ `@google-cloud/speech`, audio `WEBM_OPUS` o `LINEAR16` 16 kHz.
302
+ - **OpenAI**: `gpt-4o-transcribe` o `whisper-1`,
303
+ `POST /v1/audio/transcriptions`, API key.
304
+ - **Deepgram / AssemblyAI**: `POST /v1/listen`, API key.
305
+ - **`whisper.cpp` nativo**: binario + modelo `ggml`, sin WSL ni Python; la
306
+ opción más portable para uso local.
307
+
308
+ Las claves de API no deben viajar al renderer. Guárdalas en el store del plugin
309
+ o, mejor, en variables de entorno del servidor.
245
310
 
246
311
  ## Desarrollo
247
312
 
248
313
  ```
249
314
  src/
250
- ├── index.ts Parte servidor: fija el id "voice-dictation"
315
+ ├── index.ts Parte servidor: registra los RPC de Edge y WhisperX
316
+ ├── rpc.ts Contrato RPC compartido (voces, síntesis, dictado)
317
+ ├── edge.ts Voces y síntesis de Edge (servidor)
318
+ ├── whisper/
319
+ │ ├── server.ts Worker persistente, spawn WSL/Node, timeouts
320
+ │ └── worker.py WhisperX en la GPU; protocolo JSON por stdin/stdout
251
321
  ├── gui.tsx Entrada ./gui-experimental
252
322
  └── gui/
253
323
  ├── definition.ts os, stores, i18n es/en
254
324
  ├── contract.ts Ipc: dictado y lectura
255
325
  ├── window.tsx Píldoras, comandos e inserción de texto
256
- ├── main.ts Dictado y voz de respaldo
257
- ├── speak.ts Limpieza de Markdown, troceado y speechSynthesis
326
+ ├── recorder.ts getUserMedia + MediaRecorder para WhisperX
327
+ ├── main.ts Dictado Win+H / System.Speech y voz SAPI
328
+ ├── speak.ts Limpieza de Markdown, troceado, Edge y speechSynthesis
258
329
  ├── settings.tsx Página de ajustes
259
330
  └── engines/
260
331
  ├── script.ts Script de PowerShell para el dictado
@@ -263,10 +334,13 @@ src/
263
334
  └── sapi-speech.ts Voz de respaldo y listado de voces SAPI
264
335
  ```
265
336
 
337
+ La guía de WhisperX está en [`docs/whisperx.md`](docs/whisperx.md).
338
+
266
339
  `npm run typecheck` valida los tipos, `npm run build` genera el bundle CJS y
267
340
  `npm test` ejecuta el smoke test (`test/smoke.cjs`): simula el host de la app,
268
- un `powershell.exe` falso y la API de voz del navegador para comprobar dictado,
269
- lectura, limpieza de Markdown, cancelación, fallback y errores.
341
+ un `powershell.exe` falso, la API de voz del navegador, el RPC de Edge y la
342
+ grabadora de WhisperX para comprobar dictado, lectura, limpieza de Markdown,
343
+ pausa, cancelación, fallback y errores.
270
344
 
271
345
  ## Publicar
272
346
 
@@ -0,0 +1,182 @@
1
+ # Dictado con WhisperX local (Windows + WSL)
2
+
3
+ Guía para configurar el motor **WhisperX local** del plugin en un equipo con
4
+ Windows y WSL2. WhisperX transcribe **audio a texto** (dictado); no es un motor
5
+ de lectura.
6
+
7
+ > Este motor es **avanzado**. Para un usuario sin WSL, Python y GPU NVIDIA, usa
8
+ > `Windows Voice Typing (Win+H)` o un motor en la nube. La integración con
9
+ > `whisper.cpp` nativo (sin WSL) está en el roadmap.
10
+
11
+ ## Requisitos
12
+
13
+ - Windows 10/11 con **WSL2** y una distro **Ubuntu-22.04**.
14
+ - **GPU NVIDIA** con un driver de Windows compatible con WSL CUDA.
15
+ - No instales un driver Linux dentro de WSL: usa el driver de Windows.
16
+ - Comprueba con `nvidia-smi` dentro de WSL.
17
+ - **Python 3.10** y `python3-venv`.
18
+ - **ffmpeg** (WhisperX lo usa para decodificar el audio).
19
+ - Un **venv** con `whisperx`, `torch` con CUDA y `pyannote`.
20
+ - Modelo `large-v3-turbo` (se descarga la primera vez, ~1,5 GB).
21
+ - ~8 GB de VRAM recomendados para `large-v3-turbo` en `float16`.
22
+
23
+ ## 1. Instalar WSL y Ubuntu
24
+
25
+ En PowerShell como administrador:
26
+
27
+ ```powershell
28
+ wsl --install -d Ubuntu-22.04
29
+ ```
30
+
31
+ Reinicia si hace falta y comprueba dentro de WSL:
32
+
33
+ ```bash
34
+ uname -a
35
+ nvidia-smi
36
+ ```
37
+
38
+ `nvidia-smi` debe listar tu GPU. Si no aparece, actualiza el driver NVIDIA de
39
+ Windows a una versión con soporte WSL/CUDA.
40
+
41
+ ## 2. Crear el entorno de WhisperX
42
+
43
+ Dentro de WSL:
44
+
45
+ ```bash
46
+ sudo apt update
47
+ sudo apt install -y python3 python3-venv python3-pip ffmpeg
48
+
49
+ mkdir -p ~/whisperx
50
+ cd ~/whisperx
51
+ python3 -m venv whisperx-env
52
+ source whisperx-env/bin/activate
53
+
54
+ pip install --upgrade pip
55
+ # El índice CUDA debe coincidir con tu driver y la versión de torch.
56
+ # cu128 sirve para torch 2.8; usa cu121/cu124 si tu driver es más antiguo.
57
+ pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu128
58
+ pip install whisperx
59
+ ```
60
+
61
+ Verifica:
62
+
63
+ ```bash
64
+ python3 - <<'PY'
65
+ import torch, whisperx
66
+ print("torch", torch.__version__)
67
+ print("cuda", torch.cuda.is_available())
68
+ print("whisperx", whisperx.__file__)
69
+ PY
70
+ ```
71
+
72
+ Debe imprimir `cuda True`. Si `torch.cuda.is_available()` es `False`, el
73
+ problema es el driver NVIDIA/WSL o la versión de torch, no el plugin.
74
+
75
+ ### Token de Hugging Face (si hace falta)
76
+
77
+ WhisperX descarga modelos de VAD de `pyannote`. Si pide autenticación:
78
+
79
+ 1. Crea un token en <https://huggingface.co/settings/tokens>.
80
+ 2. Acepta los términos del modelo de VAD que use tu versión de whisperx.
81
+ 3. Inicia sesión:
82
+
83
+ ```bash
84
+ huggingface-cli login
85
+ # o exporta HF_TOKEN=...
86
+ ```
87
+
88
+ ## 3. Configurar el plugin
89
+
90
+ En **Ajustes → Dictado por voz**:
91
+
92
+ | Ajuste | Valor |
93
+ |---|---|
94
+ | Motor de dictado | `WhisperX local (WSL)` |
95
+ | Python del entorno | Ruta del Python del venv, por ejemplo `~/whisperx/whisperx-env/bin/python` |
96
+ | Modelo | `large-v3-turbo` |
97
+ | Idioma | `Automático` (o `es`, `en`…) |
98
+ | Comprobar WhisperX | Arranca el worker y confirma que el modelo carga |
99
+
100
+ El valor por defecto del plugin apunta a:
101
+
102
+ ```
103
+ ~/peopleby/voice_to_text/api_voice/demo/whisperx-env/bin/python
104
+ ```
105
+
106
+ Cámbialo por tu ruta si instalaste el venv en otro sitio.
107
+
108
+ Pulsa **Comprobar**. La primera vez tarda 10–30 s porque carga el modelo en la
109
+ GPU. Después, cada dictado de una frase corta tarda ~1–2 s.
110
+
111
+ ## 4. Usar el dictado
112
+
113
+ - Pulsa la píldora del micrófono → `Grabando…`.
114
+ - Vuelve a pulsarla → `Transcribiendo…`.
115
+ - El texto se inserta en el composer con `composer.insert()`.
116
+
117
+ La grabación se hace en la ventana (`getUserMedia` + `MediaRecorder`, WebM/Opus)
118
+ y se envía por RPC al servidor del plugin.
119
+
120
+ ## Cómo funciona por dentro
121
+
122
+ ```
123
+ Ventana (Windows) Servidor OpenCode
124
+ getUserMedia + MediaRecorder RPC whisperTranscribe
125
+ audio WebM base64 ───────► escribe %TEMP%\opencode-voice-dictation\whisper-*.webm
126
+ lanza: wsl.exe -d Ubuntu-22.04 -e bash -lc
127
+ '"$HOME/.../python" "/mnt/c/.../src/whisper/worker.py"'
128
+ texto insertado ◄─────── worker: whisperx large-v3-turbo en CUDA
129
+ ```
130
+
131
+ - El worker es `src/whisper/worker.py` y se mantiene vivo: carga el modelo una
132
+ sola vez.
133
+ - La ruta del audio en Windows (`C:\Users\...\Temp\...`) se convierte a
134
+ `/mnt/c/Users/...` para que WSL la lea.
135
+ - El Python del venv se pasa con `$HOME`, porque una `~` entre comillas no se
136
+ expande en `bash -lc`.
137
+ - Si el worker muere, el RPC devuelve el `stderr` real; ya no se queda en
138
+ «Transcribiendo…».
139
+
140
+ ## Solución de problemas
141
+
142
+ | Síntoma | Causa / solución |
143
+ |---|---|
144
+ | `No such file or directory` al arrancar | La ruta del Python del venv no existe o no empieza por `$HOME`. Usa la ruta absoluta o `~/...`. |
145
+ | `torch.cuda.is_available()` es `False` | Driver NVIDIA de Windows desactualizado o falta soporte WSL. `nvidia-smi` debe funcionar en WSL. |
146
+ | Pide token de Hugging Face | Inicia sesión con `huggingface-cli login` y acepta los términos del modelo de VAD. |
147
+ | Se queda en `Transcribiendo…` | Mira el toast de error y el log del servidor; el worker devuelve el `stderr`. |
148
+ | El RPC devuelve 400 | El plugin server no cargó. Ajustes → Extensiones → Plugins → **Reload**, o reinicia el servicio. |
149
+ | La primera transcripción tarda mucho | Es la carga del modelo. Las siguientes son rápidas. |
150
+ | `Cannot find package 'msedge-tts'` | Es del motor de lectura Edge, no de Whisper. Edge se carga de forma perezosa y no impide Whisper. |
151
+
152
+ ## Privacidad
153
+
154
+ El audio se queda en tu equipo: se escribe en `%TEMP%`, se pasa a WSL y se
155
+ transcribe en local. WhisperX no envía el audio a ningún servicio. Solo se
156
+ descargan los modelos la primera vez.
157
+
158
+ ## Roadmap de motores
159
+
160
+ La idea es añadir más motores de dictado con la misma interfaz:
161
+
162
+ | Motor | Estado | Notas |
163
+ |---|---|---|
164
+ | Windows Voice Typing (Win+H) | Disponible | Cero configuración, online |
165
+ | System.Speech | Disponible | Offline, calidad antigua |
166
+ | WhisperX local (WSL) | Disponible | Avanzado, NVIDIA + WSL |
167
+ | `whisper.cpp` nativo | Planeado | Sin WSL/Python; binario + modelo |
168
+ | Google Cloud Speech-to-Text | Planeado | API key, online |
169
+ | OpenAI (`gpt-4o-transcribe` / `whisper-1`) | Planeado | API key, online |
170
+ | Deepgram / AssemblyAI | Planeado | API key, online |
171
+
172
+ Para añadir un motor en la nube, la ventana ya graba el audio y lo envía por
173
+ RPC. Solo hace falta:
174
+
175
+ 1. Un método RPC por motor (o un `transcribe({ engine, audio, ... })` genérico).
176
+ 2. La llamada al proveedor en el servidor.
177
+ 3. Los ajustes de API key e idioma.
178
+ 4. Mostrar el texto con `composer.insert()` o, si se quiere dictar hacia otra
179
+ app, copiarlo con `ctx.system.copy()`.
180
+
181
+ Las claves de API no deben viajar al renderer: se guardan en el store del plugin
182
+ o, mejor, en variables de entorno del servidor.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-voice-dictation",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Offline voice dictation and read-aloud for OpenCode Desktop on Windows",
5
5
  "license": "MIT",
6
6
  "author": "Fran Carlos <francarlos803@gmail.com>",
@@ -32,7 +32,8 @@
32
32
  "src",
33
33
  "dist",
34
34
  "README.md",
35
- "LICENSE"
35
+ "LICENSE",
36
+ "docs"
36
37
  ],
37
38
  "dependencies": {
38
39
  "msedge-tts": "^2.1.1",
@@ -42,7 +42,10 @@ function toWslPath(path: string): string {
42
42
 
43
43
  function spawnWorker(python: string, model: string): ChildProcess {
44
44
  const env = { ...process.env, WHISPER_MODEL: model, WHISPER_DEVICE: "cuda" }
45
- const command = `"${python}" "${toWslPath(workerPath())}"`
45
+
46
+ // Inside `bash -lc`, a quoted `~` does not expand. Use $HOME instead.
47
+ const pythonArg = python.startsWith("~/") ? `"$HOME/${python.slice(2)}"` : `"${python}"`
48
+ const command = `${pythonArg} "${toWslPath(workerPath())}"`
46
49
 
47
50
  if (process.platform === "win32") {
48
51
  return spawn("wsl.exe", ["-d", "Ubuntu-22.04", "-e", "bash", "-lc", command], {
@@ -119,14 +122,25 @@ export class WhisperEngine {
119
122
 
120
123
  child.on("close", (code) => {
121
124
  clearTimeout(timer)
122
- if (this.status.ready) {
125
+ const detail = this.stderr.trim()
126
+
127
+ if (!this.status.ready) {
128
+ this.status = {
129
+ ready: false,
130
+ model: this.model,
131
+ device: "cuda",
132
+ error: detail || `Whisper worker exited with code ${code ?? "unknown"}`,
133
+ }
134
+ this.readyReject?.(new Error(this.status.error))
135
+ } else {
123
136
  this.status = {
124
137
  ready: false,
125
138
  model: this.model,
126
139
  device: "cuda",
127
- error: this.stderr.trim() || `Whisper worker exited with code ${code ?? "unknown"}`,
140
+ error: detail || `Whisper worker exited with code ${code ?? "unknown"}`,
128
141
  }
129
142
  }
143
+
130
144
  this.failWaiters(new Error(this.status.error || "Whisper worker stopped"))
131
145
  this.child = undefined
132
146
  this.readyPromise = undefined