opencode-voice-dictation 0.6.2 → 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,14 +11,16 @@ escritorio:
11
11
  - **Dictado preciso**: por defecto delega en Windows Voice Typing (Win+H).
12
12
  - **Modo offline**: `System.Speech` sigue disponible como alternativa sin
13
13
  conexión y sin depender del dictado de Windows.
14
+ - **Linux nativo**: en Ubuntu, `whisper.cpp` transcribe en la CPU sin WSL ni
15
+ Python. Ver [la guía](https://github.com/evious/opencode-voice-dictation/blob/main/docs/whispercpp.md).
14
16
  - **Sin portapapeles**: la lectura usa `composer.insert()` en la posición del
15
17
  cursor, sin robar el foco ni simular `Ctrl+V`.
16
18
  - **Lectura natural**: voces neuronales de Edge (45 en español), con respaldo
17
19
  a las voces del sistema si no hay internet.
18
20
  - **WhisperX local (avanzado)**: dictado con WhisperX en WSL y GPU NVIDIA. Ver
19
21
  [la guía](https://github.com/evious/opencode-voice-dictation/blob/main/docs/whisperx.md).
20
- - **Solo Windows**: la app declara `os: ["windows"]`; en otros sistemas aparece
21
- como no disponible en vez de fallar.
22
+ - **Windows y Linux**: la app declara `os: ["windows", "linux"]` y muestra en
23
+ cada sistema solo los motores que existen.
22
24
 
23
25
  ## Instalación rápida
24
26
 
@@ -44,8 +46,9 @@ opencode plugin add github:evious/opencode-voice-dictation
44
46
 
45
47
  ## Compatibilidad
46
48
 
47
- Probado con OpenCode Desktop **2.2.6** en Windows 11. La API de plugins GUI es
48
- 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.
49
52
 
50
53
  ## Cómo funciona
51
54
 
@@ -71,8 +74,14 @@ píldora enfoca el composer, el proceso `main` envía `Win+H` con `keybd_event`,
71
74
  Windows escribe directamente en el campo. El plugin no transcribe ni toca el
72
75
  texto.
73
76
 
77
+ En Linux el flujo es distinto: la ventana graba con `getUserMedia` +
78
+ `MediaRecorder`, envía el audio por RPC y el servidor lo pasa a `ffmpeg`
79
+ (16 kHz WAV) y a un `whisper-server` local que mantiene el modelo cargado.
80
+
74
81
  ## Requisitos
75
82
 
83
+ **Windows**
84
+
76
85
  - OpenCode Desktop en **Windows 10/11**.
77
86
  - Node.js 22 o superior para compilar (solo en desarrollo).
78
87
  - Un **paquete de idioma de reconocimiento de voz** instalado:
@@ -80,6 +89,13 @@ texto.
80
89
  Reconocimiento de voz. No basta con el paquete de visualización.
81
90
  - Micrófono predeterminado configurado.
82
91
 
92
+ **Linux (Ubuntu)**
93
+
94
+ - OpenCode Desktop y `ffmpeg` instalados.
95
+ - `glibc` compatible con el binario oficial de `whisper.cpp` (Ubuntu 22.04+).
96
+ - Micrófono accesible por la app. El primer dictado descarga el binario
97
+ (~10 MB) y el modelo (`base-q5_1`, 57 MB) desde Ajustes.
98
+
83
99
  Comprobación rápida en PowerShell:
84
100
 
85
101
  ```powershell
@@ -157,25 +173,27 @@ espacio, se añade uno de separación (configurable).
157
173
 
158
174
  | Motor | Requisitos | Estado |
159
175
  |---|---|---|
160
- | Windows Voice Typing (Win+H) | Windows 11, internet | Por defecto |
176
+ | Windows Voice Typing (Win+H) | Windows 11, internet | Por defecto en Windows |
161
177
  | System.Speech | Windows, offline | Disponible |
162
178
  | WhisperX local (WSL) | WSL2 + Python + CUDA + NVIDIA | Avanzado · [guía](https://github.com/evious/opencode-voice-dictation/blob/main/docs/whisperx.md) |
163
- | `whisper.cpp` nativo | Binario + modelo | Roadmap |
179
+ | `whisper.cpp` nativo | Linux, CPU, descarga única | Por defecto en Linux · [guía](https://github.com/evious/opencode-voice-dictation/blob/main/docs/whispercpp.md) |
164
180
  | Google Cloud Speech-to-Text | API key, internet | Roadmap |
165
181
  | OpenAI (`gpt-4o-transcribe`) | API key, internet | Roadmap |
166
182
  | Deepgram / AssemblyAI | API key, internet | Roadmap |
167
183
 
168
- El motor se elige en **Ajustes → Dictado por voz → Motor de dictado**.
184
+ El motor se elige en **Ajustes → Dictado por voz → Motor de dictado**. En Linux
185
+ solo aparece `whisper.cpp`; en Windows aparecen Win+H, System.Speech y WhisperX.
169
186
 
170
- WhisperX es el único que requiere instalación manual: WSL2, un venv de Python
171
- con `whisperx`/`torch` CUDA y una GPU NVIDIA. El resto funciona sin setup local.
187
+ WhisperX y whisper.cpp son los únicos que requieren instalación: WhisperX pide
188
+ WSL2, un venv de Python con CUDA y una GPU NVIDIA; whisper.cpp se descarga desde
189
+ los propios ajustes. Los motores en la nube del roadmap solo pedirán una API key.
172
190
 
173
191
  ## Ajustes
174
192
 
175
193
  | Ajuste | Por defecto | Qué hace |
176
194
  |---|---|---|
177
- | Motor de dictado | Windows Voice Typing (Win+H) | Win+H, `System.Speech` offline o `WhisperX local (WSL)`. |
178
- | Probar Win+H | — | Envía el atajo y comprueba que Windows abre su barra de dictado. |
195
+ | Motor de dictado | Según el sistema | En Windows: Win+H, `System.Speech` o `WhisperX local (WSL)`. En Linux: `whisper.cpp`. |
196
+ | Probar Win+H | — | Solo Windows. Envía el atajo y comprueba que Windows abre su barra de dictado. |
179
197
  | Idioma | `es-ES` | Solo para `System.Speech`. Reconocedor instalado en Windows. Si no existe el exacto, usa el mismo idioma base. |
180
198
  | Confianza mínima | 0,4 | Solo para `System.Speech`. Descarta resultados con menor confianza. |
181
199
  | Espacio antes del texto | Sí | Solo para `System.Speech`. Separa el dictado de lo que ya hay escrito. |
@@ -185,6 +203,10 @@ con `whisperx`/`torch` CUDA y una GPU NVIDIA. El resto funciona sin setup local.
185
203
  | Modelo (WhisperX) | `large-v3-turbo` | Solo WhisperX. `large-v3` o `medium` si necesitas otra cosa. |
186
204
  | Idioma (WhisperX) | Automático | Solo WhisperX. Autodetección o `es`, `en`, `pt`. |
187
205
  | Comprobar WhisperX | — | Arranca el worker y valida GPU/modelo. |
206
+ | Modelo (whisper.cpp) | `base-q5_1` | Solo Linux. `base-q5_1` (57 MB) o `small-q5_1` (180 MB, más preciso). |
207
+ | Idioma (whisper.cpp) | Automático | Solo Linux. Autodetección o `es`, `en`, `pt`. |
208
+ | Descargar motor | — | Solo Linux. Baja el binario oficial y el modelo a `~/.local/share/opencode-voice-dictation`. |
209
+ | Comprobar whisper.cpp | — | Solo Linux. Indica si el servidor y el modelo están instalados. |
188
210
 
189
211
  ### Lectura en voz alta
190
212
 
@@ -214,10 +236,13 @@ El plugin funciona en local salvo lo que elijas y use red:
214
236
 
215
237
  - **WhisperX**: escribe el audio en `%TEMP%\opencode-voice-dictation`, lo pasa a
216
238
  WSL y ejecuta el worker con tu venv. El audio no sale del equipo.
239
+ - **whisper.cpp**: escribe el audio en `%TEMP%`, lo convierte con `ffmpeg` y lo
240
+ envía a un servidor local en `127.0.0.1`. El audio no sale del equipo. La
241
+ primera vez descarga el binario y el modelo desde GitHub/Hugging Face.
217
242
  - **Edge**: envía el texto a Microsoft para sintetizar la voz (online).
218
243
  - **Futuros motores en la nube**: enviarán el audio al proveedor que configures.
219
244
 
220
- Para el dictado/lectura con Windows:
245
+ En **Windows**, para el dictado/lectura:
221
246
 
222
247
  - Escribe un script `dictation-<hash>.ps1` o `speak-<hash>.ps1` en
223
248
  `%TEMP%\opencode-voice-dictation`.
@@ -228,6 +253,10 @@ Por eso Windows y antivirus como Kaspersky pueden pedir permiso la primera vez:
228
253
  es esperado. Elige **Permitir ahora** y marca **Recordar mi elección**. Si
229
254
  bloqueas el micrófono, verás el aviso «No hay ningún micrófono disponible».
230
255
 
256
+ En **Linux**, la app abre el micrófono con `getUserMedia`; el navegador/Electron
257
+ puede pedir permiso la primera vez. El servidor solo ejecuta `ffmpeg` y el
258
+ binario descargado.
259
+
231
260
  ## Solución de problemas
232
261
 
233
262
  | Síntoma | Causa / solución |
@@ -239,12 +268,15 @@ bloqueas el micrófono, verás el aviso «No hay ningún micrófono disponible»
239
268
  | «Windows no tiene ningún reconocedor de voz instalado» | Mismo caso: no hay ningún idioma de voz instalado. |
240
269
  | «No hay ningún micrófono disponible» | Revisa Configuración → Sistema → Sonido → Entrada y el micrófono predeterminado. |
241
270
  | El motor se detiene inesperadamente | Otro proceso puede estar usando el micrófono en modo exclusivo. Ejecuta la prueba de micrófono en Ajustes. |
242
- | La píldora no aparece | Comprueba que el plugin está en el `opencode.json` global, que la app corre en Windows y que el plugin figura como cargado en Ajustes → Extensiones → Plugins. |
243
- | No transcribe nada | Sube el nivel de entrada del micrófono y baja la confianza mínima a 0,2. |
244
- | «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. |
245
274
  | La lectura se corta o no suena | Prueba «Probar voz» en Ajustes; si suena ahí, el problema es el dispositivo de salida de la app. |
246
275
  | «Abre una conversación para leer su último mensaje» | En Home o en un chat nuevo sin mensajes no hay respuesta que leer. |
247
276
  | WhisperX se queda en «Transcribiendo…» | El worker no arrancó. Mira el toast: ruta del Python, CUDA o token de Hugging Face. Guía: <https://github.com/evious/opencode-voice-dictation/blob/main/docs/whisperx.md>. |
277
+ | whisper.cpp dice «not installed» | Entra en Ajustes → Dictado por voz → **Descargar motor**. Requiere internet la primera vez. |
278
+ | whisper.cpp falla al arrancar | Comprueba que `ffmpeg` está en el `PATH` (`ffmpeg -version`) y que el binario es compatible con tu `glibc` (Ubuntu 22.04+). |
279
+ | whisper.cpp tarda mucho | Usa `base-q5_1` en portátiles modestos; `small-q5_1` necesita más CPU y memoria. La primera transcripción carga el modelo. |
248
280
  | El RPC devuelve 400 o no cargan las voces de Edge | El plugin server no cargó: Ajustes → Extensiones → Plugins → **Reload**, o reinicia el servicio. |
249
281
  | WhisperX pide token de Hugging Face | `huggingface-cli login` y acepta los términos del modelo de VAD de `pyannote`. |
250
282
 
@@ -261,14 +293,22 @@ de la app muestran los más importantes.
261
293
  portable: es un motor avanzado para equipos ya preparados.
262
294
  - El primer dictado con WhisperX tarda 10–30 s porque carga el modelo en la GPU;
263
295
  los siguientes son rápidos.
296
+ - **whisper.cpp** usa la CPU. En un portátil muy modesto `base-q5_1` va en tiempo
297
+ real o algo más lento; `small-q5_1` mejora la precisión a cambio de CPU y
298
+ memoria. No hay GPU por ahora.
299
+ - El binario oficial de `whisper.cpp` es para Ubuntu (glibc reciente). En
300
+ distros antiguas o arquitecturas raras habría que compilarlo a mano.
301
+ - El primer uso descarga ~10 MB de binario y 57–180 MB de modelo. Después
302
+ funciona sin internet.
264
303
  - El modo Win+H envía la combinación con `keybd_event`. Kaspersky o la
265
304
  protección UIPI de Windows pueden bloquearla; en ese caso usa `System.Speech`.
266
305
  - Con Win+H el texto lo escribe Windows en el campo enfocado: el plugin no
267
306
  puede descartarlo si cambias de sesión ni limpiarlo.
268
- - No hay resultados parciales con `System.Speech`: el texto aparece al terminar
269
- cada frase.
270
- - La lectura usa las voces del sistema; si Electron no las expone, el respaldo
271
- 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).
272
312
  - La lectura empieza con el mensaje tal como esté en ese momento: si el agente
273
313
  sigue escribiendo, leerá la versión parcial.
274
314
  - Una sola dictación a la vez (hay un micrófono). Si empiezas en otra ventana,
@@ -277,8 +317,8 @@ de la app muestran los más importantes.
277
317
  para no escribirlo en la sesión equivocada.
278
318
 
279
319
  El dictado vive en `src/gui` (Win+H y System.Speech) y en el servidor
280
- (`src/whisper` para WhisperX y `src/edge.ts` para la lectura). Para añadir un
281
- motor nuevo, consulta la sección siguiente.
320
+ (`src/whisper` para WhisperX, `src/whispercpp` para Linux y `src/edge.ts` para la
321
+ lectura). Para añadir un motor nuevo, consulta la sección siguiente.
282
322
 
283
323
  ## Añadir un motor de dictado nuevo
284
324
 
@@ -289,9 +329,11 @@ RPC al servidor. Eso es reutilizable para cualquier motor: local o en la nube.
289
329
  `transcribe({ audio, mime, language, ... })` o uno por motor.
290
330
  2. **Servidor** (`src/index.ts`): implementa el método. Para la nube, llama al
291
331
  proveedor desde el servidor, nunca desde la ventana.
292
- 3. **Ajustes** (`src/gui/settings.tsx`): añade el motor al selector y sus campos
332
+ 3. **Capacidades** (`src/index.ts`): añade el motor a la lista que devuelve
333
+ `capabilities` para que la ventana lo ofrezca solo donde exista.
334
+ 4. **Ajustes** (`src/gui/settings.tsx`): añade el motor al selector y sus campos
293
335
  (API key, idioma, modelo).
294
- 4. **Ventana** (`src/gui/window.tsx`): enruta el modo del motor a la grabación y
336
+ 5. **Ventana** (`src/gui/window.tsx`): enruta el modo del motor a la grabación y
295
337
  al RPC; inserta con `composer.insert()` o copia con `ctx.system.copy()` si
296
338
  quieres dictar hacia otra app.
297
339
 
@@ -302,8 +344,9 @@ Proveedores previstos:
302
344
  - **OpenAI**: `gpt-4o-transcribe` o `whisper-1`,
303
345
  `POST /v1/audio/transcriptions`, API key.
304
346
  - **Deepgram / AssemblyAI**: `POST /v1/listen`, API key.
305
- - **`whisper.cpp` nativo**: binario + modelo `ggml`, sin WSL ni Python; la
306
- opción más portable para uso local.
347
+
348
+ `whisper.cpp` ya está implementado en `src/whispercpp/server.ts` como referencia
349
+ de un motor local: descarga, proceso persistente, conversión de audio y RPC.
307
350
 
308
351
  Las claves de API no deben viajar al renderer. Guárdalas en el store del plugin
309
352
  o, mejor, en variables de entorno del servidor.
@@ -312,18 +355,20 @@ o, mejor, en variables de entorno del servidor.
312
355
 
313
356
  ```
314
357
  src/
315
- ├── index.ts Parte servidor: registra los RPC de Edge y WhisperX
358
+ ├── index.ts Parte servidor: registra los RPC y capabilities
316
359
  ├── rpc.ts Contrato RPC compartido (voces, síntesis, dictado)
317
360
  ├── edge.ts Voces y síntesis de Edge (servidor)
318
361
  ├── whisper/
319
362
  │ ├── server.ts Worker persistente, spawn WSL/Node, timeouts
320
363
  │ └── worker.py WhisperX en la GPU; protocolo JSON por stdin/stdout
364
+ ├── whispercpp/
365
+ │ └── server.ts Linux: descarga, whisper-server persistente, ffmpeg
321
366
  ├── gui.tsx Entrada ./gui-experimental
322
367
  └── gui/
323
368
  ├── definition.ts os, stores, i18n es/en
324
369
  ├── contract.ts Ipc: dictado y lectura
325
370
  ├── window.tsx Píldoras, comandos e inserción de texto
326
- ├── recorder.ts getUserMedia + MediaRecorder para WhisperX
371
+ ├── recorder.ts getUserMedia + MediaRecorder para WhisperX/whisper.cpp
327
372
  ├── main.ts Dictado Win+H / System.Speech y voz SAPI
328
373
  ├── speak.ts Limpieza de Markdown, troceado, Edge y speechSynthesis
329
374
  ├── settings.tsx Página de ajustes
@@ -334,13 +379,16 @@ src/
334
379
  └── sapi-speech.ts Voz de respaldo y listado de voces SAPI
335
380
  ```
336
381
 
337
- La guía de WhisperX está en [`docs/whisperx.md`](docs/whisperx.md).
382
+ Guías:
383
+
384
+ - WhisperX (Windows + WSL): [`docs/whisperx.md`](docs/whisperx.md).
385
+ - whisper.cpp (Linux nativo): [`docs/whispercpp.md`](docs/whispercpp.md).
338
386
 
339
387
  `npm run typecheck` valida los tipos, `npm run build` genera el bundle CJS y
340
388
  `npm test` ejecuta el smoke test (`test/smoke.cjs`): simula el host de la app,
341
- un `powershell.exe` falso, la API de voz del navegador, el RPC de Edge y la
342
- grabadora de WhisperX para comprobar dictado, lectura, limpieza de Markdown,
343
- pausa, cancelación, fallback y errores.
389
+ un `powershell.exe` falso, la API de voz del navegador, el RPC de Edge y las
390
+ grabadoras de WhisperX/whisper.cpp para comprobar dictado, lectura, limpieza de
391
+ Markdown, pausa, cancelación, fallback, cambio de motor por sistema y errores.
344
392
 
345
393
  ## Publicar
346
394
 
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const $=require("@opencode/plugin/gui"),c=require("effect"),K=require("node:child_process"),Q=require("node:crypto"),j=require("node:fs"),te=require("node:os"),M=require("node:path"),d=require("solid-js/web"),R=require("solid-js"),Ce=$.Plugin.define({os:["windows"],stores:{prefs:$.Store.global(c.Schema.Struct({locale:c.Schema.String,minConfidence:c.Schema.Number,trailingSpace:c.Schema.Boolean,autoFocus:c.Schema.Boolean}),{locale:"es-ES",minConfidence:.4,trailingSpace:!0,autoFocus:!0}),speech:$.Store.global(c.Schema.Struct({voiceURI:c.Schema.String,rate:c.Schema.Number,skipCode:c.Schema.Boolean}),{voiceURI:"",rate:1,skipCode:!0}),engine:$.Store.global(c.Schema.Struct({mode:c.Schema.String}),{mode:"windows"}),tts:$.Store.global(c.Schema.Struct({engine:c.Schema.String,voice:c.Schema.String}),{engine:"edge",voice:"es-ES-XimenaNeural"}),whisper:$.Store.global(c.Schema.Struct({python:c.Schema.String,model:c.Schema.String,language:c.Schema.String}),{python:"~/peopleby/voice_to_text/api_voice/demo/whisperx-env/bin/python",model:"large-v3-turbo",language:""})},i18n:{en:{"pill.idle":"Dictate","pill.starting":"Starting…","pill.listening":"Listening…","pill.dictating":"Dictating…","pill.error":"Retry dictation","pill.title":"Voice dictation","pill.winH":"Open Windows voice typing (Win+H)","pill.stop":"Stop dictation","command.toggle":"Toggle voice dictation","command.toggle.description":"Dictate into the prompt with the selected engine","command.configure":"Voice dictation settings","error.title":"Voice dictation","error.language":"The speech language {locale} is not installed. Available: {available}.","error.none":"Windows has no speech recognizer installed. Settings › Time & language › Language › Speech.","error.mic":"No microphone is available for speech recognition.","error.system":"System.Speech is not available in this Windows installation.","error.exit":"The dictation engine stopped unexpectedly.","error.unknown":"Dictation failed ({code}).","toast.discarded":"Dictation discarded: the active session changed.","settings.title":"Voice dictation","settings.description":"Offline dictation with the Windows speech recognizer. The text is inserted into the prompt; it is never sent automatically.","settings.section":"Dictation","settings.engine":"Dictation engine","settings.engine.description":"Windows uses Windows 11 voice typing (Win+H), which is more accurate. System.Speech works offline but is less precise.","engine.windows":"Windows Voice Typing (Win+H)","engine.system":"System.Speech (offline)","engine.whisper":"WhisperX local (WSL)","pill.recording":"Recording…","pill.transcribing":"Transcribing…","pill.whisper":"Record and transcribe with WhisperX","error.whisper":"WhisperX could not transcribe the audio.","whisper.section":"WhisperX (local)","whisper.python":"Environment Python","whisper.python.description":"Python of the venv that has whisperx installed.","whisper.model":"Model","whisper.model.description":"large-v3-turbo is the fastest with good accuracy on a GPU.","whisper.language":"Language","whisper.language.description":"Automatic detects the language of each recording.","whisper.language.auto":"Automatic","whisper.test":"Check WhisperX","whisper.test.description":"Starts the worker and reports whether the model loads.","whisper.test.run":"Check","whisper.test.ready":"WhisperX ready: {model} ({device})","whisper.test.error":"WhisperX is not ready: {error}","whisper.empty":"WhisperX returned no text.","settings.winh":"Test Win+H","settings.winh.description":"Sends the Windows voice typing shortcut. If the floating bar appears, it works.","settings.winh.run":"Test Win+H","settings.winh.sent":"Win+H sent. If the Windows dictation bar did not appear, check Settings › Accessibility › Voice typing.","error.winh":"Windows voice typing could not be triggered.","settings.language":"Language","settings.language.description":"A speech recognizer installed in Windows. Accents and punctuation depend on it.","settings.confidence":"Minimum confidence","settings.confidence.description":"Results below this threshold are ignored to avoid noise.","confidence.low":"Low (0.2)","confidence.normal":"Normal (0.4)","confidence.high":"High (0.6)","settings.space":"Add a space before the text","settings.space.description":"Separates the dictation from what is already in the prompt.","settings.focus":"Focus the prompt after dictating","settings.focus.description":"Moves the cursor to the prompt so you can review or send it.","settings.test.section":"Diagnostics","settings.test":"Test microphone","settings.test.description":"Checks installed recognizers and the default microphone.","settings.test.run":"Run test","settings.test.success":"Speech recognition ready: {locale}","settings.test.failed":"Not ready: {reason}","common.reason.no-recognizers":"no speech language installed","common.reason.no-microphone":"no microphone available","read.idle":"Read","read.speaking":"Reading…","read.paused":"Paused","read.pause":"Pause","read.resume":"Resume","read.restart":"Read again","read.stop":"Stop reading","read.title":"Read the last message aloud","command.read":"Read the last message aloud","command.read.description":"Speaks the last reply with a system voice","read.noSession":"Open a conversation to read its last message.","read.empty":"The last message has no text to read.","read.error":"The last message could not be read.","read.noVoices":"Windows has no voices installed for reading.","speech.section":"Read aloud","speech.engine":"Voice engine","speech.engine.description":"Edge neural sounds much more natural but needs internet. System uses the installed Windows voices, offline.","speech.engine.edge":"Edge neural (online)","speech.engine.system":"System (offline)","error.edge":"The Edge voice could not be used. Check the connection or switch to the system voice.","speech.edge.error":"Edge voices could not be loaded","speech.voice":"Voice","speech.voice.description":"The voice used for reading.","speech.voice.auto":"Automatic","speech.rate":"Speed","speech.rate.description":"How fast the text is spoken.","speech.rate.slow":"0.8×","speech.rate.normal":"1×","speech.rate.fast":"1.2×","speech.rate.faster":"1.5×","speech.skipCode":"Skip code blocks","speech.skipCode.description":"Read only the prose; code, paths and commands are left out.","speech.preview":"Test voice","speech.preview.description":"Speaks a short sample with the current voice and speed.","speech.preview.text":"Hello, this is a reading test."},es:{"pill.idle":"Dictar","pill.starting":"Iniciando…","pill.listening":"Escuchando…","pill.dictating":"Dictando…","pill.error":"Reintentar dictado","pill.title":"Dictado por voz","pill.winH":"Abrir el dictado de Windows (Win+H)","pill.stop":"Detener dictado","command.toggle":"Alternar dictado por voz","command.toggle.description":"Dicta en el prompt con el motor elegido","command.configure":"Ajustes de dictado por voz","error.title":"Dictado por voz","error.language":"El idioma de reconocimiento {locale} no está instalado. Disponibles: {available}.","error.none":"Windows no tiene ningún reconocedor de voz instalado. Configuración › Hora e idioma › Idioma › Voz.","error.mic":"No hay ningún micrófono disponible para el reconocimiento de voz.","error.system":"System.Speech no está disponible en esta instalación de Windows.","error.exit":"El motor de dictado se detuvo inesperadamente.","error.unknown":"El dictado falló ({code}).","toast.discarded":"Dictado descartado: la sesión activa cambió.","settings.title":"Dictado por voz","settings.description":"Dictado offline con el reconocedor de voz de Windows. El texto se inserta en el prompt; nunca se envía solo.","settings.section":"Dictado","settings.engine":"Motor de dictado","settings.engine.description":"Windows usa el dictado de Windows 11 (Win+H), más preciso. System.Speech funciona sin Windows 11 pero es menos exacto.","engine.windows":"Windows Voice Typing (Win+H)","engine.system":"System.Speech (offline)","engine.whisper":"WhisperX local (WSL)","pill.recording":"Grabando…","pill.transcribing":"Transcribiendo…","pill.whisper":"Grabar y transcribir con WhisperX","error.whisper":"WhisperX no pudo transcribir el audio.","whisper.section":"WhisperX (local)","whisper.python":"Python del entorno","whisper.python.description":"Python del venv que tiene whisperx instalado.","whisper.model":"Modelo","whisper.model.description":"large-v3-turbo es el más rápido con buena precisión en GPU.","whisper.language":"Idioma","whisper.language.description":"Automático detecta el idioma de cada grabación.","whisper.language.auto":"Automático","whisper.test":"Comprobar WhisperX","whisper.test.description":"Arranca el worker e indica si el modelo carga.","whisper.test.run":"Comprobar","whisper.test.ready":"WhisperX listo: {model} ({device})","whisper.test.error":"WhisperX no está listo: {error}","whisper.empty":"WhisperX no devolvió texto.","settings.winh":"Probar Win+H","settings.winh.description":"Envía el atajo del dictado de Windows. Si aparece la barra flotante, funciona.","settings.winh.run":"Probar Win+H","settings.winh.sent":"Win+H enviado. Si no apareció la barra, revisa Configuración › Accesibilidad › Dictado por voz.","settings.language":"Idioma","settings.language.description":"Un reconocedor instalado en Windows. Los acentos y la puntuación dependen de él.","settings.confidence":"Confianza mínima","settings.confidence.description":"Los resultados por debajo de este umbral se ignoran para evitar ruido.","confidence.low":"Baja (0,2)","confidence.normal":"Normal (0,4)","confidence.high":"Alta (0,6)","settings.space":"Añadir un espacio antes del texto","settings.space.description":"Separa el dictado de lo que ya hay en el prompt.","settings.focus":"Enfocar el prompt al terminar","settings.focus.description":"Mueve el cursor al prompt para revisar o enviar el texto.","settings.test.section":"Diagnóstico","settings.test":"Probar micrófono","settings.test.description":"Comprueba los reconocedores instalados y el micrófono predeterminado.","settings.test.run":"Ejecutar prueba","settings.test.success":"Reconocimiento de voz listo: {locale}","settings.test.failed":"No está listo: {reason}","common.reason.no-recognizers":"no hay ningún idioma de voz instalado","common.reason.no-microphone":"no hay micrófono disponible","read.idle":"Leer","read.speaking":"Leyendo…","read.paused":"Pausado","read.pause":"Pausar","read.resume":"Reanudar","read.restart":"Leer de nuevo","read.stop":"Detener lectura","read.title":"Leer en voz alta el último mensaje","command.read":"Leer en voz alta el último mensaje","command.read.description":"Reproduce la última respuesta con una voz del sistema","read.noSession":"Abre una conversación para leer su último mensaje.","read.empty":"El último mensaje no tiene texto para leer.","read.error":"No se pudo leer el último mensaje.","read.noVoices":"Windows no tiene voces instaladas para la lectura.","speech.section":"Lectura en voz alta","speech.engine":"Motor de voz","speech.engine.description":"Edge neuronal suena mucho más natural pero necesita internet. Sistema usa las voces instaladas de Windows, sin conexión.","speech.engine.edge":"Edge neuronal (online)","speech.engine.system":"Sistema (offline)","error.edge":"No se pudo usar la voz de Edge. Revisa la conexión o cambia a la voz del sistema.","speech.edge.error":"No se pudieron cargar las voces de Edge","speech.voice":"Voz","speech.voice.description":"La voz que se usa para leer.","speech.voice.auto":"Automática","speech.rate":"Velocidad","speech.rate.description":"A qué ritmo se lee el texto.","speech.rate.slow":"0,8×","speech.rate.normal":"1×","speech.rate.fast":"1,2×","speech.rate.faster":"1,5×","speech.skipCode":"Ignorar bloques de código","speech.skipCode.description":"Lee solo la prosa; omite código, rutas y comandos.","speech.preview":"Probar voz","speech.preview.description":"Lee una frase corta con la voz y velocidad actuales.","speech.preview.text":"Hola, esta es una prueba de lectura."}}}),ie=$.Ipc.define({id:"voice-dictation.dictation",state:c.Schema.Struct({status:c.Schema.Literals(["idle","starting","listening","error"]),error:c.Schema.String,locale:c.Schema.String}),methods:{start:{input:c.Schema.Struct({locale:c.Schema.String,minConfidence:c.Schema.Number})},stop:{},diagnose:{output:c.Schema.Struct({locales:c.Schema.Array(c.Schema.String),microphone:c.Schema.Boolean,selected:c.Schema.String,error:c.Schema.String})}},events:{final:c.Schema.Struct({text:c.Schema.String,confidence:c.Schema.Number}),error:c.Schema.Struct({code:c.Schema.String,detail:c.Schema.String,available:c.Schema.Array(c.Schema.String)})}}),le=$.Ipc.define({id:"voice-dictation.speech",methods:{speak:{input:c.Schema.Struct({text:c.Schema.String,rate:c.Schema.Number,voice:c.Schema.String})},stop:{},voices:{output:c.Schema.Array(c.Schema.String)}},events:{done:c.Schema.Struct({ok:c.Schema.Boolean})}}),ne=$.Ipc.define({id:"voice-dictation.voice-typing",methods:{toggle:{}}}),oe=String.raw`param(
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const E=require("@opencode/plugin/gui"),p=require("effect"),ee=require("node:child_process"),re=require("node:crypto"),M=require("node:fs"),ne=require("node:os"),G=require("node:path"),a=require("solid-js/web"),S=require("solid-js"),ke=E.Plugin.define({os:["windows","linux"],stores:{prefs:E.Store.global(p.Schema.Struct({locale:p.Schema.String,minConfidence:p.Schema.Number,trailingSpace:p.Schema.Boolean,autoFocus:p.Schema.Boolean}),{locale:"es-ES",minConfidence:.4,trailingSpace:!0,autoFocus:!0}),speech:E.Store.global(p.Schema.Struct({voiceURI:p.Schema.String,rate:p.Schema.Number,skipCode:p.Schema.Boolean}),{voiceURI:"",rate:1,skipCode:!0}),engine:E.Store.global(p.Schema.Struct({mode:p.Schema.String}),{mode:"windows"}),tts:E.Store.global(p.Schema.Struct({engine:p.Schema.String,voice:p.Schema.String}),{engine:"edge",voice:"es-ES-XimenaNeural"}),whisper:E.Store.global(p.Schema.Struct({python:p.Schema.String,model:p.Schema.String,language:p.Schema.String}),{python:"~/peopleby/voice_to_text/api_voice/demo/whisperx-env/bin/python",model:"large-v3-turbo",language:""}),whispercpp:E.Store.global(p.Schema.Struct({model:p.Schema.String,language:p.Schema.String}),{model:"base-q5_1",language:"auto"})},i18n:{en:{"pill.idle":"Dictate","pill.starting":"Starting…","pill.listening":"Listening…","pill.dictating":"Dictating…","pill.error":"Retry dictation","pill.title":"Voice dictation","pill.winH":"Open Windows voice typing (Win+H)","pill.stop":"Stop dictation","command.toggle":"Toggle voice dictation","command.toggle.description":"Dictate into the prompt with the selected engine","command.configure":"Voice dictation settings","error.title":"Voice dictation","error.language":"The speech language {locale} is not installed. Available: {available}.","error.none":"Windows has no speech recognizer installed. Settings › Time & language › Language › Speech.","error.mic":"No microphone is available for speech recognition.","error.system":"System.Speech is not available in this Windows installation.","error.exit":"The dictation engine stopped unexpectedly.","error.unknown":"Dictation failed ({code}).","toast.discarded":"Dictation discarded: the active session changed.","settings.title":"Voice dictation","settings.description":"Offline dictation on Windows and Linux. The text is inserted into the prompt; it is never sent automatically.","settings.section":"Dictation","settings.engine":"Dictation engine","settings.engine.description":"Windows offers Win+H and System.Speech; Linux uses whisper.cpp locally.","engine.windows":"Windows Voice Typing (Win+H)","engine.system":"System.Speech (offline)","engine.whisper":"WhisperX local (WSL)","engine.whispercpp":"whisper.cpp (local, native)","pill.recording":"Recording…","pill.transcribing":"Transcribing…","pill.whisper":"Record and transcribe with WhisperX","error.whisper":"WhisperX could not transcribe the audio.","whisper.section":"WhisperX (local)","whisper.python":"Environment Python","whisper.python.description":"Python of the venv that has whisperx installed.","whisper.model":"Model","whisper.model.description":"large-v3-turbo is the fastest with good accuracy on a GPU.","whisper.language":"Language","whisper.language.description":"Automatic detects the language of each recording.","whisper.language.auto":"Automatic","whisper.test":"Check WhisperX","whisper.test.description":"Starts the worker and reports whether the model loads.","whisper.test.run":"Check","whisper.test.ready":"WhisperX ready: {model} ({device})","whisper.test.error":"WhisperX is not ready: {error}","whisper.empty":"WhisperX returned no text.","pill.whispercpp":"Record and transcribe with whisper.cpp","error.whispercpp":"whisper.cpp could not transcribe the audio.","whispercpp.section":"whisper.cpp (local, native)","whispercpp.description":"Runs on the CPU without WSL or Python. The first time you need to download the server (~10 MB) and the model.","whispercpp.model":"Model","whispercpp.model.description":"base-q5_1 is the lightest model (57 MB) and works on modest laptops. small-q5_1 is more accurate but uses more CPU and memory.","whispercpp.language":"Language","whispercpp.language.description":"Automatic detects the language of each recording.","whispercpp.language.auto":"Automatic","whispercpp.install":"Download engine","whispercpp.install.description":"Downloads the official whisper.cpp server and the selected model to ~/.local/share/opencode-voice-dictation. Needs internet once.","whispercpp.install.run":"Download","whispercpp.install.busy":"Downloading…","whispercpp.install.ok":"whisper.cpp installed: {model}","whispercpp.install.error":"Could not install whisper.cpp: {error}","whispercpp.test":"Check whisper.cpp","whispercpp.test.description":"Reports whether the server and the selected model are installed.","whispercpp.test.run":"Check","whispercpp.test.ready":"whisper.cpp ready: {model}","whispercpp.test.missing":"whisper.cpp is not installed. Download the engine first.","whispercpp.test.error":"whisper.cpp is not ready: {error}","whispercpp.empty":"whisper.cpp returned no text.","whispercpp.linux":"whisper.cpp is only available on Linux. On Windows use Win+H, System.Speech or WhisperX.","settings.winh":"Test Win+H","settings.winh.description":"Sends the Windows voice typing shortcut. If the floating bar appears, it works.","settings.winh.run":"Test Win+H","settings.winh.sent":"Win+H sent. If the Windows dictation bar did not appear, check Settings › Accessibility › Voice typing.","error.winh":"Windows voice typing could not be triggered.","settings.language":"Language","settings.language.description":"A speech recognizer installed in Windows. Accents and punctuation depend on it.","settings.confidence":"Minimum confidence","settings.confidence.description":"Results below this threshold are ignored to avoid noise.","confidence.low":"Low (0.2)","confidence.normal":"Normal (0.4)","confidence.high":"High (0.6)","settings.space":"Add a space before the text","settings.space.description":"Separates the dictation from what is already in the prompt.","settings.focus":"Focus the prompt after dictating","settings.focus.description":"Moves the cursor to the prompt so you can review or send it.","settings.test.section":"Diagnostics","settings.test":"Test microphone","settings.test.description":"Checks installed recognizers and the default microphone.","settings.test.run":"Run test","settings.test.success":"Speech recognition ready: {locale}","settings.test.failed":"Not ready: {reason}","common.reason.no-recognizers":"no speech language installed","common.reason.no-microphone":"no microphone available","read.idle":"Read","read.speaking":"Reading…","read.paused":"Paused","read.pause":"Pause","read.resume":"Resume","read.restart":"Read again","read.stop":"Stop reading","read.title":"Read the last message aloud","command.read":"Read the last message aloud","command.read.description":"Speaks the last reply with a system voice","read.noSession":"Open a conversation to read its last message.","read.empty":"The last message has no text to read.","read.error":"The last message could not be read.","read.noVoices":"The system has no voices installed for reading.","speech.section":"Read aloud","speech.engine":"Voice engine","speech.engine.description":"Edge neural sounds much more natural but needs internet. System uses the installed Windows voices, offline.","speech.engine.edge":"Edge neural (online)","speech.engine.system":"System (offline)","error.edge":"The Edge voice could not be used. Check the connection or switch to the system voice.","speech.edge.error":"Edge voices could not be loaded","speech.voice":"Voice","speech.voice.description":"The voice used for reading.","speech.voice.auto":"Automatic","speech.rate":"Speed","speech.rate.description":"How fast the text is spoken.","speech.rate.slow":"0.8×","speech.rate.normal":"1×","speech.rate.fast":"1.2×","speech.rate.faster":"1.5×","speech.skipCode":"Skip code blocks","speech.skipCode.description":"Read only the prose; code, paths and commands are left out.","speech.preview":"Test voice","speech.preview.description":"Speaks a short sample with the current voice and speed.","speech.preview.text":"Hello, this is a reading test."},es:{"pill.idle":"Dictar","pill.starting":"Iniciando…","pill.listening":"Escuchando…","pill.dictating":"Dictando…","pill.error":"Reintentar dictado","pill.title":"Dictado por voz","pill.winH":"Abrir el dictado de Windows (Win+H)","pill.stop":"Detener dictado","command.toggle":"Alternar dictado por voz","command.toggle.description":"Dicta en el prompt con el motor elegido","command.configure":"Ajustes de dictado por voz","error.title":"Dictado por voz","error.language":"El idioma de reconocimiento {locale} no está instalado. Disponibles: {available}.","error.none":"Windows no tiene ningún reconocedor de voz instalado. Configuración › Hora e idioma › Idioma › Voz.","error.mic":"No hay ningún micrófono disponible para el reconocimiento de voz.","error.system":"System.Speech no está disponible en esta instalación de Windows.","error.exit":"El motor de dictado se detuvo inesperadamente.","error.unknown":"El dictado falló ({code}).","toast.discarded":"Dictado descartado: la sesión activa cambió.","settings.title":"Dictado por voz","settings.description":"Dictado offline en Windows y Linux. El texto se inserta en el prompt; nunca se envía solo.","settings.section":"Dictado","settings.engine":"Motor de dictado","settings.engine.description":"Windows ofrece Win+H y System.Speech; Linux usa whisper.cpp en local.","engine.windows":"Windows Voice Typing (Win+H)","engine.system":"System.Speech (offline)","engine.whisper":"WhisperX local (WSL)","engine.whispercpp":"whisper.cpp (local, nativo)","pill.recording":"Grabando…","pill.transcribing":"Transcribiendo…","pill.whisper":"Grabar y transcribir con WhisperX","error.whisper":"WhisperX no pudo transcribir el audio.","whisper.section":"WhisperX (local)","whisper.python":"Python del entorno","whisper.python.description":"Python del venv que tiene whisperx instalado.","whisper.model":"Modelo","whisper.model.description":"large-v3-turbo es el más rápido con buena precisión en GPU.","whisper.language":"Idioma","whisper.language.description":"Automático detecta el idioma de cada grabación.","whisper.language.auto":"Automático","whisper.test":"Comprobar WhisperX","whisper.test.description":"Arranca el worker e indica si el modelo carga.","whisper.test.run":"Comprobar","whisper.test.ready":"WhisperX listo: {model} ({device})","whisper.test.error":"WhisperX no está listo: {error}","whisper.empty":"WhisperX no devolvió texto.","pill.whispercpp":"Grabar y transcribir con whisper.cpp","error.whispercpp":"whisper.cpp no pudo transcribir el audio.","whispercpp.section":"whisper.cpp (local, nativo)","whispercpp.description":"Funciona en CPU sin WSL ni Python. La primera vez hay que descargar el servidor (~10 MB) y el modelo.","whispercpp.model":"Modelo","whispercpp.model.description":"base-q5_1 es el más ligero (57 MB) y va bien en portátiles modestos. small-q5_1 es más preciso pero consume más CPU y memoria.","whispercpp.language":"Idioma","whispercpp.language.description":"Automático detecta el idioma de cada grabación.","whispercpp.language.auto":"Automático","whispercpp.install":"Descargar motor","whispercpp.install.description":"Descarga el servidor oficial de whisper.cpp y el modelo elegido en ~/.local/share/opencode-voice-dictation. Solo necesita internet una vez.","whispercpp.install.run":"Descargar","whispercpp.install.busy":"Descargando…","whispercpp.install.ok":"whisper.cpp instalado: {model}","whispercpp.install.error":"No se pudo instalar whisper.cpp: {error}","whispercpp.test":"Comprobar whisper.cpp","whispercpp.test.description":"Indica si el servidor y el modelo elegido están instalados.","whispercpp.test.run":"Comprobar","whispercpp.test.ready":"whisper.cpp listo: {model}","whispercpp.test.missing":"whisper.cpp no está instalado. Descarga el motor primero.","whispercpp.test.error":"whisper.cpp no está listo: {error}","whispercpp.empty":"whisper.cpp no devolvió texto.","whispercpp.linux":"whisper.cpp solo está disponible en Linux. En Windows usa Win+H, System.Speech o WhisperX.","settings.winh":"Probar Win+H","settings.winh.description":"Envía el atajo del dictado de Windows. Si aparece la barra flotante, funciona.","settings.winh.run":"Probar Win+H","settings.winh.sent":"Win+H enviado. Si no apareció la barra, revisa Configuración › Accesibilidad › Dictado por voz.","settings.language":"Idioma","settings.language.description":"Un reconocedor instalado en Windows. Los acentos y la puntuación dependen de él.","settings.confidence":"Confianza mínima","settings.confidence.description":"Los resultados por debajo de este umbral se ignoran para evitar ruido.","confidence.low":"Baja (0,2)","confidence.normal":"Normal (0,4)","confidence.high":"Alta (0,6)","settings.space":"Añadir un espacio antes del texto","settings.space.description":"Separa el dictado de lo que ya hay en el prompt.","settings.focus":"Enfocar el prompt al terminar","settings.focus.description":"Mueve el cursor al prompt para revisar o enviar el texto.","settings.test.section":"Diagnóstico","settings.test":"Probar micrófono","settings.test.description":"Comprueba los reconocedores instalados y el micrófono predeterminado.","settings.test.run":"Ejecutar prueba","settings.test.success":"Reconocimiento de voz listo: {locale}","settings.test.failed":"No está listo: {reason}","common.reason.no-recognizers":"no hay ningún idioma de voz instalado","common.reason.no-microphone":"no hay micrófono disponible","read.idle":"Leer","read.speaking":"Leyendo…","read.paused":"Pausado","read.pause":"Pausar","read.resume":"Reanudar","read.restart":"Leer de nuevo","read.stop":"Detener lectura","read.title":"Leer en voz alta el último mensaje","command.read":"Leer en voz alta el último mensaje","command.read.description":"Reproduce la última respuesta con una voz del sistema","read.noSession":"Abre una conversación para leer su último mensaje.","read.empty":"El último mensaje no tiene texto para leer.","read.error":"No se pudo leer el último mensaje.","read.noVoices":"El sistema no tiene voces instaladas para la lectura.","speech.section":"Lectura en voz alta","speech.engine":"Motor de voz","speech.engine.description":"Edge neuronal suena mucho más natural pero necesita internet. Sistema usa las voces instaladas del equipo, sin conexión.","speech.engine.edge":"Edge neuronal (online)","speech.engine.system":"Sistema (offline)","error.edge":"No se pudo usar la voz de Edge. Revisa la conexión o cambia a la voz del sistema.","speech.edge.error":"No se pudieron cargar las voces de Edge","speech.voice":"Voz","speech.voice.description":"La voz que se usa para leer.","speech.voice.auto":"Automática","speech.rate":"Velocidad","speech.rate.description":"A qué ritmo se lee el texto.","speech.rate.slow":"0,8×","speech.rate.normal":"1×","speech.rate.fast":"1,2×","speech.rate.faster":"1,5×","speech.skipCode":"Ignorar bloques de código","speech.skipCode.description":"Lee solo la prosa; omite código, rutas y comandos.","speech.preview":"Probar voz","speech.preview.description":"Lee una frase corta con la voz y velocidad actuales.","speech.preview.text":"Hola, esta es una prueba de lectura."}}}),oe=E.Ipc.define({id:"voice-dictation.dictation",state:p.Schema.Struct({status:p.Schema.Literals(["idle","starting","listening","error"]),error:p.Schema.String,locale:p.Schema.String}),methods:{start:{input:p.Schema.Struct({locale:p.Schema.String,minConfidence:p.Schema.Number})},stop:{},diagnose:{output:p.Schema.Struct({locales:p.Schema.Array(p.Schema.String),microphone:p.Schema.Boolean,selected:p.Schema.String,error:p.Schema.String})}},events:{final:p.Schema.Struct({text:p.Schema.String,confidence:p.Schema.Number}),error:p.Schema.Struct({code:p.Schema.String,detail:p.Schema.String,available:p.Schema.Array(p.Schema.String)})}}),he=E.Ipc.define({id:"voice-dictation.speech",methods:{speak:{input:p.Schema.Struct({text:p.Schema.String,rate:p.Schema.Number,voice:p.Schema.String})},stop:{},voices:{output:p.Schema.Array(p.Schema.String)}},events:{done:p.Schema.Struct({ok:p.Schema.Boolean})}}),se=E.Ipc.define({id:"voice-dictation.voice-typing",methods:{toggle:{}}}),pe=String.raw`param(
2
2
  [string]$Culture = "es-ES",
3
3
  [double]$MinConfidence = 0.4,
4
4
  [switch]$Diagnose
@@ -105,9 +105,9 @@ while ($true) {
105
105
  Start-Sleep -Milliseconds 250
106
106
  }
107
107
  }
108
- `,$e="powershell.exe";function 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;