opencode-voice-dictation 0.6.2 → 0.7.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 +79 -31
- package/dist/gui-experimental.cjs +8 -8
- package/docs/whispercpp.md +143 -0
- package/docs/whisperx.md +3 -2
- package/package.json +4 -2
- package/src/gui/definition.ts +69 -11
- package/src/gui/settings.tsx +186 -17
- package/src/gui/window.tsx +59 -20
- package/src/index.ts +34 -4
- package/src/rpc.ts +80 -0
- package/src/whispercpp/server.ts +277 -0
package/README.md
CHANGED
|
@@ -11,14 +11,16 @@ escritorio:
|
|
|
11
11
|
- **Dictado preciso**: por defecto delega en Windows Voice Typing (Win+H).
|
|
12
12
|
- **Modo offline**: `System.Speech` sigue disponible como alternativa sin
|
|
13
13
|
conexión y sin depender del dictado de Windows.
|
|
14
|
+
- **Linux nativo**: en Ubuntu, `whisper.cpp` transcribe en la CPU sin WSL ni
|
|
15
|
+
Python. Ver [la guía](https://github.com/evious/opencode-voice-dictation/blob/main/docs/whispercpp.md).
|
|
14
16
|
- **Sin portapapeles**: la lectura usa `composer.insert()` en la posición del
|
|
15
17
|
cursor, sin robar el foco ni simular `Ctrl+V`.
|
|
16
18
|
- **Lectura natural**: voces neuronales de Edge (45 en español), con respaldo
|
|
17
19
|
a las voces del sistema si no hay internet.
|
|
18
20
|
- **WhisperX local (avanzado)**: dictado con WhisperX en WSL y GPU NVIDIA. Ver
|
|
19
21
|
[la guía](https://github.com/evious/opencode-voice-dictation/blob/main/docs/whisperx.md).
|
|
20
|
-
- **
|
|
21
|
-
|
|
22
|
+
- **Windows y Linux**: la app declara `os: ["windows", "linux"]` y muestra en
|
|
23
|
+
cada sistema solo los motores que existen.
|
|
22
24
|
|
|
23
25
|
## Instalación rápida
|
|
24
26
|
|
|
@@ -44,8 +46,9 @@ opencode plugin add github:evious/opencode-voice-dictation
|
|
|
44
46
|
|
|
45
47
|
## Compatibilidad
|
|
46
48
|
|
|
47
|
-
Probado con OpenCode Desktop **2.2.6** en Windows 11. La API de
|
|
48
|
-
experimental (`gui-experimental`) y puede cambiar entre versiones
|
|
49
|
+
Probado con OpenCode Desktop **2.2.6** en Windows 11 y Ubuntu 24.04. La API de
|
|
50
|
+
plugins GUI es experimental (`gui-experimental`) y puede cambiar entre versiones
|
|
51
|
+
de OpenCode.
|
|
49
52
|
|
|
50
53
|
## Cómo funciona
|
|
51
54
|
|
|
@@ -71,8 +74,14 @@ píldora enfoca el composer, el proceso `main` envía `Win+H` con `keybd_event`,
|
|
|
71
74
|
Windows escribe directamente en el campo. El plugin no transcribe ni toca el
|
|
72
75
|
texto.
|
|
73
76
|
|
|
77
|
+
En Linux el flujo es distinto: la ventana graba con `getUserMedia` +
|
|
78
|
+
`MediaRecorder`, envía el audio por RPC y el servidor lo pasa a `ffmpeg`
|
|
79
|
+
(16 kHz WAV) y a un `whisper-server` local que mantiene el modelo cargado.
|
|
80
|
+
|
|
74
81
|
## Requisitos
|
|
75
82
|
|
|
83
|
+
**Windows**
|
|
84
|
+
|
|
76
85
|
- OpenCode Desktop en **Windows 10/11**.
|
|
77
86
|
- Node.js 22 o superior para compilar (solo en desarrollo).
|
|
78
87
|
- Un **paquete de idioma de reconocimiento de voz** instalado:
|
|
@@ -80,6 +89,13 @@ texto.
|
|
|
80
89
|
Reconocimiento de voz. No basta con el paquete de visualización.
|
|
81
90
|
- Micrófono predeterminado configurado.
|
|
82
91
|
|
|
92
|
+
**Linux (Ubuntu)**
|
|
93
|
+
|
|
94
|
+
- OpenCode Desktop y `ffmpeg` instalados.
|
|
95
|
+
- `glibc` compatible con el binario oficial de `whisper.cpp` (Ubuntu 22.04+).
|
|
96
|
+
- Micrófono accesible por la app. El primer dictado descarga el binario
|
|
97
|
+
(~10 MB) y el modelo (`base-q5_1`, 57 MB) desde Ajustes.
|
|
98
|
+
|
|
83
99
|
Comprobación rápida en PowerShell:
|
|
84
100
|
|
|
85
101
|
```powershell
|
|
@@ -157,25 +173,27 @@ espacio, se añade uno de separación (configurable).
|
|
|
157
173
|
|
|
158
174
|
| Motor | Requisitos | Estado |
|
|
159
175
|
|---|---|---|
|
|
160
|
-
| Windows Voice Typing (Win+H) | Windows 11, internet | Por defecto |
|
|
176
|
+
| Windows Voice Typing (Win+H) | Windows 11, internet | Por defecto en Windows |
|
|
161
177
|
| System.Speech | Windows, offline | Disponible |
|
|
162
178
|
| 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 |
|
|
179
|
+
| `whisper.cpp` nativo | Linux, CPU, descarga única | Por defecto en Linux · [guía](https://github.com/evious/opencode-voice-dictation/blob/main/docs/whispercpp.md) |
|
|
164
180
|
| Google Cloud Speech-to-Text | API key, internet | Roadmap |
|
|
165
181
|
| OpenAI (`gpt-4o-transcribe`) | API key, internet | Roadmap |
|
|
166
182
|
| Deepgram / AssemblyAI | API key, internet | Roadmap |
|
|
167
183
|
|
|
168
|
-
El motor se elige en **Ajustes → Dictado por voz → Motor de dictado**.
|
|
184
|
+
El motor se elige en **Ajustes → Dictado por voz → Motor de dictado**. En Linux
|
|
185
|
+
solo aparece `whisper.cpp`; en Windows aparecen Win+H, System.Speech y WhisperX.
|
|
169
186
|
|
|
170
|
-
WhisperX
|
|
171
|
-
con
|
|
187
|
+
WhisperX y whisper.cpp son los únicos que requieren instalación: WhisperX pide
|
|
188
|
+
WSL2, un venv de Python con CUDA y una GPU NVIDIA; whisper.cpp se descarga desde
|
|
189
|
+
los propios ajustes. Los motores en la nube del roadmap solo pedirán una API key.
|
|
172
190
|
|
|
173
191
|
## Ajustes
|
|
174
192
|
|
|
175
193
|
| Ajuste | Por defecto | Qué hace |
|
|
176
194
|
|---|---|---|
|
|
177
|
-
| Motor de dictado |
|
|
178
|
-
| Probar Win+H | — | Envía el atajo y comprueba que Windows abre su barra de dictado. |
|
|
195
|
+
| Motor de dictado | Según el sistema | En Windows: Win+H, `System.Speech` o `WhisperX local (WSL)`. En Linux: `whisper.cpp`. |
|
|
196
|
+
| Probar Win+H | — | Solo Windows. Envía el atajo y comprueba que Windows abre su barra de dictado. |
|
|
179
197
|
| Idioma | `es-ES` | Solo para `System.Speech`. Reconocedor instalado en Windows. Si no existe el exacto, usa el mismo idioma base. |
|
|
180
198
|
| Confianza mínima | 0,4 | Solo para `System.Speech`. Descarta resultados con menor confianza. |
|
|
181
199
|
| Espacio antes del texto | Sí | Solo para `System.Speech`. Separa el dictado de lo que ya hay escrito. |
|
|
@@ -185,6 +203,10 @@ con `whisperx`/`torch` CUDA y una GPU NVIDIA. El resto funciona sin setup local.
|
|
|
185
203
|
| Modelo (WhisperX) | `large-v3-turbo` | Solo WhisperX. `large-v3` o `medium` si necesitas otra cosa. |
|
|
186
204
|
| Idioma (WhisperX) | Automático | Solo WhisperX. Autodetección o `es`, `en`, `pt`. |
|
|
187
205
|
| Comprobar WhisperX | — | Arranca el worker y valida GPU/modelo. |
|
|
206
|
+
| Modelo (whisper.cpp) | `base-q5_1` | Solo Linux. `base-q5_1` (57 MB) o `small-q5_1` (180 MB, más preciso). |
|
|
207
|
+
| Idioma (whisper.cpp) | Automático | Solo Linux. Autodetección o `es`, `en`, `pt`. |
|
|
208
|
+
| Descargar motor | — | Solo Linux. Baja el binario oficial y el modelo a `~/.local/share/opencode-voice-dictation`. |
|
|
209
|
+
| Comprobar whisper.cpp | — | Solo Linux. Indica si el servidor y el modelo están instalados. |
|
|
188
210
|
|
|
189
211
|
### Lectura en voz alta
|
|
190
212
|
|
|
@@ -214,10 +236,13 @@ El plugin funciona en local salvo lo que elijas y use red:
|
|
|
214
236
|
|
|
215
237
|
- **WhisperX**: escribe el audio en `%TEMP%\opencode-voice-dictation`, lo pasa a
|
|
216
238
|
WSL y ejecuta el worker con tu venv. El audio no sale del equipo.
|
|
239
|
+
- **whisper.cpp**: escribe el audio en `%TEMP%`, lo convierte con `ffmpeg` y lo
|
|
240
|
+
envía a un servidor local en `127.0.0.1`. El audio no sale del equipo. La
|
|
241
|
+
primera vez descarga el binario y el modelo desde GitHub/Hugging Face.
|
|
217
242
|
- **Edge**: envía el texto a Microsoft para sintetizar la voz (online).
|
|
218
243
|
- **Futuros motores en la nube**: enviarán el audio al proveedor que configures.
|
|
219
244
|
|
|
220
|
-
|
|
245
|
+
En **Windows**, para el dictado/lectura:
|
|
221
246
|
|
|
222
247
|
- Escribe un script `dictation-<hash>.ps1` o `speak-<hash>.ps1` en
|
|
223
248
|
`%TEMP%\opencode-voice-dictation`.
|
|
@@ -228,6 +253,10 @@ Por eso Windows y antivirus como Kaspersky pueden pedir permiso la primera vez:
|
|
|
228
253
|
es esperado. Elige **Permitir ahora** y marca **Recordar mi elección**. Si
|
|
229
254
|
bloqueas el micrófono, verás el aviso «No hay ningún micrófono disponible».
|
|
230
255
|
|
|
256
|
+
En **Linux**, la app abre el micrófono con `getUserMedia`; el navegador/Electron
|
|
257
|
+
puede pedir permiso la primera vez. El servidor solo ejecuta `ffmpeg` y el
|
|
258
|
+
binario descargado.
|
|
259
|
+
|
|
231
260
|
## Solución de problemas
|
|
232
261
|
|
|
233
262
|
| Síntoma | Causa / solución |
|
|
@@ -239,12 +268,15 @@ bloqueas el micrófono, verás el aviso «No hay ningún micrófono disponible»
|
|
|
239
268
|
| «Windows no tiene ningún reconocedor de voz instalado» | Mismo caso: no hay ningún idioma de voz instalado. |
|
|
240
269
|
| «No hay ningún micrófono disponible» | Revisa Configuración → Sistema → Sonido → Entrada y el micrófono predeterminado. |
|
|
241
270
|
| El motor se detiene inesperadamente | Otro proceso puede estar usando el micrófono en modo exclusivo. Ejecuta la prueba de micrófono en Ajustes. |
|
|
242
|
-
| La píldora no aparece | Comprueba que el plugin está en el `opencode.json` global, que la app corre en Windows y que el plugin figura como cargado en Ajustes → Extensiones → Plugins. |
|
|
243
|
-
| No transcribe nada | Sube el nivel de entrada del micrófono y baja la confianza mínima a 0,2. |
|
|
244
|
-
| «
|
|
271
|
+
| La píldora no aparece | Comprueba que el plugin está en el `opencode.json` global, que la app corre en Windows/Linux y que el plugin figura como cargado en Ajustes → Extensiones → Plugins. |
|
|
272
|
+
| No transcribe nada | Sube el nivel de entrada del micrófono y, en `System.Speech`, baja la confianza mínima a 0,2. |
|
|
273
|
+
| «El sistema no tiene voces instaladas para la lectura» | Instala una voz del sistema, o usa Edge (online). El plugin prueba las voces de Chromium y cae a SAPI en Windows si no hay. |
|
|
245
274
|
| 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. |
|
|
246
275
|
| «Abre una conversación para leer su último mensaje» | En Home o en un chat nuevo sin mensajes no hay respuesta que leer. |
|
|
247
276
|
| 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>. |
|
|
277
|
+
| whisper.cpp dice «not installed» | Entra en Ajustes → Dictado por voz → **Descargar motor**. Requiere internet la primera vez. |
|
|
278
|
+
| whisper.cpp falla al arrancar | Comprueba que `ffmpeg` está en el `PATH` (`ffmpeg -version`) y que el binario es compatible con tu `glibc` (Ubuntu 22.04+). |
|
|
279
|
+
| whisper.cpp tarda mucho | Usa `base-q5_1` en portátiles modestos; `small-q5_1` necesita más CPU y memoria. La primera transcripción carga el modelo. |
|
|
248
280
|
| 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
281
|
| WhisperX pide token de Hugging Face | `huggingface-cli login` y acepta los términos del modelo de VAD de `pyannote`. |
|
|
250
282
|
|
|
@@ -261,14 +293,22 @@ de la app muestran los más importantes.
|
|
|
261
293
|
portable: es un motor avanzado para equipos ya preparados.
|
|
262
294
|
- El primer dictado con WhisperX tarda 10–30 s porque carga el modelo en la GPU;
|
|
263
295
|
los siguientes son rápidos.
|
|
296
|
+
- **whisper.cpp** usa la CPU. En un portátil muy modesto `base-q5_1` va en tiempo
|
|
297
|
+
real o algo más lento; `small-q5_1` mejora la precisión a cambio de CPU y
|
|
298
|
+
memoria. No hay GPU por ahora.
|
|
299
|
+
- El binario oficial de `whisper.cpp` es para Ubuntu (glibc reciente). En
|
|
300
|
+
distros antiguas o arquitecturas raras habría que compilarlo a mano.
|
|
301
|
+
- El primer uso descarga ~10 MB de binario y 57–180 MB de modelo. Después
|
|
302
|
+
funciona sin internet.
|
|
264
303
|
- El modo Win+H envía la combinación con `keybd_event`. Kaspersky o la
|
|
265
304
|
protección UIPI de Windows pueden bloquearla; en ese caso usa `System.Speech`.
|
|
266
305
|
- Con Win+H el texto lo escribe Windows en el campo enfocado: el plugin no
|
|
267
306
|
puede descartarlo si cambias de sesión ni limpiarlo.
|
|
268
|
-
- No hay resultados parciales con `System.Speech
|
|
269
|
-
|
|
270
|
-
- La lectura usa las voces del sistema; si Electron no las expone, el
|
|
271
|
-
por PowerShell usa las voces SAPI, con una latencia de arranque de
|
|
307
|
+
- No hay resultados parciales con `System.Speech` ni con whisper.cpp: el texto
|
|
308
|
+
aparece al terminar la grabación.
|
|
309
|
+
- La lectura usa las voces del sistema; si Electron no las expone, en Windows el
|
|
310
|
+
respaldo por PowerShell usa las voces SAPI, con una latencia de arranque de
|
|
311
|
+
~0,5 s. En Linux, si no hay voces del sistema, usa Edge (online).
|
|
272
312
|
- La lectura empieza con el mensaje tal como esté en ese momento: si el agente
|
|
273
313
|
sigue escribiendo, leerá la versión parcial.
|
|
274
314
|
- Una sola dictación a la vez (hay un micrófono). Si empiezas en otra ventana,
|
|
@@ -277,8 +317,8 @@ de la app muestran los más importantes.
|
|
|
277
317
|
para no escribirlo en la sesión equivocada.
|
|
278
318
|
|
|
279
319
|
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
|
|
281
|
-
motor nuevo, consulta la sección siguiente.
|
|
320
|
+
(`src/whisper` para WhisperX, `src/whispercpp` para Linux y `src/edge.ts` para la
|
|
321
|
+
lectura). Para añadir un motor nuevo, consulta la sección siguiente.
|
|
282
322
|
|
|
283
323
|
## Añadir un motor de dictado nuevo
|
|
284
324
|
|
|
@@ -289,9 +329,11 @@ RPC al servidor. Eso es reutilizable para cualquier motor: local o en la nube.
|
|
|
289
329
|
`transcribe({ audio, mime, language, ... })` o uno por motor.
|
|
290
330
|
2. **Servidor** (`src/index.ts`): implementa el método. Para la nube, llama al
|
|
291
331
|
proveedor desde el servidor, nunca desde la ventana.
|
|
292
|
-
3. **
|
|
332
|
+
3. **Capacidades** (`src/index.ts`): añade el motor a la lista que devuelve
|
|
333
|
+
`capabilities` para que la ventana lo ofrezca solo donde exista.
|
|
334
|
+
4. **Ajustes** (`src/gui/settings.tsx`): añade el motor al selector y sus campos
|
|
293
335
|
(API key, idioma, modelo).
|
|
294
|
-
|
|
336
|
+
5. **Ventana** (`src/gui/window.tsx`): enruta el modo del motor a la grabación y
|
|
295
337
|
al RPC; inserta con `composer.insert()` o copia con `ctx.system.copy()` si
|
|
296
338
|
quieres dictar hacia otra app.
|
|
297
339
|
|
|
@@ -302,8 +344,9 @@ Proveedores previstos:
|
|
|
302
344
|
- **OpenAI**: `gpt-4o-transcribe` o `whisper-1`,
|
|
303
345
|
`POST /v1/audio/transcriptions`, API key.
|
|
304
346
|
- **Deepgram / AssemblyAI**: `POST /v1/listen`, API key.
|
|
305
|
-
|
|
306
|
-
|
|
347
|
+
|
|
348
|
+
`whisper.cpp` ya está implementado en `src/whispercpp/server.ts` como referencia
|
|
349
|
+
de un motor local: descarga, proceso persistente, conversión de audio y RPC.
|
|
307
350
|
|
|
308
351
|
Las claves de API no deben viajar al renderer. Guárdalas en el store del plugin
|
|
309
352
|
o, mejor, en variables de entorno del servidor.
|
|
@@ -312,18 +355,20 @@ o, mejor, en variables de entorno del servidor.
|
|
|
312
355
|
|
|
313
356
|
```
|
|
314
357
|
src/
|
|
315
|
-
├── index.ts Parte servidor: registra los RPC
|
|
358
|
+
├── index.ts Parte servidor: registra los RPC y capabilities
|
|
316
359
|
├── rpc.ts Contrato RPC compartido (voces, síntesis, dictado)
|
|
317
360
|
├── edge.ts Voces y síntesis de Edge (servidor)
|
|
318
361
|
├── whisper/
|
|
319
362
|
│ ├── server.ts Worker persistente, spawn WSL/Node, timeouts
|
|
320
363
|
│ └── worker.py WhisperX en la GPU; protocolo JSON por stdin/stdout
|
|
364
|
+
├── whispercpp/
|
|
365
|
+
│ └── server.ts Linux: descarga, whisper-server persistente, ffmpeg
|
|
321
366
|
├── gui.tsx Entrada ./gui-experimental
|
|
322
367
|
└── gui/
|
|
323
368
|
├── definition.ts os, stores, i18n es/en
|
|
324
369
|
├── contract.ts Ipc: dictado y lectura
|
|
325
370
|
├── window.tsx Píldoras, comandos e inserción de texto
|
|
326
|
-
├── recorder.ts getUserMedia + MediaRecorder para WhisperX
|
|
371
|
+
├── recorder.ts getUserMedia + MediaRecorder para WhisperX/whisper.cpp
|
|
327
372
|
├── main.ts Dictado Win+H / System.Speech y voz SAPI
|
|
328
373
|
├── speak.ts Limpieza de Markdown, troceado, Edge y speechSynthesis
|
|
329
374
|
├── settings.tsx Página de ajustes
|
|
@@ -334,13 +379,16 @@ src/
|
|
|
334
379
|
└── sapi-speech.ts Voz de respaldo y listado de voces SAPI
|
|
335
380
|
```
|
|
336
381
|
|
|
337
|
-
|
|
382
|
+
Guías:
|
|
383
|
+
|
|
384
|
+
- WhisperX (Windows + WSL): [`docs/whisperx.md`](docs/whisperx.md).
|
|
385
|
+
- whisper.cpp (Linux nativo): [`docs/whispercpp.md`](docs/whispercpp.md).
|
|
338
386
|
|
|
339
387
|
`npm run typecheck` valida los tipos, `npm run build` genera el bundle CJS y
|
|
340
388
|
`npm test` ejecuta el smoke test (`test/smoke.cjs`): simula el host de la app,
|
|
341
|
-
un `powershell.exe` falso, la API de voz del navegador, el RPC de Edge y
|
|
342
|
-
|
|
343
|
-
pausa, cancelación, fallback y errores.
|
|
389
|
+
un `powershell.exe` falso, la API de voz del navegador, el RPC de Edge y las
|
|
390
|
+
grabadoras de WhisperX/whisper.cpp para comprobar dictado, lectura, limpieza de
|
|
391
|
+
Markdown, pausa, cancelación, fallback, cambio de motor por sistema y errores.
|
|
344
392
|
|
|
345
393
|
## Publicar
|
|
346
394
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const $=require("@opencode/plugin/gui"),c=require("effect"),K=require("node:child_process"),Q=require("node:crypto"),j=require("node:fs"),te=require("node:os"),M=require("node:path"),d=require("solid-js/web"),R=require("solid-js"),Ce=$.Plugin.define({os:["windows"],stores:{prefs:$.Store.global(c.Schema.Struct({locale:c.Schema.String,minConfidence:c.Schema.Number,trailingSpace:c.Schema.Boolean,autoFocus:c.Schema.Boolean}),{locale:"es-ES",minConfidence:.4,trailingSpace:!0,autoFocus:!0}),speech:$.Store.global(c.Schema.Struct({voiceURI:c.Schema.String,rate:c.Schema.Number,skipCode:c.Schema.Boolean}),{voiceURI:"",rate:1,skipCode:!0}),engine:$.Store.global(c.Schema.Struct({mode:c.Schema.String}),{mode:"windows"}),tts:$.Store.global(c.Schema.Struct({engine:c.Schema.String,voice:c.Schema.String}),{engine:"edge",voice:"es-ES-XimenaNeural"}),whisper:$.Store.global(c.Schema.Struct({python:c.Schema.String,model:c.Schema.String,language:c.Schema.String}),{python:"~/peopleby/voice_to_text/api_voice/demo/whisperx-env/bin/python",model:"large-v3-turbo",language:""})},i18n:{en:{"pill.idle":"Dictate","pill.starting":"Starting…","pill.listening":"Listening…","pill.dictating":"Dictating…","pill.error":"Retry dictation","pill.title":"Voice dictation","pill.winH":"Open Windows voice typing (Win+H)","pill.stop":"Stop dictation","command.toggle":"Toggle voice dictation","command.toggle.description":"Dictate into the prompt with the selected engine","command.configure":"Voice dictation settings","error.title":"Voice dictation","error.language":"The speech language {locale} is not installed. Available: {available}.","error.none":"Windows has no speech recognizer installed. Settings › Time & language › Language › Speech.","error.mic":"No microphone is available for speech recognition.","error.system":"System.Speech is not available in this Windows installation.","error.exit":"The dictation engine stopped unexpectedly.","error.unknown":"Dictation failed ({code}).","toast.discarded":"Dictation discarded: the active session changed.","settings.title":"Voice dictation","settings.description":"Offline dictation with the Windows speech recognizer. The text is inserted into the prompt; it is never sent automatically.","settings.section":"Dictation","settings.engine":"Dictation engine","settings.engine.description":"Windows uses Windows 11 voice typing (Win+H), which is more accurate. System.Speech works offline but is less precise.","engine.windows":"Windows Voice Typing (Win+H)","engine.system":"System.Speech (offline)","engine.whisper":"WhisperX local (WSL)","pill.recording":"Recording…","pill.transcribing":"Transcribing…","pill.whisper":"Record and transcribe with WhisperX","error.whisper":"WhisperX could not transcribe the audio.","whisper.section":"WhisperX (local)","whisper.python":"Environment Python","whisper.python.description":"Python of the venv that has whisperx installed.","whisper.model":"Model","whisper.model.description":"large-v3-turbo is the fastest with good accuracy on a GPU.","whisper.language":"Language","whisper.language.description":"Automatic detects the language of each recording.","whisper.language.auto":"Automatic","whisper.test":"Check WhisperX","whisper.test.description":"Starts the worker and reports whether the model loads.","whisper.test.run":"Check","whisper.test.ready":"WhisperX ready: {model} ({device})","whisper.test.error":"WhisperX is not ready: {error}","whisper.empty":"WhisperX returned no text.","settings.winh":"Test Win+H","settings.winh.description":"Sends the Windows voice typing shortcut. If the floating bar appears, it works.","settings.winh.run":"Test Win+H","settings.winh.sent":"Win+H sent. If the Windows dictation bar did not appear, check Settings › Accessibility › Voice typing.","error.winh":"Windows voice typing could not be triggered.","settings.language":"Language","settings.language.description":"A speech recognizer installed in Windows. Accents and punctuation depend on it.","settings.confidence":"Minimum confidence","settings.confidence.description":"Results below this threshold are ignored to avoid noise.","confidence.low":"Low (0.2)","confidence.normal":"Normal (0.4)","confidence.high":"High (0.6)","settings.space":"Add a space before the text","settings.space.description":"Separates the dictation from what is already in the prompt.","settings.focus":"Focus the prompt after dictating","settings.focus.description":"Moves the cursor to the prompt so you can review or send it.","settings.test.section":"Diagnostics","settings.test":"Test microphone","settings.test.description":"Checks installed recognizers and the default microphone.","settings.test.run":"Run test","settings.test.success":"Speech recognition ready: {locale}","settings.test.failed":"Not ready: {reason}","common.reason.no-recognizers":"no speech language installed","common.reason.no-microphone":"no microphone available","read.idle":"Read","read.speaking":"Reading…","read.paused":"Paused","read.pause":"Pause","read.resume":"Resume","read.restart":"Read again","read.stop":"Stop reading","read.title":"Read the last message aloud","command.read":"Read the last message aloud","command.read.description":"Speaks the last reply with a system voice","read.noSession":"Open a conversation to read its last message.","read.empty":"The last message has no text to read.","read.error":"The last message could not be read.","read.noVoices":"Windows has no voices installed for reading.","speech.section":"Read aloud","speech.engine":"Voice engine","speech.engine.description":"Edge neural sounds much more natural but needs internet. System uses the installed Windows voices, offline.","speech.engine.edge":"Edge neural (online)","speech.engine.system":"System (offline)","error.edge":"The Edge voice could not be used. Check the connection or switch to the system voice.","speech.edge.error":"Edge voices could not be loaded","speech.voice":"Voice","speech.voice.description":"The voice used for reading.","speech.voice.auto":"Automatic","speech.rate":"Speed","speech.rate.description":"How fast the text is spoken.","speech.rate.slow":"0.8×","speech.rate.normal":"1×","speech.rate.fast":"1.2×","speech.rate.faster":"1.5×","speech.skipCode":"Skip code blocks","speech.skipCode.description":"Read only the prose; code, paths and commands are left out.","speech.preview":"Test voice","speech.preview.description":"Speaks a short sample with the current voice and speed.","speech.preview.text":"Hello, this is a reading test."},es:{"pill.idle":"Dictar","pill.starting":"Iniciando…","pill.listening":"Escuchando…","pill.dictating":"Dictando…","pill.error":"Reintentar dictado","pill.title":"Dictado por voz","pill.winH":"Abrir el dictado de Windows (Win+H)","pill.stop":"Detener dictado","command.toggle":"Alternar dictado por voz","command.toggle.description":"Dicta en el prompt con el motor elegido","command.configure":"Ajustes de dictado por voz","error.title":"Dictado por voz","error.language":"El idioma de reconocimiento {locale} no está instalado. Disponibles: {available}.","error.none":"Windows no tiene ningún reconocedor de voz instalado. Configuración › Hora e idioma › Idioma › Voz.","error.mic":"No hay ningún micrófono disponible para el reconocimiento de voz.","error.system":"System.Speech no está disponible en esta instalación de Windows.","error.exit":"El motor de dictado se detuvo inesperadamente.","error.unknown":"El dictado falló ({code}).","toast.discarded":"Dictado descartado: la sesión activa cambió.","settings.title":"Dictado por voz","settings.description":"Dictado offline con el reconocedor de voz de Windows. El texto se inserta en el prompt; nunca se envía solo.","settings.section":"Dictado","settings.engine":"Motor de dictado","settings.engine.description":"Windows usa el dictado de Windows 11 (Win+H), más preciso. System.Speech funciona sin Windows 11 pero es menos exacto.","engine.windows":"Windows Voice Typing (Win+H)","engine.system":"System.Speech (offline)","engine.whisper":"WhisperX local (WSL)","pill.recording":"Grabando…","pill.transcribing":"Transcribiendo…","pill.whisper":"Grabar y transcribir con WhisperX","error.whisper":"WhisperX no pudo transcribir el audio.","whisper.section":"WhisperX (local)","whisper.python":"Python del entorno","whisper.python.description":"Python del venv que tiene whisperx instalado.","whisper.model":"Modelo","whisper.model.description":"large-v3-turbo es el más rápido con buena precisión en GPU.","whisper.language":"Idioma","whisper.language.description":"Automático detecta el idioma de cada grabación.","whisper.language.auto":"Automático","whisper.test":"Comprobar WhisperX","whisper.test.description":"Arranca el worker e indica si el modelo carga.","whisper.test.run":"Comprobar","whisper.test.ready":"WhisperX listo: {model} ({device})","whisper.test.error":"WhisperX no está listo: {error}","whisper.empty":"WhisperX no devolvió texto.","settings.winh":"Probar Win+H","settings.winh.description":"Envía el atajo del dictado de Windows. Si aparece la barra flotante, funciona.","settings.winh.run":"Probar Win+H","settings.winh.sent":"Win+H enviado. Si no apareció la barra, revisa Configuración › Accesibilidad › Dictado por voz.","settings.language":"Idioma","settings.language.description":"Un reconocedor instalado en Windows. Los acentos y la puntuación dependen de él.","settings.confidence":"Confianza mínima","settings.confidence.description":"Los resultados por debajo de este umbral se ignoran para evitar ruido.","confidence.low":"Baja (0,2)","confidence.normal":"Normal (0,4)","confidence.high":"Alta (0,6)","settings.space":"Añadir un espacio antes del texto","settings.space.description":"Separa el dictado de lo que ya hay en el prompt.","settings.focus":"Enfocar el prompt al terminar","settings.focus.description":"Mueve el cursor al prompt para revisar o enviar el texto.","settings.test.section":"Diagnóstico","settings.test":"Probar micrófono","settings.test.description":"Comprueba los reconocedores instalados y el micrófono predeterminado.","settings.test.run":"Ejecutar prueba","settings.test.success":"Reconocimiento de voz listo: {locale}","settings.test.failed":"No está listo: {reason}","common.reason.no-recognizers":"no hay ningún idioma de voz instalado","common.reason.no-microphone":"no hay micrófono disponible","read.idle":"Leer","read.speaking":"Leyendo…","read.paused":"Pausado","read.pause":"Pausar","read.resume":"Reanudar","read.restart":"Leer de nuevo","read.stop":"Detener lectura","read.title":"Leer en voz alta el último mensaje","command.read":"Leer en voz alta el último mensaje","command.read.description":"Reproduce la última respuesta con una voz del sistema","read.noSession":"Abre una conversación para leer su último mensaje.","read.empty":"El último mensaje no tiene texto para leer.","read.error":"No se pudo leer el último mensaje.","read.noVoices":"Windows no tiene voces instaladas para la lectura.","speech.section":"Lectura en voz alta","speech.engine":"Motor de voz","speech.engine.description":"Edge neuronal suena mucho más natural pero necesita internet. Sistema usa las voces instaladas de Windows, sin conexión.","speech.engine.edge":"Edge neuronal (online)","speech.engine.system":"Sistema (offline)","error.edge":"No se pudo usar la voz de Edge. Revisa la conexión o cambia a la voz del sistema.","speech.edge.error":"No se pudieron cargar las voces de Edge","speech.voice":"Voz","speech.voice.description":"La voz que se usa para leer.","speech.voice.auto":"Automática","speech.rate":"Velocidad","speech.rate.description":"A qué ritmo se lee el texto.","speech.rate.slow":"0,8×","speech.rate.normal":"1×","speech.rate.fast":"1,2×","speech.rate.faster":"1,5×","speech.skipCode":"Ignorar bloques de código","speech.skipCode.description":"Lee solo la prosa; omite código, rutas y comandos.","speech.preview":"Probar voz","speech.preview.description":"Lee una frase corta con la voz y velocidad actuales.","speech.preview.text":"Hola, esta es una prueba de lectura."}}}),ie=$.Ipc.define({id:"voice-dictation.dictation",state:c.Schema.Struct({status:c.Schema.Literals(["idle","starting","listening","error"]),error:c.Schema.String,locale:c.Schema.String}),methods:{start:{input:c.Schema.Struct({locale:c.Schema.String,minConfidence:c.Schema.Number})},stop:{},diagnose:{output:c.Schema.Struct({locales:c.Schema.Array(c.Schema.String),microphone:c.Schema.Boolean,selected:c.Schema.String,error:c.Schema.String})}},events:{final:c.Schema.Struct({text:c.Schema.String,confidence:c.Schema.Number}),error:c.Schema.Struct({code:c.Schema.String,detail:c.Schema.String,available:c.Schema.Array(c.Schema.String)})}}),le=$.Ipc.define({id:"voice-dictation.speech",methods:{speak:{input:c.Schema.Struct({text:c.Schema.String,rate:c.Schema.Number,voice:c.Schema.String})},stop:{},voices:{output:c.Schema.Array(c.Schema.String)}},events:{done:c.Schema.Struct({ok:c.Schema.Boolean})}}),ne=$.Ipc.define({id:"voice-dictation.voice-typing",methods:{toggle:{}}}),oe=String.raw`param(
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const E=require("@opencode/plugin/gui"),p=require("effect"),ee=require("node:child_process"),re=require("node:crypto"),M=require("node:fs"),ne=require("node:os"),G=require("node:path"),a=require("solid-js/web"),S=require("solid-js"),ke=E.Plugin.define({os:["windows","linux"],stores:{prefs:E.Store.global(p.Schema.Struct({locale:p.Schema.String,minConfidence:p.Schema.Number,trailingSpace:p.Schema.Boolean,autoFocus:p.Schema.Boolean}),{locale:"es-ES",minConfidence:.4,trailingSpace:!0,autoFocus:!0}),speech:E.Store.global(p.Schema.Struct({voiceURI:p.Schema.String,rate:p.Schema.Number,skipCode:p.Schema.Boolean}),{voiceURI:"",rate:1,skipCode:!0}),engine:E.Store.global(p.Schema.Struct({mode:p.Schema.String}),{mode:"windows"}),tts:E.Store.global(p.Schema.Struct({engine:p.Schema.String,voice:p.Schema.String}),{engine:"edge",voice:"es-ES-XimenaNeural"}),whisper:E.Store.global(p.Schema.Struct({python:p.Schema.String,model:p.Schema.String,language:p.Schema.String}),{python:"~/peopleby/voice_to_text/api_voice/demo/whisperx-env/bin/python",model:"large-v3-turbo",language:""}),whispercpp:E.Store.global(p.Schema.Struct({model:p.Schema.String,language:p.Schema.String}),{model:"base-q5_1",language:"auto"})},i18n:{en:{"pill.idle":"Dictate","pill.starting":"Starting…","pill.listening":"Listening…","pill.dictating":"Dictating…","pill.error":"Retry dictation","pill.title":"Voice dictation","pill.winH":"Open Windows voice typing (Win+H)","pill.stop":"Stop dictation","command.toggle":"Toggle voice dictation","command.toggle.description":"Dictate into the prompt with the selected engine","command.configure":"Voice dictation settings","error.title":"Voice dictation","error.language":"The speech language {locale} is not installed. Available: {available}.","error.none":"Windows has no speech recognizer installed. Settings › Time & language › Language › Speech.","error.mic":"No microphone is available for speech recognition.","error.system":"System.Speech is not available in this Windows installation.","error.exit":"The dictation engine stopped unexpectedly.","error.unknown":"Dictation failed ({code}).","toast.discarded":"Dictation discarded: the active session changed.","settings.title":"Voice dictation","settings.description":"Offline dictation on Windows and Linux. The text is inserted into the prompt; it is never sent automatically.","settings.section":"Dictation","settings.engine":"Dictation engine","settings.engine.description":"Windows offers Win+H and System.Speech; Linux uses whisper.cpp locally.","engine.windows":"Windows Voice Typing (Win+H)","engine.system":"System.Speech (offline)","engine.whisper":"WhisperX local (WSL)","engine.whispercpp":"whisper.cpp (local, native)","pill.recording":"Recording…","pill.transcribing":"Transcribing…","pill.whisper":"Record and transcribe with WhisperX","error.whisper":"WhisperX could not transcribe the audio.","whisper.section":"WhisperX (local)","whisper.python":"Environment Python","whisper.python.description":"Python of the venv that has whisperx installed.","whisper.model":"Model","whisper.model.description":"large-v3-turbo is the fastest with good accuracy on a GPU.","whisper.language":"Language","whisper.language.description":"Automatic detects the language of each recording.","whisper.language.auto":"Automatic","whisper.test":"Check WhisperX","whisper.test.description":"Starts the worker and reports whether the model loads.","whisper.test.run":"Check","whisper.test.ready":"WhisperX ready: {model} ({device})","whisper.test.error":"WhisperX is not ready: {error}","whisper.empty":"WhisperX returned no text.","pill.whispercpp":"Record and transcribe with whisper.cpp","error.whispercpp":"whisper.cpp could not transcribe the audio.","whispercpp.section":"whisper.cpp (local, native)","whispercpp.description":"Runs on the CPU without WSL or Python. The first time you need to download the server (~10 MB) and the model.","whispercpp.model":"Model","whispercpp.model.description":"base-q5_1 is the lightest model (57 MB) and works on modest laptops. small-q5_1 is more accurate but uses more CPU and memory.","whispercpp.language":"Language","whispercpp.language.description":"Automatic detects the language of each recording.","whispercpp.language.auto":"Automatic","whispercpp.install":"Download engine","whispercpp.install.description":"Downloads the official whisper.cpp server and the selected model to ~/.local/share/opencode-voice-dictation. Needs internet once.","whispercpp.install.run":"Download","whispercpp.install.busy":"Downloading…","whispercpp.install.ok":"whisper.cpp installed: {model}","whispercpp.install.error":"Could not install whisper.cpp: {error}","whispercpp.test":"Check whisper.cpp","whispercpp.test.description":"Reports whether the server and the selected model are installed.","whispercpp.test.run":"Check","whispercpp.test.ready":"whisper.cpp ready: {model}","whispercpp.test.missing":"whisper.cpp is not installed. Download the engine first.","whispercpp.test.error":"whisper.cpp is not ready: {error}","whispercpp.empty":"whisper.cpp returned no text.","whispercpp.linux":"whisper.cpp is only available on Linux. On Windows use Win+H, System.Speech or WhisperX.","settings.winh":"Test Win+H","settings.winh.description":"Sends the Windows voice typing shortcut. If the floating bar appears, it works.","settings.winh.run":"Test Win+H","settings.winh.sent":"Win+H sent. If the Windows dictation bar did not appear, check Settings › Accessibility › Voice typing.","error.winh":"Windows voice typing could not be triggered.","settings.language":"Language","settings.language.description":"A speech recognizer installed in Windows. Accents and punctuation depend on it.","settings.confidence":"Minimum confidence","settings.confidence.description":"Results below this threshold are ignored to avoid noise.","confidence.low":"Low (0.2)","confidence.normal":"Normal (0.4)","confidence.high":"High (0.6)","settings.space":"Add a space before the text","settings.space.description":"Separates the dictation from what is already in the prompt.","settings.focus":"Focus the prompt after dictating","settings.focus.description":"Moves the cursor to the prompt so you can review or send it.","settings.test.section":"Diagnostics","settings.test":"Test microphone","settings.test.description":"Checks installed recognizers and the default microphone.","settings.test.run":"Run test","settings.test.success":"Speech recognition ready: {locale}","settings.test.failed":"Not ready: {reason}","common.reason.no-recognizers":"no speech language installed","common.reason.no-microphone":"no microphone available","read.idle":"Read","read.speaking":"Reading…","read.paused":"Paused","read.pause":"Pause","read.resume":"Resume","read.restart":"Read again","read.stop":"Stop reading","read.title":"Read the last message aloud","command.read":"Read the last message aloud","command.read.description":"Speaks the last reply with a system voice","read.noSession":"Open a conversation to read its last message.","read.empty":"The last message has no text to read.","read.error":"The last message could not be read.","read.noVoices":"The system has no voices installed for reading.","speech.section":"Read aloud","speech.engine":"Voice engine","speech.engine.description":"Edge neural sounds much more natural but needs internet. System uses the installed Windows voices, offline.","speech.engine.edge":"Edge neural (online)","speech.engine.system":"System (offline)","error.edge":"The Edge voice could not be used. Check the connection or switch to the system voice.","speech.edge.error":"Edge voices could not be loaded","speech.voice":"Voice","speech.voice.description":"The voice used for reading.","speech.voice.auto":"Automatic","speech.rate":"Speed","speech.rate.description":"How fast the text is spoken.","speech.rate.slow":"0.8×","speech.rate.normal":"1×","speech.rate.fast":"1.2×","speech.rate.faster":"1.5×","speech.skipCode":"Skip code blocks","speech.skipCode.description":"Read only the prose; code, paths and commands are left out.","speech.preview":"Test voice","speech.preview.description":"Speaks a short sample with the current voice and speed.","speech.preview.text":"Hello, this is a reading test."},es:{"pill.idle":"Dictar","pill.starting":"Iniciando…","pill.listening":"Escuchando…","pill.dictating":"Dictando…","pill.error":"Reintentar dictado","pill.title":"Dictado por voz","pill.winH":"Abrir el dictado de Windows (Win+H)","pill.stop":"Detener dictado","command.toggle":"Alternar dictado por voz","command.toggle.description":"Dicta en el prompt con el motor elegido","command.configure":"Ajustes de dictado por voz","error.title":"Dictado por voz","error.language":"El idioma de reconocimiento {locale} no está instalado. Disponibles: {available}.","error.none":"Windows no tiene ningún reconocedor de voz instalado. Configuración › Hora e idioma › Idioma › Voz.","error.mic":"No hay ningún micrófono disponible para el reconocimiento de voz.","error.system":"System.Speech no está disponible en esta instalación de Windows.","error.exit":"El motor de dictado se detuvo inesperadamente.","error.unknown":"El dictado falló ({code}).","toast.discarded":"Dictado descartado: la sesión activa cambió.","settings.title":"Dictado por voz","settings.description":"Dictado offline en Windows y Linux. El texto se inserta en el prompt; nunca se envía solo.","settings.section":"Dictado","settings.engine":"Motor de dictado","settings.engine.description":"Windows ofrece Win+H y System.Speech; Linux usa whisper.cpp en local.","engine.windows":"Windows Voice Typing (Win+H)","engine.system":"System.Speech (offline)","engine.whisper":"WhisperX local (WSL)","engine.whispercpp":"whisper.cpp (local, nativo)","pill.recording":"Grabando…","pill.transcribing":"Transcribiendo…","pill.whisper":"Grabar y transcribir con WhisperX","error.whisper":"WhisperX no pudo transcribir el audio.","whisper.section":"WhisperX (local)","whisper.python":"Python del entorno","whisper.python.description":"Python del venv que tiene whisperx instalado.","whisper.model":"Modelo","whisper.model.description":"large-v3-turbo es el más rápido con buena precisión en GPU.","whisper.language":"Idioma","whisper.language.description":"Automático detecta el idioma de cada grabación.","whisper.language.auto":"Automático","whisper.test":"Comprobar WhisperX","whisper.test.description":"Arranca el worker e indica si el modelo carga.","whisper.test.run":"Comprobar","whisper.test.ready":"WhisperX listo: {model} ({device})","whisper.test.error":"WhisperX no está listo: {error}","whisper.empty":"WhisperX no devolvió texto.","pill.whispercpp":"Grabar y transcribir con whisper.cpp","error.whispercpp":"whisper.cpp no pudo transcribir el audio.","whispercpp.section":"whisper.cpp (local, nativo)","whispercpp.description":"Funciona en CPU sin WSL ni Python. La primera vez hay que descargar el servidor (~10 MB) y el modelo.","whispercpp.model":"Modelo","whispercpp.model.description":"base-q5_1 es el más ligero (57 MB) y va bien en portátiles modestos. small-q5_1 es más preciso pero consume más CPU y memoria.","whispercpp.language":"Idioma","whispercpp.language.description":"Automático detecta el idioma de cada grabación.","whispercpp.language.auto":"Automático","whispercpp.install":"Descargar motor","whispercpp.install.description":"Descarga el servidor oficial de whisper.cpp y el modelo elegido en ~/.local/share/opencode-voice-dictation. Solo necesita internet una vez.","whispercpp.install.run":"Descargar","whispercpp.install.busy":"Descargando…","whispercpp.install.ok":"whisper.cpp instalado: {model}","whispercpp.install.error":"No se pudo instalar whisper.cpp: {error}","whispercpp.test":"Comprobar whisper.cpp","whispercpp.test.description":"Indica si el servidor y el modelo elegido están instalados.","whispercpp.test.run":"Comprobar","whispercpp.test.ready":"whisper.cpp listo: {model}","whispercpp.test.missing":"whisper.cpp no está instalado. Descarga el motor primero.","whispercpp.test.error":"whisper.cpp no está listo: {error}","whispercpp.empty":"whisper.cpp no devolvió texto.","whispercpp.linux":"whisper.cpp solo está disponible en Linux. En Windows usa Win+H, System.Speech o WhisperX.","settings.winh":"Probar Win+H","settings.winh.description":"Envía el atajo del dictado de Windows. Si aparece la barra flotante, funciona.","settings.winh.run":"Probar Win+H","settings.winh.sent":"Win+H enviado. Si no apareció la barra, revisa Configuración › Accesibilidad › Dictado por voz.","settings.language":"Idioma","settings.language.description":"Un reconocedor instalado en Windows. Los acentos y la puntuación dependen de él.","settings.confidence":"Confianza mínima","settings.confidence.description":"Los resultados por debajo de este umbral se ignoran para evitar ruido.","confidence.low":"Baja (0,2)","confidence.normal":"Normal (0,4)","confidence.high":"Alta (0,6)","settings.space":"Añadir un espacio antes del texto","settings.space.description":"Separa el dictado de lo que ya hay en el prompt.","settings.focus":"Enfocar el prompt al terminar","settings.focus.description":"Mueve el cursor al prompt para revisar o enviar el texto.","settings.test.section":"Diagnóstico","settings.test":"Probar micrófono","settings.test.description":"Comprueba los reconocedores instalados y el micrófono predeterminado.","settings.test.run":"Ejecutar prueba","settings.test.success":"Reconocimiento de voz listo: {locale}","settings.test.failed":"No está listo: {reason}","common.reason.no-recognizers":"no hay ningún idioma de voz instalado","common.reason.no-microphone":"no hay micrófono disponible","read.idle":"Leer","read.speaking":"Leyendo…","read.paused":"Pausado","read.pause":"Pausar","read.resume":"Reanudar","read.restart":"Leer de nuevo","read.stop":"Detener lectura","read.title":"Leer en voz alta el último mensaje","command.read":"Leer en voz alta el último mensaje","command.read.description":"Reproduce la última respuesta con una voz del sistema","read.noSession":"Abre una conversación para leer su último mensaje.","read.empty":"El último mensaje no tiene texto para leer.","read.error":"No se pudo leer el último mensaje.","read.noVoices":"El sistema no tiene voces instaladas para la lectura.","speech.section":"Lectura en voz alta","speech.engine":"Motor de voz","speech.engine.description":"Edge neuronal suena mucho más natural pero necesita internet. Sistema usa las voces instaladas del equipo, sin conexión.","speech.engine.edge":"Edge neuronal (online)","speech.engine.system":"Sistema (offline)","error.edge":"No se pudo usar la voz de Edge. Revisa la conexión o cambia a la voz del sistema.","speech.edge.error":"No se pudieron cargar las voces de Edge","speech.voice":"Voz","speech.voice.description":"La voz que se usa para leer.","speech.voice.auto":"Automática","speech.rate":"Velocidad","speech.rate.description":"A qué ritmo se lee el texto.","speech.rate.slow":"0,8×","speech.rate.normal":"1×","speech.rate.fast":"1,2×","speech.rate.faster":"1,5×","speech.skipCode":"Ignorar bloques de código","speech.skipCode.description":"Lee solo la prosa; omite código, rutas y comandos.","speech.preview":"Probar voz","speech.preview.description":"Lee una frase corta con la voz y velocidad actuales.","speech.preview.text":"Hola, esta es una prueba de lectura."}}}),oe=E.Ipc.define({id:"voice-dictation.dictation",state:p.Schema.Struct({status:p.Schema.Literals(["idle","starting","listening","error"]),error:p.Schema.String,locale:p.Schema.String}),methods:{start:{input:p.Schema.Struct({locale:p.Schema.String,minConfidence:p.Schema.Number})},stop:{},diagnose:{output:p.Schema.Struct({locales:p.Schema.Array(p.Schema.String),microphone:p.Schema.Boolean,selected:p.Schema.String,error:p.Schema.String})}},events:{final:p.Schema.Struct({text:p.Schema.String,confidence:p.Schema.Number}),error:p.Schema.Struct({code:p.Schema.String,detail:p.Schema.String,available:p.Schema.Array(p.Schema.String)})}}),he=E.Ipc.define({id:"voice-dictation.speech",methods:{speak:{input:p.Schema.Struct({text:p.Schema.String,rate:p.Schema.Number,voice:p.Schema.String})},stop:{},voices:{output:p.Schema.Array(p.Schema.String)}},events:{done:p.Schema.Struct({ok:p.Schema.Boolean})}}),se=E.Ipc.define({id:"voice-dictation.voice-typing",methods:{toggle:{}}}),pe=String.raw`param(
|
|
2
2
|
[string]$Culture = "es-ES",
|
|
3
3
|
[double]$MinConfidence = 0.4,
|
|
4
4
|
[switch]$Diagnose
|
|
@@ -105,9 +105,9 @@ while ($true) {
|
|
|
105
105
|
Start-Sleep -Milliseconds 250
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
`,$e="powershell.exe";function
|
|
109
|
-
`);for(;
|
|
110
|
-
`)}}),i.stderr?.setEncoding("utf8"),i.stderr?.on("data",
|
|
108
|
+
`,$e="powershell.exe";function me(){const t=G.join(ne.tmpdir(),"opencode-voice-dictation");M.mkdirSync(t,{recursive:!0});const e=re.createHash("sha1").update(pe).digest("hex").slice(0,12),i=G.join(t,`dictation-${e}.ps1`);return M.writeFileSync(i,pe,"utf8"),i}function we(t){return ee.spawn($e,t,{windowsHide:!0,stdio:["ignore","pipe","pipe"]})}function fe(t){const e=t.pid;if(e===void 0){t.kill();return}ee.spawn("taskkill",["/PID",String(e),"/T","/F"],{windowsHide:!0,stdio:"ignore"}).on("error",()=>t.kill())}function Re(t,e){const i=we(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",me(),"-Culture",t.locale,"-MinConfidence",String(t.minConfidence)]);let d=!1,n="",l="";const u=c=>{switch(c.type){case"ready":e.onReady(c.locale??t.locale);break;case"final":c.text&&e.onFinal(c.text,c.confidence??0);break;case"error":e.onError(c.code??"UNKNOWN",c.detail??"",c.available??[]);break}};return i.stdout?.setEncoding("utf8"),i.stdout?.on("data",c=>{l+=c;let g=l.indexOf(`
|
|
109
|
+
`);for(;g>=0;){const h=l.slice(0,g).replace(/\r$/,"").trim();if(l=l.slice(g+1),h)try{u(JSON.parse(h))}catch{}g=l.indexOf(`
|
|
110
|
+
`)}}),i.stderr?.setEncoding("utf8"),i.stderr?.on("data",c=>{n=(n+c).slice(-2e3)}),i.on("error",c=>{d||e.onError("ENGINE_START_FAILED",c.message,[])}),i.on("close",c=>{d||e.onExit(c,n.trim().slice(-500))}),{stop(){d=!0,fe(i)}}}function Pe(){return new Promise((t,e)=>{const i=we(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",me(),"-Diagnose"]);let d="",n="",l=!1;const u=(g,h)=>{l||(l=!0,clearTimeout(c),g?e(g):t(h))},c=setTimeout(()=>{fe(i),u(new Error("diagnose timed out"))},2e4);i.stdout?.setEncoding("utf8"),i.stdout?.on("data",g=>{d+=g}),i.stderr?.setEncoding("utf8"),i.stderr?.on("data",g=>{n+=g}),i.on("error",g=>u(g)),i.on("close",()=>{const g=d.split(/\r?\n/).map(h=>h.trim()).filter(h=>h.startsWith("{")).at(-1);if(!g){u(new Error(n.trim()||"no output from PowerShell"));return}try{const h=JSON.parse(g);u(void 0,{locales:h.locales??[],microphone:h.microphone===!0,selected:h.selected??"",error:h.error??""})}catch(h){u(h instanceof Error?h:new Error(String(h)))}})})}const de=String.raw`param(
|
|
111
111
|
[string]$TextFile = "",
|
|
112
112
|
[int]$Rate = 0,
|
|
113
113
|
[string]$Voice = "",
|
|
@@ -160,7 +160,7 @@ try {
|
|
|
160
160
|
} catch { }
|
|
161
161
|
|
|
162
162
|
$synth.Dispose()
|
|
163
|
-
`,
|
|
163
|
+
`,Te="powershell.exe";function ve(){const t=G.join(ne.tmpdir(),"opencode-voice-dictation");return M.mkdirSync(t,{recursive:!0}),t}function ye(){const t=re.createHash("sha1").update(de).digest("hex").slice(0,12),e=G.join(ve(),`speak-${t}.ps1`);return M.writeFileSync(e,de,"utf8"),e}function Ie(t){const e=G.join(ve(),`speech-${re.randomUUID()}.txt`);return M.writeFileSync(e,t,"utf8"),e}function Se(t){return ee.spawn(Te,t,{windowsHide:!0,stdio:["ignore","pipe","pipe"]})}function be(t){const e=t.pid;if(e===void 0){t.kill();return}ee.spawn("taskkill",["/PID",String(e),"/T","/F"],{windowsHide:!0,stdio:"ignore"}).on("error",()=>t.kill())}function Ae(t){return Math.max(-10,Math.min(10,Math.round((t-1)*5)))}function Ne(t,e){const i=Ie(t.text),d=Se(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",ye(),"-TextFile",i,"-Rate",String(Ae(t.rate)),"-Voice",t.voice]);let n=!1;const l=()=>{try{M.rmSync(i,{force:!0})}catch{}};return d.on("error",()=>{l(),n||e(!1)}),d.on("close",u=>{l(),n||e(u===0)}),{stop(){n=!0,be(d),l()}}}function Le(){return new Promise(t=>{const e=Se(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",ye(),"-ListVoices"]);let i="",d=!1;const n=u=>{d||(d=!0,clearTimeout(l),t(u))},l=setTimeout(()=>{be(e),n([])},1e4);e.stdout?.setEncoding("utf8"),e.stdout?.on("data",u=>{i+=u}),e.on("error",()=>n([])),e.on("close",()=>{const u=i.split(/\r?\n/).map(c=>c.trim()).filter(c=>c.length>0);n(u)})})}const ue=String.raw`$ErrorActionPreference = "Stop"
|
|
164
164
|
|
|
165
165
|
Add-Type @"
|
|
166
166
|
using System;
|
|
@@ -187,6 +187,6 @@ public static class WinH {
|
|
|
187
187
|
"@
|
|
188
188
|
|
|
189
189
|
[WinH]::Send()
|
|
190
|
-
`,
|
|
191
|
-
`);return
|
|
192
|
-
`)};return t.on("done",()=>{S(!1),f(!1)}),_()?.addEventListener?.("voiceschanged",()=>{m()}),m(),{speaking:g,paused:E,backend:P,engine:r,voice:u,error:F,canPause:h,setConfig:p,voices:y,refreshVoices:m,lastAssistantText:Se,speak:ee,cancel:n,pause:D,resume:H,togglePause:x,restart:we,preview:async()=>{const v=await ee(e.t("speech.preview.text"));if(v){const w=r()==="edge"?`Edge · ${u()}`:`${P()} · ${i.value.voiceURI||"auto"}`;e.toast.show({variant:"default",title:e.t("read.title"),description:w})}return v}}}const Ue=["es-ES","es-MX","es-AR","es-CO","pt-BR","pt-PT","en-US","en-GB","fr-FR","de-DE","it-IT"];function De(e){const t=$.usePlugin(),i=t.ui,l=t.stores.prefs,r=t.stores.speech,o=t.stores.tts,u=t.stores.engine,s=t.stores.whisper,p=t.main(ie),g=t.main(ne),[f,E]=R.createSignal([]),[S,y]=R.createSignal(!1),[N,F]=R.createSignal(!1);R.onMount(()=>{e.speaker.setConfig({engine:o.value.engine,voice:o.value.voice}),p.diagnose().then(n=>{n.locales.length>0&&E(n.locales)}).catch(()=>{}),e.speaker.refreshVoices()});const O=()=>{const n=f(),h=l.value.locale;return(n.includes(h)?n:[h,...n]).map(H=>({value:H,label:H}))},P=()=>{const n=e.speaker.voices().map(h=>({value:h.id,label:h.label}));return o.value.engine==="edge"?n:[{value:"",label:t.t("speech.voice.auto")},...n]},U=()=>o.value.engine==="edge"?o.value.voice:r.value.voiceURI,I=n=>{if(o.value.engine==="edge"){o.update(h=>{h.voice=n}),e.speaker.setConfig({voice:n});return}k({voiceURI:n})},V=n=>{l.update(h=>{n.locale!==void 0&&(h.locale=n.locale),n.minConfidence!==void 0&&(h.minConfidence=n.minConfidence),n.trailingSpace!==void 0&&(h.trailingSpace=n.trailingSpace),n.autoFocus!==void 0&&(h.autoFocus=n.autoFocus)})},k=n=>{r.update(h=>{n.voiceURI!==void 0&&(h.voiceURI=n.voiceURI),n.rate!==void 0&&(h.rate=n.rate),n.skipCode!==void 0&&(h.skipCode=n.skipCode)})},A=n=>{u.update(h=>{h.mode=n})},L=n=>{s.update(h=>{n.python!==void 0&&(h.python=n.python),n.model!==void 0&&(h.model=n.model),n.language!==void 0&&(h.language=n.language)})},B=async()=>{F(!0);try{const n=await t.client.rpc(J).whisperStatus({python:s.value.python,model:s.value.model});n.ready?t.toast.show({variant:"success",title:t.t("pill.title"),description:t.t("whisper.test.ready",{model:n.model,device:n.device})}):t.toast.show({variant:"error",title:t.t("pill.title"),description:t.t("whisper.test.error",{error:n.error||"?"})})}catch(n){t.toast.show({variant:"error",title:t.t("pill.title"),description:t.t("whisper.test.error",{error:n instanceof Error?n.message:String(n)})})}finally{F(!1)}},_=n=>{o.update(h=>{h.engine=n}),e.speaker.setConfig({engine:n}),e.speaker.refreshVoices()},a=async()=>{try{await g.toggle(),t.toast.show({variant:"default",title:t.t("settings.winh"),description:t.t("settings.winh.sent")})}catch(n){t.toast.show({variant:"error",title:t.t("pill.title"),description:n instanceof Error?n.message:t.t("error.winh")})}},m=async()=>{try{await e.speaker.preview()}catch(n){t.toast.show({variant:"error",title:t.t("read.title"),description:n instanceof Error?n.message:t.t("read.error")})}},b=async()=>{y(!0);try{const n=await p.diagnose();if(n.locales.length===0){t.toast.show({variant:"error",title:t.t("error.title"),description:t.t("settings.test.failed",{reason:t.t("common.reason.no-recognizers")}),persistent:!0});return}if(!n.microphone){t.toast.show({variant:"error",title:t.t("error.title"),description:t.t("settings.test.failed",{reason:t.t("common.reason.no-microphone")}),persistent:!0});return}n.locales.length>0&&!f().length&&E(n.locales),t.toast.show({variant:"success",title:t.t("error.title"),description:t.t("settings.test.success",{locale:n.selected||n.locales[0]||l.value.locale})})}catch(n){t.toast.show({variant:"error",title:t.t("error.title"),description:n instanceof Error?n.message:String(n)})}finally{y(!1)}};return d.createComponent(i.SettingsLayout,{get title(){return t.t("settings.title")},get description(){return t.t("settings.description")},get children(){return[d.createComponent(i.SettingsSection,{get title(){return t.t("settings.section")},get children(){return[d.createComponent(i.SettingsRow,{id:"engine",get title(){return t.t("settings.engine")},get description(){return t.t("settings.engine.description")},get children(){return d.createComponent(i.Select,{get label(){return t.t("settings.engine")},get value(){return u.value.mode},get options(){return[{value:"windows",label:t.t("engine.windows")},{value:"system",label:t.t("engine.system")},{value:"whisper",label:t.t("engine.whisper")}]},onChange:n=>A(n)})}}),d.createComponent(R.Show,{get when(){return u.value.mode==="windows"},get children(){return d.createComponent(i.SettingsRow,{id:"winh",get title(){return t.t("settings.winh")},get description(){return t.t("settings.winh.description")},get children(){return d.createComponent(i.Button,{onClick:()=>{a()},get children(){return t.t("settings.winh.run")}})}})}}),d.createComponent(R.Show,{get when(){return u.value.mode==="whisper"},get children(){return[d.createComponent(i.SettingsRow,{id:"whisper-python",get title(){return t.t("whisper.python")},get description(){return t.t("whisper.python.description")},get children(){return d.createComponent(i.TextInput,{get label(){return t.t("whisper.python")},get value(){return s.value.python},onInput:n=>L({python:n})})}}),d.createComponent(i.SettingsRow,{id:"whisper-model",get title(){return t.t("whisper.model")},get description(){return t.t("whisper.model.description")},get children(){return d.createComponent(i.Select,{get label(){return t.t("whisper.model")},get value(){return s.value.model},options:[{value:"large-v3-turbo",label:"large-v3-turbo"},{value:"large-v3",label:"large-v3"},{value:"medium",label:"medium"}],onChange:n=>L({model:n})})}}),d.createComponent(i.SettingsRow,{id:"whisper-language",get title(){return t.t("whisper.language")},get description(){return t.t("whisper.language.description")},get children(){return d.createComponent(i.Select,{get label(){return t.t("whisper.language")},get value(){return s.value.language},get options(){return[{value:"",label:t.t("whisper.language.auto")},{value:"es",label:"Español"},{value:"en",label:"English"},{value:"pt",label:"Português"}]},onChange:n=>L({language:n})})}}),d.createComponent(i.SettingsRow,{id:"whisper-test",get title(){return t.t("whisper.test")},get description(){return t.t("whisper.test.description")},get children(){return d.createComponent(i.Button,{get busy(){return N()},onClick:()=>{B()},get children(){return t.t("whisper.test.run")}})}})]}}),d.createComponent(R.Show,{get when(){return u.value.mode==="system"},get children(){return[d.createComponent(i.SettingsRow,{id:"language",get title(){return t.t("settings.language")},get description(){return t.t("settings.language.description")},get children(){return d.createComponent(i.Select,{get label(){return t.t("settings.language")},get value(){return l.value.locale},get options(){return d.memo(()=>O().length>0)()?O():Ue.map(n=>({value:n,label:n}))},onChange:n=>V({locale:n})})}}),d.createComponent(i.SettingsRow,{id:"confidence",get title(){return t.t("settings.confidence")},get description(){return t.t("settings.confidence.description")},get children(){return d.createComponent(i.Select,{get label(){return t.t("settings.confidence")},get value(){return String(l.value.minConfidence)},get options(){return[{value:"0.2",label:t.t("confidence.low")},{value:"0.4",label:t.t("confidence.normal")},{value:"0.6",label:t.t("confidence.high")}]},onChange:n=>V({minConfidence:Number(n)||.4})})}}),d.createComponent(i.SettingsRow,{id:"space",get title(){return t.t("settings.space")},get description(){return t.t("settings.space.description")},get children(){return d.createComponent(i.Switch,{get label(){return t.t("settings.space")},hideLabel:!0,get checked(){return l.value.trailingSpace},onChange:n=>V({trailingSpace:n})})}}),d.createComponent(i.SettingsRow,{id:"focus",get title(){return t.t("settings.focus")},get description(){return t.t("settings.focus.description")},get children(){return d.createComponent(i.Switch,{get label(){return t.t("settings.focus")},hideLabel:!0,get checked(){return l.value.autoFocus},onChange:n=>V({autoFocus:n})})}})]}})]}}),d.createComponent(i.SettingsSection,{get title(){return t.t("speech.section")},get children(){return[d.createComponent(i.SettingsRow,{id:"tts-engine",get title(){return t.t("speech.engine")},get description(){return t.t("speech.engine.description")},get children(){return d.createComponent(i.Select,{get label(){return t.t("speech.engine")},get value(){return o.value.engine},get options(){return[{value:"edge",label:t.t("speech.engine.edge")},{value:"system",label:t.t("speech.engine.system")}]},onChange:n=>_(n)})}}),d.createComponent(i.SettingsRow,{id:"voice",get title(){return t.t("speech.voice")},get description(){return t.t("speech.voice.description")},get children(){return d.createComponent(i.Select,{get label(){return t.t("speech.voice")},get value(){return U()},get options(){return P()},onChange:n=>I(n)})}}),d.createComponent(R.Show,{get when(){return e.speaker.error()},get children(){return d.createComponent(i.SettingsRow,{id:"tts-error",get title(){return t.t("speech.edge.error")},get description(){return e.speaker.error()}})}}),d.createComponent(i.SettingsRow,{id:"rate",get title(){return t.t("speech.rate")},get description(){return t.t("speech.rate.description")},get children(){return d.createComponent(i.Select,{get label(){return t.t("speech.rate")},get value(){return String(r.value.rate)},get options(){return[{value:"0.8",label:t.t("speech.rate.slow")},{value:"1",label:t.t("speech.rate.normal")},{value:"1.2",label:t.t("speech.rate.fast")},{value:"1.5",label:t.t("speech.rate.faster")}]},onChange:n=>k({rate:Number(n)||1})})}}),d.createComponent(i.SettingsRow,{id:"skip-code",get title(){return t.t("speech.skipCode")},get description(){return t.t("speech.skipCode.description")},get children(){return d.createComponent(i.Switch,{get label(){return t.t("speech.skipCode")},hideLabel:!0,get checked(){return r.value.skipCode},onChange:n=>k({skipCode:n})})}}),d.createComponent(i.SettingsRow,{id:"preview",get title(){return t.t("speech.preview")},get description(){return t.t("speech.preview.description")},get children(){return d.createComponent(i.Button,{onClick:()=>{m()},get children(){return t.t("read.idle")}})}})]}}),d.createComponent(i.SettingsSection,{get title(){return t.t("settings.test.section")},get children(){return d.createComponent(i.SettingsRow,{id:"test",get title(){return t.t("settings.test")},get description(){return t.t("settings.test.description")},get children(){return d.createComponent(i.Button,{get busy(){return S()},onClick:()=>{b()},get children(){return t.t("settings.test.run")}})}})}})]}})}async function He(){const e=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,noiseSuppression:!0}}),t=MediaRecorder.isTypeSupported("audio/webm;codecs=opus")?"audio/webm;codecs=opus":"audio/webm",i=new MediaRecorder(e,{mimeType:t}),l=[];return i.ondataavailable=r=>{r.data.size>0&&l.push(r.data)},i.start(),{stop:()=>new Promise(r=>{i.onstop=()=>{e.getTracks().forEach(o=>o.stop()),r(new Blob(l,{type:t}))},i.stop()})}}async function je(e){const t=new Uint8Array(await e.arrayBuffer());let i="";const l=32768;for(let r=0;r<t.length;r+=l)i+=String.fromCharCode(...t.subarray(r,r+l));return btoa(i)}function Be(e){const t=$.usePlugin();return d.memo(()=>e.actions().map(i=>d.createComponent(t.ui.Button,{get variant(){return i.id==="stop"?"danger":"neutral"},get icon(){return i.icon},onClick:()=>{i.run(),e.close()},get children(){return i.label}})))}const Me=e=>{const t=e.main(ie),i=e.main(le),l=e.main(ne),r=Fe(e,i),o=e.stores.prefs,u=e.stores.engine,s=e.stores.whisper,[p,g]=R.createSignal(!1),[f,E]=R.createSignal("idle");let S,y;const N=(a,m,b)=>{switch(a){case"LANGUAGE_NOT_AVAILABLE":return e.t("error.language",{locale:m||o.value.locale,available:b.length>0?b.join(", "):"—"});case"NO_RECOGNIZERS":return e.t("error.none");case"MICROPHONE_UNAVAILABLE":return e.t("error.mic");case"SYSTEM_SPEECH_UNAVAILABLE":return e.t("error.system");case"ENGINE_EXIT":return e.t("error.exit");default:return e.t("error.unknown",{code:a})}},F=()=>{p()&&(g(!1),l.toggle().catch(()=>{})),r.cancel(),S=e.sessions.current()?.key,t.start({locale:o.value.locale,minConfidence:o.value.minConfidence}).catch(a=>{e.toast.show({variant:"error",title:e.t("error.title"),description:a instanceof Error?a.message:String(a)})})},O=()=>{t.stop().catch(()=>{})},P=()=>{r.cancel(),e.screen.current()?.composer.focus(),l.toggle().then(()=>g(a=>!a)).catch(a=>{g(!1),e.toast.show({variant:"error",title:e.t("pill.title"),description:a instanceof Error?`${e.t("error.winh")} ${a.message}`:e.t("error.winh")})})},U=async()=>{if(f()==="idle"){r.cancel();try{y=await He(),E("recording")}catch(a){e.toast.show({variant:"error",title:e.t("pill.title"),description:a instanceof Error?a.message:e.t("error.whisper")})}}},I=()=>{const a=y;y=void 0,a&&a.stop().catch(()=>{}),E("idle")},V=async()=>{const a=y;if(y=void 0,!a){E("idle");return}E("transcribing");try{const m=await a.stop(),b=await je(m),h=(await e.client.rpc(J).whisperTranscribe({audio:b,mime:m.type,language:s.value.language,python:s.value.python,model:s.value.model})).text.trim();if(!h){e.toast.show({variant:"default",title:e.t("pill.title"),description:e.t("whisper.empty")});return}const D=e.screen.current()?.composer;if(D){const H=D.text(),x=H.length>0&&!/\s$/.test(H)?" ":"";D.insert(x+h),D.focus()}}catch(m){e.toast.show({variant:"error",title:e.t("pill.title"),description:m instanceof Error?m.message:e.t("error.whisper")})}finally{E("idle")}},k=()=>{if(u.value.mode==="whisper"){f()==="recording"?V():f()==="idle"&&U();return}if(u.value.mode==="windows"){P();return}const a=t.state()?.status??"idle";a==="starting"||a==="listening"?O():F()},A=()=>{if(f()==="recording"&&I(),r.speaking()){r.cancel();return}if(!e.sessions.current()){e.toast.show({variant:"error",title:e.t("read.title"),description:e.t("read.noSession")});return}const a=t.state()?.status??"idle";(a==="starting"||a==="listening")&&t.stop().catch(()=>{}),(async()=>{try{const m=await r.lastAssistantText();if(!m.trim()){e.toast.show({variant:"default",title:e.t("read.title"),description:e.t("read.empty")});return}await r.speak(m)||e.toast.show({variant:"default",title:e.t("read.title"),description:e.t("read.empty")})}catch(m){e.toast.show({variant:"error",title:e.t("read.title"),description:m instanceof Error?m.message:e.t("read.error")})}})()},L=()=>{if(!r.speaking())return[{id:"start",label:e.t("read.idle"),icon:"outline-audio",run:A}];const a=r.paused(),m=[{id:"restart",label:e.t("read.restart"),icon:"outline-reset",run:()=>{r.restart()}}];return r.canPause()&&m.push({id:"pause",label:a?e.t("read.resume"):e.t("read.pause"),icon:"outline-audio",run:()=>r.togglePause()}),m.push({id:"stop",label:e.t("read.stop"),icon:"xmark-small",run:()=>r.cancel()}),m};t.on("final",a=>{const m=e.sessions.current()?.key;if(S&&m&&m!==S){e.toast.show({variant:"error",title:e.t("error.title"),description:e.t("toast.discarded")});return}const b=e.screen.current()?.composer;if(!b)return;const n=b.text(),h=o.value.trailingSpace&&n.length>0&&!/\s$/.test(n)?" ":"";b.insert(h+a.text),o.value.autoFocus&&b.focus()}),t.on("error",a=>{e.toast.show({variant:"error",title:e.t("error.title"),description:N(a.code,a.detail,a.available),persistent:a.code==="LANGUAGE_NOT_AVAILABLE"||a.code==="NO_RECOGNIZERS",duration:8e3})});const B=()=>{if(e.app.platform!=="desktop")return;if(u.value.mode==="windows")return p()?{label:e.t("pill.dictating"),icon:"outline-wave-audio",tone:"danger",title:e.t("pill.stop"),run:k}:{label:e.t("pill.idle"),icon:"outline-wave-audio",title:e.t("pill.winH"),run:k};if(u.value.mode==="whisper")return f()==="recording"?{label:e.t("pill.recording"),icon:"outline-wave-audio",tone:"danger",title:e.t("pill.stop"),run:k}:f()==="transcribing"?{label:e.t("pill.transcribing"),icon:"outline-audio",title:e.t("pill.whisper")}:{label:e.t("pill.idle"),icon:"outline-wave-audio",title:e.t("pill.whisper"),run:k};const a=t.state()?.status??"idle";return a==="starting"?{label:e.t("pill.starting"),icon:"outline-wave-audio",title:e.t("pill.title"),run:k}:a==="listening"?{label:e.t("pill.listening"),icon:"outline-wave-audio",tone:"danger",title:e.t("pill.stop"),run:k}:a==="error"?{label:e.t("pill.error"),icon:"outline-wave-audio",tone:"warning",title:e.t("pill.title"),run:k}:{label:e.t("pill.idle"),icon:"outline-wave-audio",title:e.t("pill.title"),run:k}},_=()=>{if(e.app.platform!=="desktop")return;const a=r.speaking(),m=r.paused(),b=a?L().map(n=>({id:n.id,label:n.label,icon:n.icon,tone:n.id==="stop"?"danger":void 0,run:n.run})):[];return{label:a?m?e.t("read.paused"):e.t("read.speaking"):e.t("read.idle"),icon:"outline-audio",tone:a?"success":void 0,title:e.t("read.title"),rows:b.length>0?b:void 0,run:a?void 0:A}};e.add($.ComposerPill,()=>({id:"dictate",show:()=>B()})),e.add($.ComposerPill,()=>({id:"read",order:1,show:()=>_(),menu:a=>d.createComponent(Be,{actions:L,get close(){return a.close}})})),e.add($.Command,()=>({id:"toggle",title:e.t("command.toggle"),description:e.t("command.toggle.description"),keywords:"voice dictation microphone speech dictado voz microfono",group:e.t("pill.title"),section:"prompt",bind:"mod+shift+m",slash:{name:"dictado"},editable:!0,enabled:e.app.platform==="desktop",run:k})),e.add($.Command,()=>({id:"configure",title:e.t("command.configure"),keywords:"voice dictation microphone speech dictado voz microfono",group:e.t("pill.title"),enabled:e.app.platform==="desktop",run:()=>e.layout.settings(e.id)})),e.add($.Command,()=>({id:"read",title:e.t("command.read"),description:e.t("command.read.description"),keywords:"read aloud speech voice leer voz altavoz lectura",group:e.t("pill.title"),section:"prompt",bind:"mod+shift+l",slash:{name:"leer"},editable:!0,enabled:e.app.platform==="desktop",run:A})),e.add($.SettingsPage,{id:e.id,icon:"outline-audio",get title(){return e.t("settings.title")},get entries(){return[{id:"engine",title:e.t("settings.engine"),keywords:"engine motor dictation dictado winh voz"},{id:"winh",title:e.t("settings.winh"),keywords:"winh windows voice typing dictado"},{id:"language",title:e.t("settings.language"),keywords:"voice language idioma voz"},{id:"confidence",title:e.t("settings.confidence"),keywords:"voice confidence confianza"},{id:"test",title:e.t("settings.test"),keywords:"voice microphone microphone test microfono"},{id:"voice",title:e.t("speech.voice"),keywords:"read aloud voice voz lectura altavoz"},{id:"tts-engine",title:e.t("speech.engine"),keywords:"voice engine motor edge neural sistema"},{id:"whisper-python",title:e.t("whisper.python"),keywords:"whisper python venv entorno"},{id:"whisper-model",title:e.t("whisper.model"),keywords:"whisper model modelo"},{id:"whisper-language",title:e.t("whisper.language"),keywords:"whisper language idioma"},{id:"whisper-test",title:e.t("whisper.test"),keywords:"whisper check comprobar"},{id:"rate",title:e.t("speech.rate"),keywords:"read aloud speed velocidad lectura"},{id:"skip-code",title:e.t("speech.skipCode"),keywords:"read aloud code codigo lectura"},{id:"preview",title:e.t("speech.preview"),keywords:"read aloud test voice probar voz"}]},render:()=>d.createComponent(De,{speaker:r})})},qe=$.Plugin.define({...Ce,setup:Me,main:Ve});exports.default=qe;
|
|
190
|
+
`,We="powershell.exe";function _e(){const t=G.join(ne.tmpdir(),"opencode-voice-dictation");M.mkdirSync(t,{recursive:!0});const e=re.createHash("sha1").update(ue).digest("hex").slice(0,12),i=G.join(t,`win-h-${e}.ps1`);return M.writeFileSync(i,ue,"utf8"),i}function Ve(t){return ee.spawn(We,t,{windowsHide:!0,stdio:"ignore"})}function ze(){return new Promise((t,e)=>{const i=Ve(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",_e()]);i.on("error",d=>e(d)),i.on("close",d=>{d===0?t():e(new Error(`Win+H helper exited with code ${d??"unknown"}`))})})}const De=t=>{let e="idle",i="",d="",n,l=!1;const u=()=>{n=void 0,l=!1},c=t.provide(oe,{state:()=>({status:e,error:i,locale:d}),start(y,k){n&&(l=!0,n.stop(),u()),e="starting",i="",d=y.locale,c.changed();const w=k.window;try{n=Re({locale:y.locale,minConfidence:y.minConfidence},{onReady(b){e="listening",d=b,c.changed()},onFinal(b,R){c.emit("final",{text:b,confidence:R},w)},onError(b,R,_){e="error",i=b,c.changed(),c.emit("error",{code:b,detail:R,available:_},w)},onExit(b,R){if(l){u(),e="idle",i="",d="",c.changed();return}u(),e!=="error"&&(e="error",i=b===null?"ENGINE_EXIT":`ENGINE_EXIT_${b}`,c.changed(),c.emit("error",{code:"ENGINE_EXIT",detail:R||(b===null?"":String(b)),available:[]},w))}})}catch(b){throw e="error",i="ENGINE_START_FAILED",c.changed(),b}},stop(){n&&(l=!0,n.stop(),u()),e="idle",i="",d="",c.changed()},diagnose(){return Pe()}});let g;const h=t.provide(he,{speak(y){g?.stop(),g=Ne(y,k=>{g=void 0,h.emit("done",{ok:k})})},stop(){g&&(g.stop(),g=void 0)},voices(){return Le()}});t.provide(se,{toggle(){return ze()}}),t.onCleanup(()=>{n&&(l=!0,n.stop(),u()),g?.stop(),g=void 0})},U={id:"voice-dictation.voice",methods:{edgeVoices:{input:{type:"object",additionalProperties:!1},output:{type:"array",items:{type:"object",properties:{id:{type:"string"},label:{type:"string"},locale:{type:"string"},gender:{type:"string"}},required:["id","label","locale","gender"],additionalProperties:!1}}},edgeSpeak:{input:{type:"object",properties:{text:{type:"string"},voice:{type:"string"},rate:{type:"number"}},required:["text","voice","rate"],additionalProperties:!1},output:{type:"string"}},whisperTranscribe:{input:{type:"object",properties:{audio:{type:"string"},mime:{type:"string"},language:{type:"string"},python:{type:"string"},model:{type:"string"}},required:["audio","mime","language","python","model"],additionalProperties:!1},output:{type:"object",properties:{text:{type:"string"},language:{type:"string"}},required:["text","language"],additionalProperties:!1}},whisperStatus:{input:{type:"object",properties:{python:{type:"string"},model:{type:"string"}},required:["python","model"],additionalProperties:!1},output:{type:"object",properties:{ready:{type:"boolean"},model:{type:"string"},device:{type:"string"},error:{type:"string"}},required:["ready","model","device","error"],additionalProperties:!1}},capabilities:{input:{type:"object",additionalProperties:!1},output:{type:"object",properties:{platform:{type:"string"},engines:{type:"array",items:{type:"string"}}},required:["platform","engines"],additionalProperties:!1}},whisperCppInstall:{input:{type:"object",properties:{model:{type:"string"}},required:["model"],additionalProperties:!1},output:{type:"object",properties:{ok:{type:"boolean"},model:{type:"string"},error:{type:"string"}},required:["ok","model","error"],additionalProperties:!1}},whisperCppStatus:{input:{type:"object",properties:{model:{type:"string"}},required:["model"],additionalProperties:!1},output:{type:"object",properties:{installed:{type:"boolean"},ready:{type:"boolean"},model:{type:"string"},error:{type:"string"}},required:["installed","ready","model","error"],additionalProperties:!1}},whisperCppTranscribe:{input:{type:"object",properties:{audio:{type:"string"},mime:{type:"string"},language:{type:"string"},model:{type:"string"}},required:["audio","mime","language","model"],additionalProperties:!1},output:{type:"object",properties:{text:{type:"string"},language:{type:"string"}},required:["text","language"],additionalProperties:!1}}},events:{}},Oe=250;function Ue(t,e){let i=t.replace(/\r\n/g,`
|
|
191
|
+
`);return e?i=i.replace(/```[\s\S]*?```/g," "):i=i.replace(/```[^\n]*\n([\s\S]*?)```/g,"$1"),i=i.replace(/`([^`]+)`/g,"$1"),i=i.replace(/!\[[^\]]*\]\([^)]*\)/g," "),i=i.replace(/\[([^\]]+)\]\([^)]*\)/g,"$1"),i=i.replace(/^\s*#{1,6}\s+/gm,""),i=i.replace(/^\s*>\s?/gm,""),i=i.replace(/^\s*[-*+]\s+/gm,""),i=i.replace(/^\s*\d+\.\s+/gm,""),i=i.replace(/(\*\*|__)(.*?)\1/g,"$2"),i=i.replace(/(\*|_)(.*?)\1/g,"$2"),i=i.replace(/^\s*\|.*\|\s*$/gm," "),i=i.replace(/\n{2,}/g,". "),i=i.replace(/\n/g," "),i=i.replace(/\s+/g," "),i=i.replace(/\s+([.,;:!?])/g,"$1"),i=i.replace(/([.,;:!?]){2,}/g,"$1"),i.trim()}function ge(t,e=Oe){const i=t.trim();if(!i)return[];const d=i.split(new RegExp("(?<=[.!?…])\\s+")),n=[];let l="";const u=c=>{let g="";for(const h of c.split(/\s+/))h&&(g&&g.length+1+h.length>e?(n.push(g),g=h):g=g?`${g} ${h}`:h);return g};for(const c of d){if(c.length>e){l&&(n.push(l),l=""),l=u(c);continue}l&&l.length+1+c.length>e?(n.push(l),l=c):l=l?`${l} ${c}`:c}return l&&n.push(l),n}function Be(t,e){const i=t.stores.speech,d=t.stores.tts.value,[n,l]=S.createSignal(d.engine==="edge"?"edge":"system"),[u,c]=S.createSignal(d.voice),g=r=>{(r.engine==="edge"||r.engine==="system")&&l(r.engine),r.voice!==void 0&&c(r.voice)},[h,y]=S.createSignal(!1),[k,w]=S.createSignal(!1),[b,R]=S.createSignal([]),[_,z]=S.createSignal(""),[I,D]=S.createSignal("none");let T=0,O=[],B="",N,F;const P=new Map,A=()=>typeof globalThis.speechSynthesis<"u"?globalThis.speechSynthesis:void 0,X=()=>{const r=A()?.getVoices()??[];if(r.length===0)return;const o=i.value.voiceURI;if(o){const C=r.find(Y=>Y.voiceURI===o);if(C)return C}const v=t.locale.locale().split("-")[0]?.toLowerCase()??"es";return r.find(C=>C.lang.toLowerCase().startsWith(v))??r[0]},H=async()=>{if(n()==="edge"){try{const o=await t.client.rpc(U).edgeVoices({});o.length>0?(R(o.map(v=>({id:v.id,label:v.label}))),z("")):z(t.t("speech.edge.error"))}catch(o){z(o instanceof Error?o.message:String(o))}D("edge");return}z("");const r=A()?.getVoices()??[];if(r.length>0){R(r.map(o=>({id:o.voiceURI,label:`${o.name} — ${o.lang}`}))),D("web");return}try{const o=await e.voices();if(o.length>0){R(o.map(v=>({id:v,label:v}))),D("sapi");return}}catch{}D("none")},L=()=>{N&&(N.onended=null,N.onerror=null,N.pause(),N=void 0),F&&(URL.revokeObjectURL(F),F=void 0)},s=()=>{T++,O=[],A()?.cancel(),L(),w(!1),y(!1),I()==="sapi"&&e.stop().catch(()=>{})},m=()=>I()==="web"||I()==="edge",f=()=>{if(!(!h()||k()||!m())){if(I()==="edge"){N?.pause(),w(!0);return}A()?.pause(),w(!0)}},$=()=>{if(!(!h()||!k()||!m())){if(I()==="edge"){N?.play().catch(()=>{}),w(!1);return}A()?.resume(),w(!1)}},K=()=>{k()?$():f()},Z=()=>B?j(B):Promise.resolve(!1),q=r=>{const o=u()||"es-ES-XimenaNeural",v=i.value.rate,C=ge(r,4e3),Y=`${o}|${v}|${r}`,Q=P.get(Y)??[];P.clear(),P.set(Y,Q);const V=T;y(!0),w(!1);const x=W=>{if(V===T){if(W>=C.length){w(!1),y(!1);return}(async()=>{let te=Q[W];try{te||(te=await t.client.rpc(U).edgeSpeak({text:C[W]??"",voice:o,rate:v}),Q[W]=te)}catch{V===T&&(w(!1),y(!1),t.toast.show({variant:"error",title:t.t("read.title"),description:t.t("error.edge")}));return}if(V!==T)return;const Ce=Uint8Array.from(atob(te),Ee=>Ee.charCodeAt(0)),ce=URL.createObjectURL(new Blob([Ce],{type:"audio/mpeg"}));L(),F=ce;const ie=new Audio(ce);N=ie,ie.onended=()=>{V===T&&(L(),x(W+1))},ie.onerror=()=>{V===T&&(L(),w(!1),y(!1))},await ie.play().catch(()=>{V===T&&(L(),w(!1),y(!1),t.toast.show({variant:"error",title:t.t("read.title"),description:t.t("error.edge")}))})})()}};return x(0),!0},j=async r=>{B=r;const o=Ue(r,i.value.skipCode);if(!o)return!1;if(s(),await H(),n()==="edge")return q(o);if(I()==="web"){const v=A();if(!v)return!1;const C=X(),Y=i.value.rate,Q=T;O=ge(o),y(!0),w(!1);const V=()=>{if(Q!==T)return;const x=O.shift();if(!x){w(!1),y(!1);return}const W=new SpeechSynthesisUtterance(x);C&&(W.voice=C),C?.lang&&(W.lang=C.lang),W.rate=Y,W.onend=V,W.onerror=V,v.speak(W)};return V(),!0}if(I()==="sapi"){y(!0),w(!1);try{await e.speak({text:o,rate:i.value.rate,voice:i.value.voiceURI})}catch(v){throw y(!1),v}return!0}throw y(!1),new Error(t.t("read.noVoices"))},J=async()=>{const r=t.sessions.current();if(!r)return"";const v=(await r.server.client.message.list({sessionID:r.id,type:"assistant",order:"desc",limit:1})).data[0];return!v||v.type!=="assistant"?"":v.content.filter(C=>C.type==="text").map(C=>C.text).join(`
|
|
192
|
+
`)};return e.on("done",()=>{w(!1),y(!1)}),A()?.addEventListener?.("voiceschanged",()=>{H()}),H(),{speaking:h,paused:k,backend:I,engine:n,voice:u,error:_,canPause:m,setConfig:g,voices:b,refreshVoices:H,lastAssistantText:J,speak:j,cancel:s,pause:f,resume:$,togglePause:K,restart:Z,preview:async()=>{const r=await j(t.t("speech.preview.text"));if(r){const o=n()==="edge"?`Edge · ${u()}`:`${I()} · ${i.value.voiceURI||"auto"}`;t.toast.show({variant:"default",title:t.t("read.title"),description:o})}return r}}}const Fe=["es-ES","es-MX","es-AR","es-CO","pt-BR","pt-PT","en-US","en-GB","fr-FR","de-DE","it-IT"];function He(t){const e=E.usePlugin(),i=e.ui,d=e.stores.prefs,n=e.stores.speech,l=e.stores.tts,u=e.stores.engine,c=e.stores.whisper,g=e.stores.whispercpp,h=e.main(oe),y=e.main(se),[k,w]=S.createSignal([]),[b,R]=S.createSignal(!1),[_,z]=S.createSignal(!1),[I,D]=S.createSignal(!1),[T,O]=S.createSignal(!1),[B,N]=S.createSignal("");S.onMount(()=>{t.speaker.setConfig({engine:l.value.engine,voice:l.value.voice});const r=()=>{h.diagnose().then(o=>{o.locales.length>0&&w(o.locales)}).catch(()=>{})};e.client.rpc(U).capabilities({}).then(o=>{const v=o;N(v.platform),!v.engines.includes(u.value.mode)&&v.engines.length>0&&m(v.engines[0]??"windows"),v.platform!=="linux"&&r()}).catch(()=>r()),t.speaker.refreshVoices()});const F=()=>B()==="linux"?[{value:"whispercpp",label:e.t("engine.whispercpp")}]:[{value:"windows",label:e.t("engine.windows")},{value:"system",label:e.t("engine.system")},{value:"whisper",label:e.t("engine.whisper")}],P=()=>{const r=k(),o=d.value.locale;return(r.includes(o)?r:[o,...r]).map(C=>({value:C,label:C}))},A=()=>{const r=t.speaker.voices().map(o=>({value:o.id,label:o.label}));return l.value.engine==="edge"?r:[{value:"",label:e.t("speech.voice.auto")},...r]},X=()=>l.value.engine==="edge"?l.value.voice:n.value.voiceURI,H=r=>{if(l.value.engine==="edge"){l.update(o=>{o.voice=r}),t.speaker.setConfig({voice:r});return}s({voiceURI:r})},L=r=>{d.update(o=>{r.locale!==void 0&&(o.locale=r.locale),r.minConfidence!==void 0&&(o.minConfidence=r.minConfidence),r.trailingSpace!==void 0&&(o.trailingSpace=r.trailingSpace),r.autoFocus!==void 0&&(o.autoFocus=r.autoFocus)})},s=r=>{n.update(o=>{r.voiceURI!==void 0&&(o.voiceURI=r.voiceURI),r.rate!==void 0&&(o.rate=r.rate),r.skipCode!==void 0&&(o.skipCode=r.skipCode)})},m=r=>{u.update(o=>{o.mode=r})},f=r=>{c.update(o=>{r.python!==void 0&&(o.python=r.python),r.model!==void 0&&(o.model=r.model),r.language!==void 0&&(o.language=r.language)})},$=r=>{g.update(o=>{r.model!==void 0&&(o.model=r.model),r.language!==void 0&&(o.language=r.language)})},K=async()=>{D(!0);try{const r=await e.client.rpc(U).whisperCppInstall({model:g.value.model});r.ok?e.toast.show({variant:"success",title:e.t("pill.title"),description:e.t("whispercpp.install.ok",{model:r.model})}):e.toast.show({variant:"error",title:e.t("pill.title"),description:e.t("whispercpp.install.error",{error:r.error||"?"}),persistent:!0})}catch(r){e.toast.show({variant:"error",title:e.t("pill.title"),description:e.t("whispercpp.install.error",{error:r instanceof Error?r.message:String(r)}),persistent:!0})}finally{D(!1)}},Z=async()=>{O(!0);try{const r=await e.client.rpc(U).whisperCppStatus({model:g.value.model});r.error?e.toast.show({variant:"error",title:e.t("pill.title"),description:e.t("whispercpp.test.error",{error:r.error}),persistent:!0}):r.installed?e.toast.show({variant:"success",title:e.t("pill.title"),description:e.t("whispercpp.test.ready",{model:r.model})}):e.toast.show({variant:"default",title:e.t("pill.title"),description:e.t("whispercpp.test.missing")})}catch(r){e.toast.show({variant:"error",title:e.t("pill.title"),description:e.t("whispercpp.test.error",{error:r instanceof Error?r.message:String(r)})})}finally{O(!1)}},q=async()=>{z(!0);try{const r=await e.client.rpc(U).whisperStatus({python:c.value.python,model:c.value.model});r.ready?e.toast.show({variant:"success",title:e.t("pill.title"),description:e.t("whisper.test.ready",{model:r.model,device:r.device})}):e.toast.show({variant:"error",title:e.t("pill.title"),description:e.t("whisper.test.error",{error:r.error||"?"})})}catch(r){e.toast.show({variant:"error",title:e.t("pill.title"),description:e.t("whisper.test.error",{error:r instanceof Error?r.message:String(r)})})}finally{z(!1)}},j=r=>{l.update(o=>{o.engine=r}),t.speaker.setConfig({engine:r}),t.speaker.refreshVoices()},J=async()=>{try{await y.toggle(),e.toast.show({variant:"default",title:e.t("settings.winh"),description:e.t("settings.winh.sent")})}catch(r){e.toast.show({variant:"error",title:e.t("pill.title"),description:r instanceof Error?r.message:e.t("error.winh")})}},ae=async()=>{try{await t.speaker.preview()}catch(r){e.toast.show({variant:"error",title:e.t("read.title"),description:r instanceof Error?r.message:e.t("read.error")})}},le=async()=>{R(!0);try{const r=await h.diagnose();if(r.locales.length===0){e.toast.show({variant:"error",title:e.t("error.title"),description:e.t("settings.test.failed",{reason:e.t("common.reason.no-recognizers")}),persistent:!0});return}if(!r.microphone){e.toast.show({variant:"error",title:e.t("error.title"),description:e.t("settings.test.failed",{reason:e.t("common.reason.no-microphone")}),persistent:!0});return}r.locales.length>0&&!k().length&&w(r.locales),e.toast.show({variant:"success",title:e.t("error.title"),description:e.t("settings.test.success",{locale:r.selected||r.locales[0]||d.value.locale})})}catch(r){e.toast.show({variant:"error",title:e.t("error.title"),description:r instanceof Error?r.message:String(r)})}finally{R(!1)}};return a.createComponent(i.SettingsLayout,{get title(){return e.t("settings.title")},get description(){return e.t("settings.description")},get children(){return[a.createComponent(i.SettingsSection,{get title(){return e.t("settings.section")},get children(){return[a.createComponent(i.SettingsRow,{id:"engine",get title(){return e.t("settings.engine")},get description(){return e.t("settings.engine.description")},get children(){return a.createComponent(i.Select,{get label(){return e.t("settings.engine")},get value(){return u.value.mode},get options(){return F()},onChange:r=>m(r)})}}),a.createComponent(S.Show,{get when(){return u.value.mode==="windows"},get children(){return a.createComponent(i.SettingsRow,{id:"winh",get title(){return e.t("settings.winh")},get description(){return e.t("settings.winh.description")},get children(){return a.createComponent(i.Button,{onClick:()=>{J()},get children(){return e.t("settings.winh.run")}})}})}}),a.createComponent(S.Show,{get when(){return u.value.mode==="whisper"},get children(){return[a.createComponent(i.SettingsRow,{id:"whisper-python",get title(){return e.t("whisper.python")},get description(){return e.t("whisper.python.description")},get children(){return a.createComponent(i.TextInput,{get label(){return e.t("whisper.python")},get value(){return c.value.python},onInput:r=>f({python:r})})}}),a.createComponent(i.SettingsRow,{id:"whisper-model",get title(){return e.t("whisper.model")},get description(){return e.t("whisper.model.description")},get children(){return a.createComponent(i.Select,{get label(){return e.t("whisper.model")},get value(){return c.value.model},options:[{value:"large-v3-turbo",label:"large-v3-turbo"},{value:"large-v3",label:"large-v3"},{value:"medium",label:"medium"}],onChange:r=>f({model:r})})}}),a.createComponent(i.SettingsRow,{id:"whisper-language",get title(){return e.t("whisper.language")},get description(){return e.t("whisper.language.description")},get children(){return a.createComponent(i.Select,{get label(){return e.t("whisper.language")},get value(){return c.value.language},get options(){return[{value:"",label:e.t("whisper.language.auto")},{value:"es",label:"Español"},{value:"en",label:"English"},{value:"pt",label:"Português"}]},onChange:r=>f({language:r})})}}),a.createComponent(i.SettingsRow,{id:"whisper-test",get title(){return e.t("whisper.test")},get description(){return e.t("whisper.test.description")},get children(){return a.createComponent(i.Button,{get busy(){return _()},onClick:()=>{q()},get children(){return e.t("whisper.test.run")}})}})]}}),a.createComponent(S.Show,{get when(){return u.value.mode==="whispercpp"},get children(){return[a.createComponent(i.SettingsRow,{id:"whispercpp-model",get title(){return e.t("whispercpp.model")},get description(){return e.t("whispercpp.model.description")},get children(){return a.createComponent(i.Select,{get label(){return e.t("whispercpp.model")},get value(){return g.value.model},options:[{value:"base-q5_1",label:"base-q5_1 · 57 MB"},{value:"small-q5_1",label:"small-q5_1 · 180 MB"}],onChange:r=>$({model:r})})}}),a.createComponent(i.SettingsRow,{id:"whispercpp-language",get title(){return e.t("whispercpp.language")},get description(){return e.t("whispercpp.language.description")},get children(){return a.createComponent(i.Select,{get label(){return e.t("whispercpp.language")},get value(){return g.value.language},get options(){return[{value:"auto",label:e.t("whispercpp.language.auto")},{value:"es",label:"Español"},{value:"en",label:"English"},{value:"pt",label:"Português"}]},onChange:r=>$({language:r})})}}),a.createComponent(i.SettingsRow,{id:"whispercpp-install",get title(){return e.t("whispercpp.install")},get description(){return e.t("whispercpp.install.description")},get children(){return a.createComponent(i.Button,{get busy(){return I()},onClick:()=>{K()},get children(){return e.t("whispercpp.install.run")}})}}),a.createComponent(i.SettingsRow,{id:"whispercpp-test",get title(){return e.t("whispercpp.test")},get description(){return e.t("whispercpp.test.description")},get children(){return a.createComponent(i.Button,{get busy(){return T()},onClick:()=>{Z()},get children(){return e.t("whispercpp.test.run")}})}})]}}),a.createComponent(S.Show,{get when(){return u.value.mode==="system"},get children(){return[a.createComponent(i.SettingsRow,{id:"language",get title(){return e.t("settings.language")},get description(){return e.t("settings.language.description")},get children(){return a.createComponent(i.Select,{get label(){return e.t("settings.language")},get value(){return d.value.locale},get options(){return a.memo(()=>P().length>0)()?P():Fe.map(r=>({value:r,label:r}))},onChange:r=>L({locale:r})})}}),a.createComponent(i.SettingsRow,{id:"confidence",get title(){return e.t("settings.confidence")},get description(){return e.t("settings.confidence.description")},get children(){return a.createComponent(i.Select,{get label(){return e.t("settings.confidence")},get value(){return String(d.value.minConfidence)},get options(){return[{value:"0.2",label:e.t("confidence.low")},{value:"0.4",label:e.t("confidence.normal")},{value:"0.6",label:e.t("confidence.high")}]},onChange:r=>L({minConfidence:Number(r)||.4})})}}),a.createComponent(i.SettingsRow,{id:"space",get title(){return e.t("settings.space")},get description(){return e.t("settings.space.description")},get children(){return a.createComponent(i.Switch,{get label(){return e.t("settings.space")},hideLabel:!0,get checked(){return d.value.trailingSpace},onChange:r=>L({trailingSpace:r})})}}),a.createComponent(i.SettingsRow,{id:"focus",get title(){return e.t("settings.focus")},get description(){return e.t("settings.focus.description")},get children(){return a.createComponent(i.Switch,{get label(){return e.t("settings.focus")},hideLabel:!0,get checked(){return d.value.autoFocus},onChange:r=>L({autoFocus:r})})}})]}})]}}),a.createComponent(i.SettingsSection,{get title(){return e.t("speech.section")},get children(){return[a.createComponent(i.SettingsRow,{id:"tts-engine",get title(){return e.t("speech.engine")},get description(){return e.t("speech.engine.description")},get children(){return a.createComponent(i.Select,{get label(){return e.t("speech.engine")},get value(){return l.value.engine},get options(){return[{value:"edge",label:e.t("speech.engine.edge")},{value:"system",label:e.t("speech.engine.system")}]},onChange:r=>j(r)})}}),a.createComponent(i.SettingsRow,{id:"voice",get title(){return e.t("speech.voice")},get description(){return e.t("speech.voice.description")},get children(){return a.createComponent(i.Select,{get label(){return e.t("speech.voice")},get value(){return X()},get options(){return A()},onChange:r=>H(r)})}}),a.createComponent(S.Show,{get when(){return t.speaker.error()},get children(){return a.createComponent(i.SettingsRow,{id:"tts-error",get title(){return e.t("speech.edge.error")},get description(){return t.speaker.error()}})}}),a.createComponent(i.SettingsRow,{id:"rate",get title(){return e.t("speech.rate")},get description(){return e.t("speech.rate.description")},get children(){return a.createComponent(i.Select,{get label(){return e.t("speech.rate")},get value(){return String(n.value.rate)},get options(){return[{value:"0.8",label:e.t("speech.rate.slow")},{value:"1",label:e.t("speech.rate.normal")},{value:"1.2",label:e.t("speech.rate.fast")},{value:"1.5",label:e.t("speech.rate.faster")}]},onChange:r=>s({rate:Number(r)||1})})}}),a.createComponent(i.SettingsRow,{id:"skip-code",get title(){return e.t("speech.skipCode")},get description(){return e.t("speech.skipCode.description")},get children(){return a.createComponent(i.Switch,{get label(){return e.t("speech.skipCode")},hideLabel:!0,get checked(){return n.value.skipCode},onChange:r=>s({skipCode:r})})}}),a.createComponent(i.SettingsRow,{id:"preview",get title(){return e.t("speech.preview")},get description(){return e.t("speech.preview.description")},get children(){return a.createComponent(i.Button,{onClick:()=>{ae()},get children(){return e.t("read.idle")}})}})]}}),a.createComponent(S.Show,{get when(){return B()!=="linux"},get children(){return a.createComponent(i.SettingsSection,{get title(){return e.t("settings.test.section")},get children(){return a.createComponent(i.SettingsRow,{id:"test",get title(){return e.t("settings.test")},get description(){return e.t("settings.test.description")},get children(){return a.createComponent(i.Button,{get busy(){return b()},onClick:()=>{le()},get children(){return e.t("settings.test.run")}})}})}})}})]}})}async function qe(){const t=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,noiseSuppression:!0}}),e=MediaRecorder.isTypeSupported("audio/webm;codecs=opus")?"audio/webm;codecs=opus":"audio/webm",i=new MediaRecorder(t,{mimeType:e}),d=[];return i.ondataavailable=n=>{n.data.size>0&&d.push(n.data)},i.start(),{stop:()=>new Promise(n=>{i.onstop=()=>{t.getTracks().forEach(l=>l.stop()),n(new Blob(d,{type:e}))},i.stop()})}}async function je(t){const e=new Uint8Array(await t.arrayBuffer());let i="";const d=32768;for(let n=0;n<e.length;n+=d)i+=String.fromCharCode(...e.subarray(n,n+d));return btoa(i)}function Me(t){const e=E.usePlugin();return a.memo(()=>t.actions().map(i=>a.createComponent(e.ui.Button,{get variant(){return i.id==="stop"?"danger":"neutral"},get icon(){return i.icon},onClick:()=>{i.run(),t.close()},get children(){return i.label}})))}const Ge=t=>{const e=t.main(oe),i=t.main(he),d=t.main(se),n=Be(t,i),l=t.stores.prefs,u=t.stores.engine,c=t.stores.whisper,g=t.stores.whispercpp,[h,y]=S.createSignal(!1),[k,w]=S.createSignal("idle");t.client.rpc(U).capabilities({}).then(s=>{const m=s;!m.engines.includes(u.value.mode)&&m.engines.length>0&&u.update(f=>{f.mode=m.engines[0]??"windows"})}).catch(()=>{});const b=()=>u.value.mode==="whisper"||u.value.mode==="whispercpp"?u.value.mode:void 0;let R,_;const z=(s,m,f)=>{switch(s){case"LANGUAGE_NOT_AVAILABLE":return t.t("error.language",{locale:m||l.value.locale,available:f.length>0?f.join(", "):"—"});case"NO_RECOGNIZERS":return t.t("error.none");case"MICROPHONE_UNAVAILABLE":return t.t("error.mic");case"SYSTEM_SPEECH_UNAVAILABLE":return t.t("error.system");case"ENGINE_EXIT":return t.t("error.exit");default:return t.t("error.unknown",{code:s})}},I=()=>{h()&&(y(!1),d.toggle().catch(()=>{})),n.cancel(),R=t.sessions.current()?.key,e.start({locale:l.value.locale,minConfidence:l.value.minConfidence}).catch(s=>{t.toast.show({variant:"error",title:t.t("error.title"),description:s instanceof Error?s.message:String(s)})})},D=()=>{e.stop().catch(()=>{})},T=()=>{n.cancel(),t.screen.current()?.composer.focus(),d.toggle().then(()=>y(s=>!s)).catch(s=>{y(!1),t.toast.show({variant:"error",title:t.t("pill.title"),description:s instanceof Error?`${t.t("error.winh")} ${s.message}`:t.t("error.winh")})})},O=()=>t.t(b()==="whispercpp"?"error.whispercpp":"error.whisper"),B=async()=>{if(k()==="idle"){n.cancel();try{_=await qe(),w("recording")}catch(s){t.toast.show({variant:"error",title:t.t("pill.title"),description:s instanceof Error?s.message:O()})}}},N=()=>{const s=_;_=void 0,s&&s.stop().catch(()=>{}),w("idle")},F=async()=>{const s=_;if(_=void 0,!s){w("idle");return}w("transcribing");const m=b();try{const f=await s.stop(),$=await je(f),Z=(m==="whispercpp"?await t.client.rpc(U).whisperCppTranscribe({audio:$,mime:f.type,language:g.value.language,model:g.value.model}):await t.client.rpc(U).whisperTranscribe({audio:$,mime:f.type,language:c.value.language,python:c.value.python,model:c.value.model})).text.trim();if(!Z){t.toast.show({variant:"default",title:t.t("pill.title"),description:t.t(m==="whispercpp"?"whispercpp.empty":"whisper.empty")});return}const q=t.screen.current()?.composer;if(q){const j=q.text(),J=j.length>0&&!/\s$/.test(j)?" ":"";q.insert(J+Z),q.focus()}}catch(f){t.toast.show({variant:"error",title:t.t("pill.title"),description:f instanceof Error?f.message:O()})}finally{w("idle")}},P=()=>{if(b()){k()==="recording"?F():k()==="idle"&&B();return}if(u.value.mode==="windows"){T();return}const s=e.state()?.status??"idle";s==="starting"||s==="listening"?D():I()},A=()=>{if(k()==="recording"&&N(),n.speaking()){n.cancel();return}if(!t.sessions.current()){t.toast.show({variant:"error",title:t.t("read.title"),description:t.t("read.noSession")});return}const s=e.state()?.status??"idle";(s==="starting"||s==="listening")&&e.stop().catch(()=>{}),(async()=>{try{const m=await n.lastAssistantText();if(!m.trim()){t.toast.show({variant:"default",title:t.t("read.title"),description:t.t("read.empty")});return}await n.speak(m)||t.toast.show({variant:"default",title:t.t("read.title"),description:t.t("read.empty")})}catch(m){t.toast.show({variant:"error",title:t.t("read.title"),description:m instanceof Error?m.message:t.t("read.error")})}})()},X=()=>{if(!n.speaking())return[{id:"start",label:t.t("read.idle"),icon:"outline-audio",run:A}];const s=n.paused(),m=[{id:"restart",label:t.t("read.restart"),icon:"outline-reset",run:()=>{n.restart()}}];return n.canPause()&&m.push({id:"pause",label:s?t.t("read.resume"):t.t("read.pause"),icon:"outline-audio",run:()=>n.togglePause()}),m.push({id:"stop",label:t.t("read.stop"),icon:"xmark-small",run:()=>n.cancel()}),m};e.on("final",s=>{const m=t.sessions.current()?.key;if(R&&m&&m!==R){t.toast.show({variant:"error",title:t.t("error.title"),description:t.t("toast.discarded")});return}const f=t.screen.current()?.composer;if(!f)return;const $=f.text(),K=l.value.trailingSpace&&$.length>0&&!/\s$/.test($)?" ":"";f.insert(K+s.text),l.value.autoFocus&&f.focus()}),e.on("error",s=>{t.toast.show({variant:"error",title:t.t("error.title"),description:z(s.code,s.detail,s.available),persistent:s.code==="LANGUAGE_NOT_AVAILABLE"||s.code==="NO_RECOGNIZERS",duration:8e3})});const H=()=>{if(t.app.platform!=="desktop")return;if(u.value.mode==="windows")return h()?{label:t.t("pill.dictating"),icon:"outline-wave-audio",tone:"danger",title:t.t("pill.stop"),run:P}:{label:t.t("pill.idle"),icon:"outline-wave-audio",title:t.t("pill.winH"),run:P};if(u.value.mode==="whisper"||u.value.mode==="whispercpp"){const m=t.t(u.value.mode==="whispercpp"?"pill.whispercpp":"pill.whisper");return k()==="recording"?{label:t.t("pill.recording"),icon:"outline-wave-audio",tone:"danger",title:t.t("pill.stop"),run:P}:k()==="transcribing"?{label:t.t("pill.transcribing"),icon:"outline-audio",title:m}:{label:t.t("pill.idle"),icon:"outline-wave-audio",title:m,run:P}}const s=e.state()?.status??"idle";return s==="starting"?{label:t.t("pill.starting"),icon:"outline-wave-audio",title:t.t("pill.title"),run:P}:s==="listening"?{label:t.t("pill.listening"),icon:"outline-wave-audio",tone:"danger",title:t.t("pill.stop"),run:P}:s==="error"?{label:t.t("pill.error"),icon:"outline-wave-audio",tone:"warning",title:t.t("pill.title"),run:P}:{label:t.t("pill.idle"),icon:"outline-wave-audio",title:t.t("pill.title"),run:P}},L=()=>{if(t.app.platform!=="desktop")return;const s=n.speaking(),m=n.paused(),f=s?X().map($=>({id:$.id,label:$.label,icon:$.icon,tone:$.id==="stop"?"danger":void 0,run:$.run})):[];return{label:s?m?t.t("read.paused"):t.t("read.speaking"):t.t("read.idle"),icon:"outline-audio",tone:s?"success":void 0,title:t.t("read.title"),rows:f.length>0?f:void 0,run:s?void 0:A}};t.add(E.ComposerPill,()=>({id:"dictate",show:()=>H()})),t.add(E.ComposerPill,()=>({id:"read",order:1,show:()=>L(),menu:s=>a.createComponent(Me,{actions:X,get close(){return s.close}})})),t.add(E.Command,()=>({id:"toggle",title:t.t("command.toggle"),description:t.t("command.toggle.description"),keywords:"voice dictation microphone speech dictado voz microfono",group:t.t("pill.title"),section:"prompt",bind:"mod+shift+m",slash:{name:"dictado"},editable:!0,enabled:t.app.platform==="desktop",run:P})),t.add(E.Command,()=>({id:"configure",title:t.t("command.configure"),keywords:"voice dictation microphone speech dictado voz microfono",group:t.t("pill.title"),enabled:t.app.platform==="desktop",run:()=>t.layout.settings(t.id)})),t.add(E.Command,()=>({id:"read",title:t.t("command.read"),description:t.t("command.read.description"),keywords:"read aloud speech voice leer voz altavoz lectura",group:t.t("pill.title"),section:"prompt",bind:"mod+shift+l",slash:{name:"leer"},editable:!0,enabled:t.app.platform==="desktop",run:A})),t.add(E.SettingsPage,{id:t.id,icon:"outline-audio",get title(){return t.t("settings.title")},get entries(){return[{id:"engine",title:t.t("settings.engine"),keywords:"engine motor dictation dictado winh voz"},{id:"winh",title:t.t("settings.winh"),keywords:"winh windows voice typing dictado"},{id:"language",title:t.t("settings.language"),keywords:"voice language idioma voz"},{id:"confidence",title:t.t("settings.confidence"),keywords:"voice confidence confianza"},{id:"test",title:t.t("settings.test"),keywords:"voice microphone microphone test microfono"},{id:"voice",title:t.t("speech.voice"),keywords:"read aloud voice voz lectura altavoz"},{id:"tts-engine",title:t.t("speech.engine"),keywords:"voice engine motor edge neural sistema"},{id:"whisper-python",title:t.t("whisper.python"),keywords:"whisper python venv entorno"},{id:"whisper-model",title:t.t("whisper.model"),keywords:"whisper model modelo"},{id:"whisper-language",title:t.t("whisper.language"),keywords:"whisper language idioma"},{id:"whisper-test",title:t.t("whisper.test"),keywords:"whisper check comprobar"},{id:"whispercpp-model",title:t.t("whispercpp.model"),keywords:"whisper.cpp model modelo"},{id:"whispercpp-language",title:t.t("whispercpp.language"),keywords:"whisper.cpp language idioma"},{id:"whispercpp-install",title:t.t("whispercpp.install"),keywords:"whisper.cpp download descargar instalar"},{id:"whispercpp-test",title:t.t("whispercpp.test"),keywords:"whisper.cpp check comprobar"},{id:"rate",title:t.t("speech.rate"),keywords:"read aloud speed velocidad lectura"},{id:"skip-code",title:t.t("speech.skipCode"),keywords:"read aloud code codigo lectura"},{id:"preview",title:t.t("speech.preview"),keywords:"read aloud test voice probar voz"}]},render:()=>a.createComponent(He,{speaker:n})})},Xe=E.Plugin.define({...ke,setup:Ge,main:De});exports.default=Xe;
|