opencode-voice-dictation 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,12 +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
- - **Solo Windows**: la app declara `os: ["windows"]`; en otros sistemas aparece
19
- como no disponible en vez de fallar.
20
+ - **WhisperX local (avanzado)**: dictado con WhisperX en WSL y GPU NVIDIA. Ver
21
+ [la guía](https://github.com/evious/opencode-voice-dictation/blob/main/docs/whisperx.md).
22
+ - **Windows y Linux**: la app declara `os: ["windows", "linux"]` y muestra en
23
+ cada sistema solo los motores que existen.
20
24
 
21
25
  ## Instalación rápida
22
26
 
@@ -42,8 +46,9 @@ opencode plugin add github:evious/opencode-voice-dictation
42
46
 
43
47
  ## Compatibilidad
44
48
 
45
- Probado con OpenCode Desktop **2.2.6** en Windows 11. La API de plugins GUI es
46
- experimental (`gui-experimental`) y puede cambiar entre versiones de OpenCode.
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.
47
52
 
48
53
  ## Cómo funciona
49
54
 
@@ -69,8 +74,14 @@ píldora enfoca el composer, el proceso `main` envía `Win+H` con `keybd_event`,
69
74
  Windows escribe directamente en el campo. El plugin no transcribe ni toca el
70
75
  texto.
71
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
+
72
81
  ## Requisitos
73
82
 
83
+ **Windows**
84
+
74
85
  - OpenCode Desktop en **Windows 10/11**.
75
86
  - Node.js 22 o superior para compilar (solo en desarrollo).
76
87
  - Un **paquete de idioma de reconocimiento de voz** instalado:
@@ -78,6 +89,13 @@ texto.
78
89
  Reconocimiento de voz. No basta con el paquete de visualización.
79
90
  - Micrófono predeterminado configurado.
80
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
+
81
99
  Comprobación rápida en PowerShell:
82
100
 
83
101
  ```powershell
@@ -151,17 +169,44 @@ dictado, para que el micrófono no capture la voz del altavoz.
151
169
  El texto se inserta en la posición del cursor. Si ya hay texto y no termina en
152
170
  espacio, se añade uno de separación (configurable).
153
171
 
172
+ ## Motores de dictado
173
+
174
+ | Motor | Requisitos | Estado |
175
+ |---|---|---|
176
+ | Windows Voice Typing (Win+H) | Windows 11, internet | Por defecto en Windows |
177
+ | System.Speech | Windows, offline | Disponible |
178
+ | WhisperX local (WSL) | WSL2 + Python + CUDA + NVIDIA | Avanzado · [guía](https://github.com/evious/opencode-voice-dictation/blob/main/docs/whisperx.md) |
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) |
180
+ | Google Cloud Speech-to-Text | API key, internet | Roadmap |
181
+ | OpenAI (`gpt-4o-transcribe`) | API key, internet | Roadmap |
182
+ | Deepgram / AssemblyAI | API key, internet | Roadmap |
183
+
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.
186
+
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.
190
+
154
191
  ## Ajustes
155
192
 
156
193
  | Ajuste | Por defecto | Qué hace |
157
194
  |---|---|---|
158
- | Motor de dictado | Windows Voice Typing (Win+H) | Usa el dictado de Windows 11, más preciso. `System.Speech` es la alternativa offline. |
159
- | 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. |
160
197
  | Idioma | `es-ES` | Solo para `System.Speech`. Reconocedor instalado en Windows. Si no existe el exacto, usa el mismo idioma base. |
161
198
  | Confianza mínima | 0,4 | Solo para `System.Speech`. Descarta resultados con menor confianza. |
162
199
  | Espacio antes del texto | Sí | Solo para `System.Speech`. Separa el dictado de lo que ya hay escrito. |
163
200
  | Enfocar el prompt al terminar | Sí | Solo para `System.Speech`. Mueve el cursor al prompt tras cada frase. |
164
201
  | Probar micrófono | — | Solo para `System.Speech`. Lista los idiomas instalados y comprueba el micrófono. |
202
+ | Python del entorno | Ruta del venv de WhisperX | Solo WhisperX. Python del venv con `whisperx`. |
203
+ | Modelo (WhisperX) | `large-v3-turbo` | Solo WhisperX. `large-v3` o `medium` si necesitas otra cosa. |
204
+ | Idioma (WhisperX) | Automático | Solo WhisperX. Autodetección o `es`, `en`, `pt`. |
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. |
165
210
 
166
211
  ### Lectura en voz alta
167
212
 
@@ -187,7 +232,17 @@ PowerShell (SAPI) no hay pausa real: el menú muestra solo «Leer de nuevo» y
187
232
 
188
233
  ## Permisos
189
234
 
190
- El plugin es local y no hace peticiones de red. Para funcionar:
235
+ El plugin funciona en local salvo lo que elijas y use red:
236
+
237
+ - **WhisperX**: escribe el audio en `%TEMP%\opencode-voice-dictation`, lo pasa a
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.
242
+ - **Edge**: envía el texto a Microsoft para sintetizar la voz (online).
243
+ - **Futuros motores en la nube**: enviarán el audio al proveedor que configures.
244
+
245
+ En **Windows**, para el dictado/lectura:
191
246
 
192
247
  - Escribe un script `dictation-<hash>.ps1` o `speak-<hash>.ps1` en
193
248
  `%TEMP%\opencode-voice-dictation`.
@@ -198,6 +253,10 @@ Por eso Windows y antivirus como Kaspersky pueden pedir permiso la primera vez:
198
253
  es esperado. Elige **Permitir ahora** y marca **Recordar mi elección**. Si
199
254
  bloqueas el micrófono, verás el aviso «No hay ningún micrófono disponible».
200
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
+
201
260
  ## Solución de problemas
202
261
 
203
262
  | Síntoma | Causa / solución |
@@ -209,11 +268,17 @@ bloqueas el micrófono, verás el aviso «No hay ningún micrófono disponible»
209
268
  | «Windows no tiene ningún reconocedor de voz instalado» | Mismo caso: no hay ningún idioma de voz instalado. |
210
269
  | «No hay ningún micrófono disponible» | Revisa Configuración → Sistema → Sonido → Entrada y el micrófono predeterminado. |
211
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. |
212
- | 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. |
213
- | No transcribe nada | Sube el nivel de entrada del micrófono y baja la confianza mínima a 0,2. |
214
- | «Windows no tiene voces instaladas para la lectura» | Instala una voz en Configuración → Hora e idioma → Voz, o revisa que `System.Speech` esté disponible. El plugin usa primero las voces de Chromium y cae a SAPI si no hay. |
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. |
215
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. |
216
275
  | «Abre una conversación para leer su último mensaje» | En Home o en un chat nuevo sin mensajes no hay respuesta que leer. |
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. |
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. |
281
+ | WhisperX pide token de Hugging Face | `huggingface-cli login` y acepta los términos del modelo de VAD de `pyannote`. |
217
282
 
218
283
  Los errores del plugin se registran en el log de OpenCode Desktop; los `toast`
219
284
  de la app muestran los más importantes.
@@ -224,14 +289,26 @@ de la app muestran los más importantes.
224
289
  cambiar entre versiones de OpenCode.
225
290
  - `System.Speech` no añade puntuación ni mayúsculas automáticamente y su
226
291
  precisión es inferior a Whisper o al dictado nativo de Windows 11.
292
+ - **WhisperX local** requiere WSL2, Python, CUDA y una GPU NVIDIA. No es
293
+ portable: es un motor avanzado para equipos ya preparados.
294
+ - El primer dictado con WhisperX tarda 10–30 s porque carga el modelo en la GPU;
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.
227
303
  - El modo Win+H envía la combinación con `keybd_event`. Kaspersky o la
228
304
  protección UIPI de Windows pueden bloquearla; en ese caso usa `System.Speech`.
229
305
  - Con Win+H el texto lo escribe Windows en el campo enfocado: el plugin no
230
306
  puede descartarlo si cambias de sesión ni limpiarlo.
231
- - No hay resultados parciales con `System.Speech`: el texto aparece al terminar
232
- cada frase.
233
- - La lectura usa las voces del sistema; si Electron no las expone, el respaldo
234
- por PowerShell usa las voces SAPI, con una latencia de arranque de ~0,5 s.
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).
235
312
  - La lectura empieza con el mensaje tal como esté en ese momento: si el agente
236
313
  sigue escribiendo, leerá la versión parcial.
237
314
  - Una sola dictación a la vez (hay un micrófono). Si empiezas en otra ventana,
@@ -239,22 +316,61 @@ de la app muestran los más importantes.
239
316
  - Si cambias de sesión durante el dictado, el texto se descarta con un aviso
240
317
  para no escribirlo en la sesión equivocada.
241
318
 
242
- El motor está aislado en `src/gui/engines/`: para cambiar a Whisper local o a
243
- una API en la nube basta con respetar el mismo contrato (`startEngine` y los
244
- eventos `ready` / `final` / `error`).
319
+ El dictado vive en `src/gui` (Win+H y System.Speech) y en el servidor
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.
322
+
323
+ ## Añadir un motor de dictado nuevo
324
+
325
+ La ventana ya graba el audio (`getUserMedia` + `MediaRecorder`) y lo envía por
326
+ RPC al servidor. Eso es reutilizable para cualquier motor: local o en la nube.
327
+
328
+ 1. **Contrato RPC** (`src/rpc.ts`): añade un método
329
+ `transcribe({ audio, mime, language, ... })` o uno por motor.
330
+ 2. **Servidor** (`src/index.ts`): implementa el método. Para la nube, llama al
331
+ proveedor desde el servidor, nunca desde la ventana.
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
335
+ (API key, idioma, modelo).
336
+ 5. **Ventana** (`src/gui/window.tsx`): enruta el modo del motor a la grabación y
337
+ al RPC; inserta con `composer.insert()` o copia con `ctx.system.copy()` si
338
+ quieres dictar hacia otra app.
339
+
340
+ Proveedores previstos:
341
+
342
+ - **Google Cloud Speech-to-Text**: cuenta de servicio o API key,
343
+ `@google-cloud/speech`, audio `WEBM_OPUS` o `LINEAR16` 16 kHz.
344
+ - **OpenAI**: `gpt-4o-transcribe` o `whisper-1`,
345
+ `POST /v1/audio/transcriptions`, API key.
346
+ - **Deepgram / AssemblyAI**: `POST /v1/listen`, API key.
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.
350
+
351
+ Las claves de API no deben viajar al renderer. Guárdalas en el store del plugin
352
+ o, mejor, en variables de entorno del servidor.
245
353
 
246
354
  ## Desarrollo
247
355
 
248
356
  ```
249
357
  src/
250
- ├── index.ts Parte servidor: fija el id "voice-dictation"
358
+ ├── index.ts Parte servidor: registra los RPC y capabilities
359
+ ├── rpc.ts Contrato RPC compartido (voces, síntesis, dictado)
360
+ ├── edge.ts Voces y síntesis de Edge (servidor)
361
+ ├── whisper/
362
+ │ ├── server.ts Worker persistente, spawn WSL/Node, timeouts
363
+ │ └── worker.py WhisperX en la GPU; protocolo JSON por stdin/stdout
364
+ ├── whispercpp/
365
+ │ └── server.ts Linux: descarga, whisper-server persistente, ffmpeg
251
366
  ├── gui.tsx Entrada ./gui-experimental
252
367
  └── gui/
253
368
  ├── definition.ts os, stores, i18n es/en
254
369
  ├── contract.ts Ipc: dictado y lectura
255
370
  ├── window.tsx Píldoras, comandos e inserción de texto
256
- ├── main.ts Dictado y voz de respaldo
257
- ├── speak.ts Limpieza de Markdown, troceado y speechSynthesis
371
+ ├── recorder.ts getUserMedia + MediaRecorder para WhisperX/whisper.cpp
372
+ ├── main.ts Dictado Win+H / System.Speech y voz SAPI
373
+ ├── speak.ts Limpieza de Markdown, troceado, Edge y speechSynthesis
258
374
  ├── settings.tsx Página de ajustes
259
375
  └── engines/
260
376
  ├── script.ts Script de PowerShell para el dictado
@@ -263,10 +379,16 @@ src/
263
379
  └── sapi-speech.ts Voz de respaldo y listado de voces SAPI
264
380
  ```
265
381
 
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).
386
+
266
387
  `npm run typecheck` valida los tipos, `npm run build` genera el bundle CJS y
267
388
  `npm test` ejecuta el smoke test (`test/smoke.cjs`): simula el host de la app,
268
- un `powershell.exe` falso y la API de voz del navegador para comprobar dictado,
269
- lectura, limpieza de Markdown, cancelación, fallback y errores.
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.
270
392
 
271
393
  ## Publicar
272
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 de(){const e=M.join(te.tmpdir(),"opencode-voice-dictation");j.mkdirSync(e,{recursive:!0});const t=Q.createHash("sha1").update(oe).digest("hex").slice(0,12),i=M.join(e,`dictation-${t}.ps1`);return j.writeFileSync(i,oe,"utf8"),i}function pe(e){return K.spawn($e,e,{windowsHide:!0,stdio:["ignore","pipe","pipe"]})}function ue(e){const t=e.pid;if(t===void 0){e.kill();return}K.spawn("taskkill",["/PID",String(t),"/T","/F"],{windowsHide:!0,stdio:"ignore"}).on("error",()=>e.kill())}function Ee(e,t){const i=pe(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",de(),"-Culture",e.locale,"-MinConfidence",String(e.minConfidence)]);let l=!1,r="",o="";const u=s=>{switch(s.type){case"ready":t.onReady(s.locale??e.locale);break;case"final":s.text&&t.onFinal(s.text,s.confidence??0);break;case"error":t.onError(s.code??"UNKNOWN",s.detail??"",s.available??[]);break}};return i.stdout?.setEncoding("utf8"),i.stdout?.on("data",s=>{o+=s;let p=o.indexOf(`
109
- `);for(;p>=0;){const g=o.slice(0,p).replace(/\r$/,"").trim();if(o=o.slice(p+1),g)try{u(JSON.parse(g))}catch{}p=o.indexOf(`
110
- `)}}),i.stderr?.setEncoding("utf8"),i.stderr?.on("data",s=>{r=(r+s).slice(-2e3)}),i.on("error",s=>{l||t.onError("ENGINE_START_FAILED",s.message,[])}),i.on("close",s=>{l||t.onExit(s,r.trim().slice(-500))}),{stop(){l=!0,ue(i)}}}function ke(){return new Promise((e,t)=>{const i=pe(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",de(),"-Diagnose"]);let l="",r="",o=!1;const u=(p,g)=>{o||(o=!0,clearTimeout(s),p?t(p):e(g))},s=setTimeout(()=>{ue(i),u(new Error("diagnose timed out"))},2e4);i.stdout?.setEncoding("utf8"),i.stdout?.on("data",p=>{l+=p}),i.stderr?.setEncoding("utf8"),i.stderr?.on("data",p=>{r+=p}),i.on("error",p=>u(p)),i.on("close",()=>{const p=l.split(/\r?\n/).map(g=>g.trim()).filter(g=>g.startsWith("{")).at(-1);if(!p){u(new Error(r.trim()||"no output from PowerShell"));return}try{const g=JSON.parse(p);u(void 0,{locales:g.locales??[],microphone:g.microphone===!0,selected:g.selected??"",error:g.error??""})}catch(g){u(g instanceof Error?g:new Error(String(g)))}})})}const se=String.raw`param(
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
- `,Re="powershell.exe";function ge(){const e=M.join(te.tmpdir(),"opencode-voice-dictation");return j.mkdirSync(e,{recursive:!0}),e}function he(){const e=Q.createHash("sha1").update(se).digest("hex").slice(0,12),t=M.join(ge(),`speak-${e}.ps1`);return j.writeFileSync(t,se,"utf8"),t}function Te(e){const t=M.join(ge(),`speech-${Q.randomUUID()}.txt`);return j.writeFileSync(t,e,"utf8"),t}function me(e){return K.spawn(Re,e,{windowsHide:!0,stdio:["ignore","pipe","pipe"]})}function fe(e){const t=e.pid;if(t===void 0){e.kill();return}K.spawn("taskkill",["/PID",String(t),"/T","/F"],{windowsHide:!0,stdio:"ignore"}).on("error",()=>e.kill())}function Ie(e){return Math.max(-10,Math.min(10,Math.round((e-1)*5)))}function Ae(e,t){const i=Te(e.text),l=me(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",he(),"-TextFile",i,"-Rate",String(Ie(e.rate)),"-Voice",e.voice]);let r=!1;const o=()=>{try{j.rmSync(i,{force:!0})}catch{}};return l.on("error",()=>{o(),r||t(!1)}),l.on("close",u=>{o(),r||t(u===0)}),{stop(){r=!0,fe(l),o()}}}function Ne(){return new Promise(e=>{const t=me(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",he(),"-ListVoices"]);let i="",l=!1;const r=u=>{l||(l=!0,clearTimeout(o),e(u))},o=setTimeout(()=>{fe(t),r([])},1e4);t.stdout?.setEncoding("utf8"),t.stdout?.on("data",u=>{i+=u}),t.on("error",()=>r([])),t.on("close",()=>{const u=i.split(/\r?\n/).map(s=>s.trim()).filter(s=>s.length>0);r(u)})})}const ae=String.raw`$ErrorActionPreference = "Stop"
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
- `,Pe="powershell.exe";function We(){const e=M.join(te.tmpdir(),"opencode-voice-dictation");j.mkdirSync(e,{recursive:!0});const t=Q.createHash("sha1").update(ae).digest("hex").slice(0,12),i=M.join(e,`win-h-${t}.ps1`);return j.writeFileSync(i,ae,"utf8"),i}function Le(e){return K.spawn(Pe,e,{windowsHide:!0,stdio:"ignore"})}function _e(){return new Promise((e,t)=>{const i=Le(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",We()]);i.on("error",l=>t(l)),i.on("close",l=>{l===0?e():t(new Error(`Win+H helper exited with code ${l??"unknown"}`))})})}const Ve=e=>{let t="idle",i="",l="",r,o=!1;const u=()=>{r=void 0,o=!1},s=e.provide(ie,{state:()=>({status:t,error:i,locale:l}),start(f,E){r&&(o=!0,r.stop(),u()),t="starting",i="",l=f.locale,s.changed();const S=E.window;try{r=Ee({locale:f.locale,minConfidence:f.minConfidence},{onReady(y){t="listening",l=y,s.changed()},onFinal(y,N){s.emit("final",{text:y,confidence:N},S)},onError(y,N,F){t="error",i=y,s.changed(),s.emit("error",{code:y,detail:N,available:F},S)},onExit(y,N){if(o){u(),t="idle",i="",l="",s.changed();return}u(),t!=="error"&&(t="error",i=y===null?"ENGINE_EXIT":`ENGINE_EXIT_${y}`,s.changed(),s.emit("error",{code:"ENGINE_EXIT",detail:N||(y===null?"":String(y)),available:[]},S))}})}catch(y){throw t="error",i="ENGINE_START_FAILED",s.changed(),y}},stop(){r&&(o=!0,r.stop(),u()),t="idle",i="",l="",s.changed()},diagnose(){return ke()}});let p;const g=e.provide(le,{speak(f){p?.stop(),p=Ae(f,E=>{p=void 0,g.emit("done",{ok:E})})},stop(){p&&(p.stop(),p=void 0)},voices(){return Ne()}});e.provide(ne,{toggle(){return _e()}}),e.onCleanup(()=>{r&&(o=!0,r.stop(),u()),p?.stop(),p=void 0})},J={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}}},events:{}},ze=250;function Oe(e,t){let i=e.replace(/\r\n/g,`
191
- `);return t?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 ce(e,t=ze){const i=e.trim();if(!i)return[];const l=i.split(new RegExp("(?<=[.!?…])\\s+")),r=[];let o="";const u=s=>{let p="";for(const g of s.split(/\s+/))g&&(p&&p.length+1+g.length>t?(r.push(p),p=g):p=p?`${p} ${g}`:g);return p};for(const s of l){if(s.length>t){o&&(r.push(o),o=""),o=u(s);continue}o&&o.length+1+s.length>t?(r.push(o),o=s):o=o?`${o} ${s}`:s}return o&&r.push(o),r}function Fe(e,t){const i=e.stores.speech,l=e.stores.tts.value,[r,o]=R.createSignal(l.engine==="edge"?"edge":"system"),[u,s]=R.createSignal(l.voice),p=v=>{(v.engine==="edge"||v.engine==="system")&&o(v.engine),v.voice!==void 0&&s(v.voice)},[g,f]=R.createSignal(!1),[E,S]=R.createSignal(!1),[y,N]=R.createSignal([]),[F,O]=R.createSignal(""),[P,U]=R.createSignal("none");let I=0,V=[],k="",A,L;const B=new Map,_=()=>typeof globalThis.speechSynthesis<"u"?globalThis.speechSynthesis:void 0,a=()=>{const v=_()?.getVoices()??[];if(v.length===0)return;const w=i.value.voiceURI;if(w){const T=v.find(q=>q.voiceURI===w);if(T)return T}const C=e.locale.locale().split("-")[0]?.toLowerCase()??"es";return v.find(T=>T.lang.toLowerCase().startsWith(C))??v[0]},m=async()=>{if(r()==="edge"){try{const w=await e.client.rpc(J).edgeVoices({});w.length>0?(N(w.map(C=>({id:C.id,label:C.label}))),O("")):O(e.t("speech.edge.error"))}catch(w){O(w instanceof Error?w.message:String(w))}U("edge");return}O("");const v=_()?.getVoices()??[];if(v.length>0){N(v.map(w=>({id:w.voiceURI,label:`${w.name} — ${w.lang}`}))),U("web");return}try{const w=await t.voices();if(w.length>0){N(w.map(C=>({id:C,label:C}))),U("sapi");return}}catch{}U("none")},b=()=>{A&&(A.onended=null,A.onerror=null,A.pause(),A=void 0),L&&(URL.revokeObjectURL(L),L=void 0)},n=()=>{I++,V=[],_()?.cancel(),b(),S(!1),f(!1),P()==="sapi"&&t.stop().catch(()=>{})},h=()=>P()==="web"||P()==="edge",D=()=>{if(!(!g()||E()||!h())){if(P()==="edge"){A?.pause(),S(!0);return}_()?.pause(),S(!0)}},H=()=>{if(!(!g()||!E()||!h())){if(P()==="edge"){A?.play().catch(()=>{}),S(!1);return}_()?.resume(),S(!1)}},x=()=>{E()?H():D()},we=()=>k?ee(k):Promise.resolve(!1),ve=v=>{const w=u()||"es-ES-XimenaNeural",C=i.value.rate,T=ce(v,4e3),q=`${w}|${C}|${v}`,G=B.get(q)??[];B.clear(),B.set(q,G);const z=I;f(!0),S(!1);const X=W=>{if(z===I){if(W>=T.length){S(!1),f(!1);return}(async()=>{let Z=G[W];try{Z||(Z=await e.client.rpc(J).edgeSpeak({text:T[W]??"",voice:w,rate:C}),G[W]=Z)}catch{z===I&&(S(!1),f(!1),e.toast.show({variant:"error",title:e.t("read.title"),description:e.t("error.edge")}));return}if(z!==I)return;const ye=Uint8Array.from(atob(Z),be=>be.charCodeAt(0)),re=URL.createObjectURL(new Blob([ye],{type:"audio/mpeg"}));b(),L=re;const Y=new Audio(re);A=Y,Y.onended=()=>{z===I&&(b(),X(W+1))},Y.onerror=()=>{z===I&&(b(),S(!1),f(!1))},await Y.play().catch(()=>{z===I&&(b(),S(!1),f(!1),e.toast.show({variant:"error",title:e.t("read.title"),description:e.t("error.edge")}))})})()}};return X(0),!0},ee=async v=>{k=v;const w=Oe(v,i.value.skipCode);if(!w)return!1;if(n(),await m(),r()==="edge")return ve(w);if(P()==="web"){const C=_();if(!C)return!1;const T=a(),q=i.value.rate,G=I;V=ce(w),f(!0),S(!1);const z=()=>{if(G!==I)return;const X=V.shift();if(!X){S(!1),f(!1);return}const W=new SpeechSynthesisUtterance(X);T&&(W.voice=T),T?.lang&&(W.lang=T.lang),W.rate=q,W.onend=z,W.onerror=z,C.speak(W)};return z(),!0}if(P()==="sapi"){f(!0),S(!1);try{await t.speak({text:w,rate:i.value.rate,voice:i.value.voiceURI})}catch(C){throw f(!1),C}return!0}throw f(!1),new Error(e.t("read.noVoices"))},Se=async()=>{const v=e.sessions.current();if(!v)return"";const C=(await v.server.client.message.list({sessionID:v.id,type:"assistant",order:"desc",limit:1})).data[0];return!C||C.type!=="assistant"?"":C.content.filter(T=>T.type==="text").map(T=>T.text).join(`
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&&!r.installed?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;