opencode-voice-dictation 0.4.1 → 0.5.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.
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const gui$1=require("@opencode/plugin/gui"),effect=require("effect"),node_child_process=require("node:child_process"),node_crypto=require("node:crypto"),node_fs=require("node:fs"),node_os=require("node:os"),node_path=require("node:path"),web=require("solid-js/web"),solidJs=require("solid-js"),definition=gui$1.Plugin.define({os:["windows"],stores:{prefs:gui$1.Store.global(effect.Schema.Struct({locale:effect.Schema.String,minConfidence:effect.Schema.Number,trailingSpace:effect.Schema.Boolean,autoFocus:effect.Schema.Boolean}),{locale:"es-ES",minConfidence:.4,trailingSpace:!0,autoFocus:!0}),speech:gui$1.Store.global(effect.Schema.Struct({voiceURI:effect.Schema.String,rate:effect.Schema.Number,skipCode:effect.Schema.Boolean}),{voiceURI:"",rate:1,skipCode:!0}),engine:gui$1.Store.global(effect.Schema.Struct({mode:effect.Schema.String}),{mode:"windows"}),tts:gui$1.Store.global(effect.Schema.Struct({engine:effect.Schema.String,voice:effect.Schema.String}),{engine:"edge",voice:"es-ES-XimenaNeural"})},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)","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. Falling back to the system voice.","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)","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. Se usará la voz del sistema.","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."}}}),Dictation=gui$1.Ipc.define({id:"voice-dictation.dictation",state:effect.Schema.Struct({status:effect.Schema.Literals(["idle","starting","listening","error"]),error:effect.Schema.String,locale:effect.Schema.String}),methods:{start:{input:effect.Schema.Struct({locale:effect.Schema.String,minConfidence:effect.Schema.Number})},stop:{},diagnose:{output:effect.Schema.Struct({locales:effect.Schema.Array(effect.Schema.String),microphone:effect.Schema.Boolean,selected:effect.Schema.String,error:effect.Schema.String})}},events:{final:effect.Schema.Struct({text:effect.Schema.String,confidence:effect.Schema.Number}),error:effect.Schema.Struct({code:effect.Schema.String,detail:effect.Schema.String,available:effect.Schema.Array(effect.Schema.String)})}}),Speech=gui$1.Ipc.define({id:"voice-dictation.speech",methods:{speak:{input:effect.Schema.Struct({text:effect.Schema.String,rate:effect.Schema.Number,voice:effect.Schema.String})},stop:{},voices:{output:effect.Schema.Array(effect.Schema.String)},edgeVoices:{output:effect.Schema.Array(effect.Schema.Struct({id:effect.Schema.String,label:effect.Schema.String,locale:effect.Schema.String,gender:effect.Schema.String}))},edgeSpeak:{input:effect.Schema.Struct({text:effect.Schema.String,voice:effect.Schema.String,rate:effect.Schema.Number}),output:effect.Schema.String}},events:{done:effect.Schema.Struct({ok:effect.Schema.Boolean})}}),VoiceTyping=gui$1.Ipc.define({id:"voice-dictation.voice-typing",methods:{toggle:{}}}),DICTATION_SCRIPT=String.raw`param(
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const k=require("@opencode/plugin/gui"),c=require("effect"),B=require("node:child_process"),q=require("node:crypto"),z=require("node:fs"),Z=require("node:os"),F=require("node:path"),u=require("solid-js/web"),I=require("solid-js"),$e=k.Plugin.define({os:["windows"],stores:{prefs:k.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:k.Store.global(c.Schema.Struct({voiceURI:c.Schema.String,rate:c.Schema.Number,skipCode:c.Schema.Boolean}),{voiceURI:"",rate:1,skipCode:!0}),engine:k.Store.global(c.Schema.Struct({mode:c.Schema.String}),{mode:"windows"}),tts:k.Store.global(c.Schema.Struct({engine:c.Schema.String,voice:c.Schema.String}),{engine:"edge",voice:"es-ES-XimenaNeural"})},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)","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)","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."}}}),Y=k.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)})}}),ce=k.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})}}),J=k.Ipc.define({id:"voice-dictation.voice-typing",methods:{toggle:{}}}),ie=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
- `,POWERSHELL$2="powershell.exe";function scriptFile$2(){const e=node_path.join(node_os.tmpdir(),"opencode-voice-dictation");node_fs.mkdirSync(e,{recursive:!0});const t=node_crypto.createHash("sha1").update(DICTATION_SCRIPT).digest("hex").slice(0,12),n=node_path.join(e,`dictation-${t}.ps1`);return node_fs.writeFileSync(n,DICTATION_SCRIPT,"utf8"),n}function spawnPowershell$2(e){return node_child_process.spawn(POWERSHELL$2,e,{windowsHide:!0,stdio:["ignore","pipe","pipe"]})}function killTree$1(e){const t=e.pid;if(t===void 0){e.kill();return}node_child_process.spawn("taskkill",["/PID",String(t),"/T","/F"],{windowsHide:!0,stdio:"ignore"}).on("error",()=>e.kill())}function startEngine(e,t){const n=spawnPowershell$2(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",scriptFile$2(),"-Culture",e.locale,"-MinConfidence",String(e.minConfidence)]);let s=!1,o="",r="";const d=a=>{switch(a.type){case"ready":t.onReady(a.locale??e.locale);break;case"final":a.text&&t.onFinal(a.text,a.confidence??0);break;case"error":t.onError(a.code??"UNKNOWN",a.detail??"",a.available??[]);break}};return n.stdout?.setEncoding("utf8"),n.stdout?.on("data",a=>{r+=a;let c=r.indexOf(`
109
- `);for(;c>=0;){const u=r.slice(0,c).replace(/\r$/,"").trim();if(r=r.slice(c+1),u)try{d(JSON.parse(u))}catch{}c=r.indexOf(`
110
- `)}}),n.stderr?.setEncoding("utf8"),n.stderr?.on("data",a=>{o=(o+a).slice(-2e3)}),n.on("error",a=>{s||t.onError("ENGINE_START_FAILED",a.message,[])}),n.on("close",a=>{s||t.onExit(a,o.trim().slice(-500))}),{stop(){s=!0,killTree$1(n)}}}function diagnose(){return new Promise((e,t)=>{const n=spawnPowershell$2(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",scriptFile$2(),"-Diagnose"]);let s="",o="",r=!1;const d=(c,u)=>{r||(r=!0,clearTimeout(a),c?t(c):e(u))},a=setTimeout(()=>{killTree$1(n),d(new Error("diagnose timed out"))},2e4);n.stdout?.setEncoding("utf8"),n.stdout?.on("data",c=>{s+=c}),n.stderr?.setEncoding("utf8"),n.stderr?.on("data",c=>{o+=c}),n.on("error",c=>d(c)),n.on("close",()=>{const c=s.split(/\r?\n/).map(u=>u.trim()).filter(u=>u.startsWith("{")).at(-1);if(!c){d(new Error(o.trim()||"no output from PowerShell"));return}try{const u=JSON.parse(c);d(void 0,{locales:u.locales??[],microphone:u.microphone===!0,selected:u.selected??"",error:u.error??""})}catch(u){d(u instanceof Error?u:new Error(String(u)))}})})}const SPEAK_SCRIPT=String.raw`param(
108
+ `,Ee="powershell.exe";function le(){const e=F.join(Z.tmpdir(),"opencode-voice-dictation");z.mkdirSync(e,{recursive:!0});const t=q.createHash("sha1").update(ie).digest("hex").slice(0,12),n=F.join(e,`dictation-${t}.ps1`);return z.writeFileSync(n,ie,"utf8"),n}function de(e){return B.spawn(Ee,e,{windowsHide:!0,stdio:["ignore","pipe","pipe"]})}function ue(e){const t=e.pid;if(t===void 0){e.kill();return}B.spawn("taskkill",["/PID",String(t),"/T","/F"],{windowsHide:!0,stdio:"ignore"}).on("error",()=>e.kill())}function Ce(e,t){const n=de(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",le(),"-Culture",e.locale,"-MinConfidence",String(e.minConfidence)]);let d=!1,o="",r="";const g=a=>{switch(a.type){case"ready":t.onReady(a.locale??e.locale);break;case"final":a.text&&t.onFinal(a.text,a.confidence??0);break;case"error":t.onError(a.code??"UNKNOWN",a.detail??"",a.available??[]);break}};return n.stdout?.setEncoding("utf8"),n.stdout?.on("data",a=>{r+=a;let l=r.indexOf(`
109
+ `);for(;l>=0;){const p=r.slice(0,l).replace(/\r$/,"").trim();if(r=r.slice(l+1),p)try{g(JSON.parse(p))}catch{}l=r.indexOf(`
110
+ `)}}),n.stderr?.setEncoding("utf8"),n.stderr?.on("data",a=>{o=(o+a).slice(-2e3)}),n.on("error",a=>{d||t.onError("ENGINE_START_FAILED",a.message,[])}),n.on("close",a=>{d||t.onExit(a,o.trim().slice(-500))}),{stop(){d=!0,ue(n)}}}function ke(){return new Promise((e,t)=>{const n=de(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",le(),"-Diagnose"]);let d="",o="",r=!1;const g=(l,p)=>{r||(r=!0,clearTimeout(a),l?t(l):e(p))},a=setTimeout(()=>{ue(n),g(new Error("diagnose timed out"))},2e4);n.stdout?.setEncoding("utf8"),n.stdout?.on("data",l=>{d+=l}),n.stderr?.setEncoding("utf8"),n.stderr?.on("data",l=>{o+=l}),n.on("error",l=>g(l)),n.on("close",()=>{const l=d.split(/\r?\n/).map(p=>p.trim()).filter(p=>p.startsWith("{")).at(-1);if(!l){g(new Error(o.trim()||"no output from PowerShell"));return}try{const p=JSON.parse(l);g(void 0,{locales:p.locales??[],microphone:p.microphone===!0,selected:p.selected??"",error:p.error??""})}catch(p){g(p instanceof Error?p:new Error(String(p)))}})})}const oe=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
- `,POWERSHELL$1="powershell.exe";function workDir(){const e=node_path.join(node_os.tmpdir(),"opencode-voice-dictation");return node_fs.mkdirSync(e,{recursive:!0}),e}function scriptFile$1(){const e=node_crypto.createHash("sha1").update(SPEAK_SCRIPT).digest("hex").slice(0,12),t=node_path.join(workDir(),`speak-${e}.ps1`);return node_fs.writeFileSync(t,SPEAK_SCRIPT,"utf8"),t}function textFile(e){const t=node_path.join(workDir(),`speech-${node_crypto.randomUUID()}.txt`);return node_fs.writeFileSync(t,e,"utf8"),t}function spawnPowershell$1(e){return node_child_process.spawn(POWERSHELL$1,e,{windowsHide:!0,stdio:["ignore","pipe","pipe"]})}function killTree(e){const t=e.pid;if(t===void 0){e.kill();return}node_child_process.spawn("taskkill",["/PID",String(t),"/T","/F"],{windowsHide:!0,stdio:"ignore"}).on("error",()=>e.kill())}function sapiRate(e){return Math.max(-10,Math.min(10,Math.round((e-1)*5)))}function speakSapi(e,t){const n=textFile(e.text),s=spawnPowershell$1(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",scriptFile$1(),"-TextFile",n,"-Rate",String(sapiRate(e.rate)),"-Voice",e.voice]);let o=!1;const r=()=>{try{node_fs.rmSync(n,{force:!0})}catch{}};return s.on("error",()=>{r(),o||t(!1)}),s.on("close",d=>{r(),o||t(d===0)}),{stop(){o=!0,killTree(s),r()}}}function listSapiVoices(){return new Promise(e=>{const t=spawnPowershell$1(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",scriptFile$1(),"-ListVoices"]);let n="",s=!1;const o=d=>{s||(s=!0,clearTimeout(r),e(d))},r=setTimeout(()=>{killTree(t),o([])},1e4);t.stdout?.setEncoding("utf8"),t.stdout?.on("data",d=>{n+=d}),t.on("error",()=>o([])),t.on("close",()=>{const d=n.split(/\r?\n/).map(a=>a.trim()).filter(a=>a.length>0);o(d)})})}const WIN_H_SCRIPT=String.raw`$ErrorActionPreference = "Stop"
163
+ `,Re="powershell.exe";function pe(){const e=F.join(Z.tmpdir(),"opencode-voice-dictation");return z.mkdirSync(e,{recursive:!0}),e}function ge(){const e=q.createHash("sha1").update(oe).digest("hex").slice(0,12),t=F.join(pe(),`speak-${e}.ps1`);return z.writeFileSync(t,oe,"utf8"),t}function Te(e){const t=F.join(pe(),`speech-${q.randomUUID()}.txt`);return z.writeFileSync(t,e,"utf8"),t}function he(e){return B.spawn(Re,e,{windowsHide:!0,stdio:["ignore","pipe","pipe"]})}function me(e){const t=e.pid;if(t===void 0){e.kill();return}B.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 Ne(e,t){const n=Te(e.text),d=he(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",ge(),"-TextFile",n,"-Rate",String(Ie(e.rate)),"-Voice",e.voice]);let o=!1;const r=()=>{try{z.rmSync(n,{force:!0})}catch{}};return d.on("error",()=>{r(),o||t(!1)}),d.on("close",g=>{r(),o||t(g===0)}),{stop(){o=!0,me(d),r()}}}function Ae(){return new Promise(e=>{const t=he(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",ge(),"-ListVoices"]);let n="",d=!1;const o=g=>{d||(d=!0,clearTimeout(r),e(g))},r=setTimeout(()=>{me(t),o([])},1e4);t.stdout?.setEncoding("utf8"),t.stdout?.on("data",g=>{n+=g}),t.on("error",()=>o([])),t.on("close",()=>{const g=n.split(/\r?\n/).map(a=>a.trim()).filter(a=>a.length>0);o(g)})})}const re=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
- `,POWERSHELL="powershell.exe";function scriptFile(){const e=node_path.join(node_os.tmpdir(),"opencode-voice-dictation");node_fs.mkdirSync(e,{recursive:!0});const t=node_crypto.createHash("sha1").update(WIN_H_SCRIPT).digest("hex").slice(0,12),n=node_path.join(e,`win-h-${t}.ps1`);return node_fs.writeFileSync(n,WIN_H_SCRIPT,"utf8"),n}function spawnPowershell(e){return node_child_process.spawn(POWERSHELL,e,{windowsHide:!0,stdio:"ignore"})}function sendVoiceTypingShortcut(){return new Promise((e,t)=>{const n=spawnPowershell(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",scriptFile()]);n.on("error",s=>t(s)),n.on("close",s=>{s===0?e():t(new Error(`Win+H helper exited with code ${s??"unknown"}`))})})}let cached;function load(){if(cached)return cached;const runtimeRequire=eval("require");return cached=runtimeRequire("msedge-tts"),cached}function shortName(e){const t=e.FriendlyName.replace(/^Microsoft\s+/i,"").replace(/\s+Online.*$/i,"").replace(/\s*-\s*Spanish.*$/i,"").trim();return t&&t!==e.ShortName?t:e.ShortName.replace(/^[a-z]{2}-[A-Z]{2}-/,"").replace(/Neural$/,"")}async function listEdgeVoices(){const{MsEdgeTTS:e}=load(),t=new e;try{return(await t.getVoices()).filter(s=>s.Locale.toLowerCase().startsWith("es-")).map(s=>({id:s.ShortName,label:`${s.Locale} · ${shortName(s)}`,locale:s.Locale,gender:s.Gender})).sort((s,o)=>s.label.localeCompare(o.label))}finally{t.close()}}async function synthesizeEdge(e){const{MsEdgeTTS:t,OUTPUT_FORMAT:n}=load(),s=new t;try{await s.setMetadata(e.voice,n.AUDIO_24KHZ_48KBITRATE_MONO_MP3);const{audioStream:o}=s.toStream(e.text,{rate:e.rate}),r=[];return await new Promise((d,a)=>{o.on("data",c=>r.push(Buffer.from(c))),o.on("end",()=>d(Buffer.concat(r).toString("base64"))),o.on("error",a)})}finally{s.close()}}const main=e=>{let t="idle",n="",s="",o,r=!1;const d=()=>{o=void 0,r=!1},a=e.provide(Dictation,{state:()=>({status:t,error:n,locale:s}),start(f,T){o&&(r=!0,o.stop(),d()),t="starting",n="",s=f.locale,a.changed();const m=T.window;try{o=startEngine({locale:f.locale,minConfidence:f.minConfidence},{onReady(w){t="listening",s=w,a.changed()},onFinal(w,v){a.emit("final",{text:w,confidence:v},m)},onError(w,v,$){t="error",n=w,a.changed(),a.emit("error",{code:w,detail:v,available:$},m)},onExit(w,v){if(r){d(),t="idle",n="",s="",a.changed();return}d(),t!=="error"&&(t="error",n=w===null?"ENGINE_EXIT":`ENGINE_EXIT_${w}`,a.changed(),a.emit("error",{code:"ENGINE_EXIT",detail:v||(w===null?"":String(w)),available:[]},m))}})}catch(w){throw t="error",n="ENGINE_START_FAILED",a.changed(),w}},stop(){o&&(r=!0,o.stop(),d()),t="idle",n="",s="",a.changed()},diagnose(){return diagnose()}});let c;const u=e.provide(Speech,{speak(f){c?.stop(),c=speakSapi(f,T=>{c=void 0,u.emit("done",{ok:T})})},stop(){c&&(c.stop(),c=void 0)},voices(){return listSapiVoices()},edgeVoices(){return listEdgeVoices()},edgeSpeak(f){return synthesizeEdge(f)}});e.provide(VoiceTyping,{toggle(){return sendVoiceTypingShortcut()}}),e.onCleanup(()=>{o&&(r=!0,o.stop(),d()),c?.stop(),c=void 0})},MAX_CHUNK=250;function cleanForSpeech(e,t){let n=e.replace(/\r\n/g,`
191
- `);return t?n=n.replace(/```[\s\S]*?```/g," "):n=n.replace(/```[^\n]*\n([\s\S]*?)```/g,"$1"),n=n.replace(/`([^`]+)`/g,"$1"),n=n.replace(/!\[[^\]]*\]\([^)]*\)/g," "),n=n.replace(/\[([^\]]+)\]\([^)]*\)/g,"$1"),n=n.replace(/^\s*#{1,6}\s+/gm,""),n=n.replace(/^\s*>\s?/gm,""),n=n.replace(/^\s*[-*+]\s+/gm,""),n=n.replace(/^\s*\d+\.\s+/gm,""),n=n.replace(/(\*\*|__)(.*?)\1/g,"$2"),n=n.replace(/(\*|_)(.*?)\1/g,"$2"),n=n.replace(/^\s*\|.*\|\s*$/gm," "),n=n.replace(/\n{2,}/g,". "),n=n.replace(/\n/g," "),n=n.replace(/\s+/g," "),n=n.replace(/\s+([.,;:!?])/g,"$1"),n=n.replace(/([.,;:!?]){2,}/g,"$1"),n.trim()}function chunkForSpeech(e,t=MAX_CHUNK){const n=e.trim();if(!n)return[];const s=n.split(new RegExp("(?<=[.!?…])\\s+")),o=[];let r="";const d=a=>{let c="";for(const u of a.split(/\s+/))u&&(c&&c.length+1+u.length>t?(o.push(c),c=u):c=c?`${c} ${u}`:u);return c};for(const a of s){if(a.length>t){r&&(o.push(r),r=""),r=d(a);continue}r&&r.length+1+a.length>t?(o.push(r),r=a):r=r?`${r} ${a}`:a}return r&&o.push(r),o}function createSpeaker(e,t){const n=e.stores.speech,s=e.stores.tts.value,[o,r]=solidJs.createSignal(s.engine==="edge"?"edge":"system"),[d,a]=solidJs.createSignal(s.voice),c=g=>{(g.engine==="edge"||g.engine==="system")&&r(g.engine),g.voice!==void 0&&a(g.voice)},[u,f]=solidJs.createSignal(!1),[T,m]=solidJs.createSignal(!1),[w,v]=solidJs.createSignal([]),[$,N]=solidJs.createSignal("none");let E=0,A=[],l="",p,b;const k=new Map,R=()=>typeof globalThis.speechSynthesis<"u"?globalThis.speechSynthesis:void 0,i=()=>{const g=R()?.getVoices()??[];if(g.length===0)return;const S=n.value.voiceURI;if(S){const C=g.find(L=>L.voiceURI===S);if(C)return C}const y=e.locale.locale().split("-")[0]?.toLowerCase()??"es";return g.find(C=>C.lang.toLowerCase().startsWith(y))??g[0]},h=async()=>{if(o()==="edge"){try{const S=await t.edgeVoices();S.length>0&&v(S.map(y=>({id:y.id,label:y.label})))}catch{}N("edge");return}const g=R()?.getVoices()??[];if(g.length>0){v(g.map(S=>({id:S.voiceURI,label:`${S.name} — ${S.lang}`}))),N("web");return}try{const S=await t.voices();if(S.length>0){v(S.map(y=>({id:y,label:y}))),N("sapi");return}}catch{}N("none")},P=()=>{p&&(p.onended=null,p.onerror=null,p.pause(),p=void 0),b&&(URL.revokeObjectURL(b),b=void 0)},V=()=>{E++,A=[],R()?.cancel(),P(),m(!1),f(!1),$()==="sapi"&&t.stop().catch(()=>{})},D=()=>$()==="web"||$()==="edge",U=()=>{if(!(!u()||T()||!D())){if($()==="edge"){p?.pause(),m(!0);return}R()?.pause(),m(!0)}},M=()=>{if(!(!u()||!T()||!D())){if($()==="edge"){p?.play().catch(()=>{}),m(!1);return}R()?.resume(),m(!1)}},j=()=>{T()?M():U()},G=()=>l?H(l):Promise.resolve(!1),q=g=>{const S=d()||"es-ES-XimenaNeural",y=n.value.rate,C=chunkForSpeech(g,4e3),L=`${S}|${y}|${g}`,O=k.get(L)??[];k.clear(),k.set(L,O);const I=E;f(!0),m(!1);const W=_=>{if(I===E){if(_>=C.length){m(!1),f(!1);return}(async()=>{let F=O[_];try{F||(F=await t.edgeSpeak({text:C[_]??"",voice:S,rate:y}),O[_]=F)}catch{I===E&&(m(!1),f(!1),e.toast.show({variant:"error",title:e.t("read.title"),description:e.t("error.edge")}));return}if(I!==E)return;const J=Uint8Array.from(atob(F),Z=>Z.charCodeAt(0)),B=URL.createObjectURL(new Blob([J],{type:"audio/mpeg"}));P(),b=B;const z=new Audio(B);p=z,z.onended=()=>{I===E&&(P(),W(_+1))},z.onerror=()=>{I===E&&(P(),m(!1),f(!1))},await z.play().catch(()=>{I===E&&(P(),m(!1),f(!1),e.toast.show({variant:"error",title:e.t("read.title"),description:e.t("error.edge")}))})})()}};return W(0),!0},H=async g=>{l=g;const S=cleanForSpeech(g,n.value.skipCode);if(!S)return!1;if(V(),await h(),o()==="edge")return q(S);if($()==="web"){const y=R();if(!y)return!1;const C=i(),L=n.value.rate,O=E;A=chunkForSpeech(S),f(!0),m(!1);const I=()=>{if(O!==E)return;const W=A.shift();if(!W){m(!1),f(!1);return}const _=new SpeechSynthesisUtterance(W);C&&(_.voice=C),C?.lang&&(_.lang=C.lang),_.rate=L,_.onend=I,_.onerror=I,y.speak(_)};return I(),!0}if($()==="sapi"){f(!0),m(!1);try{await t.speak({text:S,rate:n.value.rate,voice:n.value.voiceURI})}catch(y){throw f(!1),y}return!0}throw f(!1),new Error(e.t("read.noVoices"))},K=async()=>{const g=e.sessions.current();if(!g)return"";const y=(await g.server.client.message.list({sessionID:g.id,type:"assistant",order:"desc",limit:1})).data[0];return!y||y.type!=="assistant"?"":y.content.filter(C=>C.type==="text").map(C=>C.text).join(`
192
- `)};return t.on("done",()=>{m(!1),f(!1)}),R()?.addEventListener?.("voiceschanged",()=>{h()}),h(),{speaking:u,paused:T,backend:$,engine:o,voice:d,canPause:D,setConfig:c,voices:w,refreshVoices:h,lastAssistantText:K,speak:H,cancel:V,pause:U,resume:M,togglePause:j,restart:G,preview:async()=>{const g=await H(e.t("speech.preview.text"));if(g){const S=o()==="edge"?`Edge · ${d()}`:`${$()} · ${n.value.voiceURI||"auto"}`;e.toast.show({variant:"default",title:e.t("read.title"),description:S})}return g}}}const FALLBACK_LOCALES=["es-ES","es-MX","es-AR","es-CO","pt-BR","pt-PT","en-US","en-GB","fr-FR","de-DE","it-IT"];function VoiceSettings(e){const t=gui$1.usePlugin(),n=t.ui,s=t.stores.prefs,o=t.stores.speech,r=t.stores.tts,d=t.stores.engine,a=t.main(Dictation),c=t.main(VoiceTyping),[u,f]=solidJs.createSignal([]),[T,m]=solidJs.createSignal(!1);solidJs.onMount(()=>{e.speaker.setConfig({engine:r.value.engine,voice:r.value.voice}),a.diagnose().then(i=>{i.locales.length>0&&f(i.locales)}).catch(()=>{}),e.speaker.refreshVoices()});const w=()=>{const i=u(),h=s.value.locale;return(i.includes(h)?i:[h,...i]).map(V=>({value:V,label:V}))},v=()=>{const i=e.speaker.voices().map(h=>({value:h.id,label:h.label}));return r.value.engine==="edge"?i:[{value:"",label:t.t("speech.voice.auto")},...i]},$=()=>r.value.engine==="edge"?r.value.voice:o.value.voiceURI,N=i=>{if(r.value.engine==="edge"){r.update(h=>{h.voice=i}),e.speaker.setConfig({voice:i});return}A({voiceURI:i})},E=i=>{s.update(h=>{i.locale!==void 0&&(h.locale=i.locale),i.minConfidence!==void 0&&(h.minConfidence=i.minConfidence),i.trailingSpace!==void 0&&(h.trailingSpace=i.trailingSpace),i.autoFocus!==void 0&&(h.autoFocus=i.autoFocus)})},A=i=>{o.update(h=>{i.voiceURI!==void 0&&(h.voiceURI=i.voiceURI),i.rate!==void 0&&(h.rate=i.rate),i.skipCode!==void 0&&(h.skipCode=i.skipCode)})},l=i=>{d.update(h=>{h.mode=i})},p=i=>{r.update(h=>{h.engine=i}),e.speaker.setConfig({engine:i}),e.speaker.refreshVoices()},b=async()=>{try{await c.toggle(),t.toast.show({variant:"default",title:t.t("settings.winh"),description:t.t("settings.winh.sent")})}catch(i){t.toast.show({variant:"error",title:t.t("pill.title"),description:i instanceof Error?i.message:t.t("error.winh")})}},k=async()=>{try{await e.speaker.preview()}catch(i){t.toast.show({variant:"error",title:t.t("read.title"),description:i instanceof Error?i.message:t.t("read.error")})}},R=async()=>{m(!0);try{const i=await a.diagnose();if(i.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(!i.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}i.locales.length>0&&!u().length&&f(i.locales),t.toast.show({variant:"success",title:t.t("error.title"),description:t.t("settings.test.success",{locale:i.selected||i.locales[0]||s.value.locale})})}catch(i){t.toast.show({variant:"error",title:t.t("error.title"),description:i instanceof Error?i.message:String(i)})}finally{m(!1)}};return web.createComponent(n.SettingsLayout,{get title(){return t.t("settings.title")},get description(){return t.t("settings.description")},get children(){return[web.createComponent(n.SettingsSection,{get title(){return t.t("settings.section")},get children(){return[web.createComponent(n.SettingsRow,{id:"engine",get title(){return t.t("settings.engine")},get description(){return t.t("settings.engine.description")},get children(){return web.createComponent(n.Select,{get label(){return t.t("settings.engine")},get value(){return d.value.mode},get options(){return[{value:"windows",label:t.t("engine.windows")},{value:"system",label:t.t("engine.system")}]},onChange:i=>l(i)})}}),web.createComponent(solidJs.Show,{get when(){return d.value.mode==="windows"},get children(){return web.createComponent(n.SettingsRow,{id:"winh",get title(){return t.t("settings.winh")},get description(){return t.t("settings.winh.description")},get children(){return web.createComponent(n.Button,{onClick:()=>{b()},get children(){return t.t("settings.winh.run")}})}})}}),web.createComponent(solidJs.Show,{get when(){return d.value.mode==="system"},get children(){return[web.createComponent(n.SettingsRow,{id:"language",get title(){return t.t("settings.language")},get description(){return t.t("settings.language.description")},get children(){return web.createComponent(n.Select,{get label(){return t.t("settings.language")},get value(){return s.value.locale},get options(){return web.memo(()=>w().length>0)()?w():FALLBACK_LOCALES.map(i=>({value:i,label:i}))},onChange:i=>E({locale:i})})}}),web.createComponent(n.SettingsRow,{id:"confidence",get title(){return t.t("settings.confidence")},get description(){return t.t("settings.confidence.description")},get children(){return web.createComponent(n.Select,{get label(){return t.t("settings.confidence")},get value(){return String(s.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:i=>E({minConfidence:Number(i)||.4})})}}),web.createComponent(n.SettingsRow,{id:"space",get title(){return t.t("settings.space")},get description(){return t.t("settings.space.description")},get children(){return web.createComponent(n.Switch,{get label(){return t.t("settings.space")},hideLabel:!0,get checked(){return s.value.trailingSpace},onChange:i=>E({trailingSpace:i})})}}),web.createComponent(n.SettingsRow,{id:"focus",get title(){return t.t("settings.focus")},get description(){return t.t("settings.focus.description")},get children(){return web.createComponent(n.Switch,{get label(){return t.t("settings.focus")},hideLabel:!0,get checked(){return s.value.autoFocus},onChange:i=>E({autoFocus:i})})}})]}})]}}),web.createComponent(n.SettingsSection,{get title(){return t.t("speech.section")},get children(){return[web.createComponent(n.SettingsRow,{id:"tts-engine",get title(){return t.t("speech.engine")},get description(){return t.t("speech.engine.description")},get children(){return web.createComponent(n.Select,{get label(){return t.t("speech.engine")},get value(){return r.value.engine},get options(){return[{value:"edge",label:t.t("speech.engine.edge")},{value:"system",label:t.t("speech.engine.system")}]},onChange:i=>p(i)})}}),web.createComponent(n.SettingsRow,{id:"voice",get title(){return t.t("speech.voice")},get description(){return t.t("speech.voice.description")},get children(){return web.createComponent(n.Select,{get label(){return t.t("speech.voice")},get value(){return $()},get options(){return v()},onChange:i=>N(i)})}}),web.createComponent(n.SettingsRow,{id:"rate",get title(){return t.t("speech.rate")},get description(){return t.t("speech.rate.description")},get children(){return web.createComponent(n.Select,{get label(){return t.t("speech.rate")},get value(){return String(o.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:i=>A({rate:Number(i)||1})})}}),web.createComponent(n.SettingsRow,{id:"skip-code",get title(){return t.t("speech.skipCode")},get description(){return t.t("speech.skipCode.description")},get children(){return web.createComponent(n.Switch,{get label(){return t.t("speech.skipCode")},hideLabel:!0,get checked(){return o.value.skipCode},onChange:i=>A({skipCode:i})})}}),web.createComponent(n.SettingsRow,{id:"preview",get title(){return t.t("speech.preview")},get description(){return t.t("speech.preview.description")},get children(){return web.createComponent(n.Button,{onClick:()=>{k()},get children(){return t.t("read.idle")}})}})]}}),web.createComponent(n.SettingsSection,{get title(){return t.t("settings.test.section")},get children(){return web.createComponent(n.SettingsRow,{id:"test",get title(){return t.t("settings.test")},get description(){return t.t("settings.test.description")},get children(){return web.createComponent(n.Button,{get busy(){return T()},onClick:()=>{R()},get children(){return t.t("settings.test.run")}})}})}})]}})}function ReadMenu(e){const t=gui$1.usePlugin();return web.memo(()=>e.actions().map(n=>web.createComponent(t.ui.Button,{get variant(){return n.id==="stop"?"danger":"neutral"},get icon(){return n.icon},onClick:()=>{n.run(),e.close()},get children(){return n.label}})))}const setup=e=>{const t=e.main(Dictation),n=e.main(Speech),s=e.main(VoiceTyping),o=createSpeaker(e,n),r=e.stores.prefs,d=e.stores.engine,[a,c]=solidJs.createSignal(!1);let u;const f=(l,p,b)=>{switch(l){case"LANGUAGE_NOT_AVAILABLE":return e.t("error.language",{locale:p||r.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:l})}},T=()=>{a()&&(c(!1),s.toggle().catch(()=>{})),o.cancel(),u=e.sessions.current()?.key,t.start({locale:r.value.locale,minConfidence:r.value.minConfidence}).catch(l=>{e.toast.show({variant:"error",title:e.t("error.title"),description:l instanceof Error?l.message:String(l)})})},m=()=>{t.stop().catch(()=>{})},w=()=>{o.cancel(),e.screen.current()?.composer.focus(),s.toggle().then(()=>c(l=>!l)).catch(l=>{c(!1),e.toast.show({variant:"error",title:e.t("pill.title"),description:l instanceof Error?`${e.t("error.winh")} ${l.message}`:e.t("error.winh")})})},v=()=>{if(d.value.mode==="windows"){w();return}const l=t.state()?.status??"idle";l==="starting"||l==="listening"?m():T()},$=()=>{if(o.speaking()){o.cancel();return}if(!e.sessions.current()){e.toast.show({variant:"error",title:e.t("read.title"),description:e.t("read.noSession")});return}const l=t.state()?.status??"idle";(l==="starting"||l==="listening")&&t.stop().catch(()=>{}),(async()=>{try{const p=await o.lastAssistantText();if(!p.trim()){e.toast.show({variant:"default",title:e.t("read.title"),description:e.t("read.empty")});return}await o.speak(p)||e.toast.show({variant:"default",title:e.t("read.title"),description:e.t("read.empty")})}catch(p){e.toast.show({variant:"error",title:e.t("read.title"),description:p instanceof Error?p.message:e.t("read.error")})}})()},N=()=>{if(!o.speaking())return[{id:"start",label:e.t("read.idle"),icon:"outline-audio",run:$}];const l=o.paused(),p=[{id:"restart",label:e.t("read.restart"),icon:"outline-reset",run:()=>{o.restart()}}];return o.canPause()&&p.push({id:"pause",label:l?e.t("read.resume"):e.t("read.pause"),icon:"outline-audio",run:()=>o.togglePause()}),p.push({id:"stop",label:e.t("read.stop"),icon:"xmark-small",run:()=>o.cancel()}),p};t.on("final",l=>{const p=e.sessions.current()?.key;if(u&&p&&p!==u){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 k=b.text(),R=r.value.trailingSpace&&k.length>0&&!/\s$/.test(k)?" ":"";b.insert(R+l.text),r.value.autoFocus&&b.focus()}),t.on("error",l=>{e.toast.show({variant:"error",title:e.t("error.title"),description:f(l.code,l.detail,l.available),persistent:l.code==="LANGUAGE_NOT_AVAILABLE"||l.code==="NO_RECOGNIZERS",duration:8e3})});const E=()=>{if(e.app.platform!=="desktop")return;if(d.value.mode==="windows")return a()?{label:e.t("pill.dictating"),icon:"outline-wave-audio",tone:"danger",title:e.t("pill.stop"),run:v}:{label:e.t("pill.idle"),icon:"outline-wave-audio",title:e.t("pill.winH"),run:v};const l=t.state()?.status??"idle";return l==="starting"?{label:e.t("pill.starting"),icon:"outline-wave-audio",title:e.t("pill.title"),run:v}:l==="listening"?{label:e.t("pill.listening"),icon:"outline-wave-audio",tone:"danger",title:e.t("pill.stop"),run:v}:l==="error"?{label:e.t("pill.error"),icon:"outline-wave-audio",tone:"warning",title:e.t("pill.title"),run:v}:{label:e.t("pill.idle"),icon:"outline-wave-audio",title:e.t("pill.title"),run:v}},A=()=>{if(e.app.platform!=="desktop")return;const l=o.speaking(),p=o.paused(),b=l?N().map(k=>({id:k.id,label:k.label,icon:k.icon,tone:k.id==="stop"?"danger":void 0,run:k.run})):[];return{label:l?p?e.t("read.paused"):e.t("read.speaking"):e.t("read.idle"),icon:"outline-audio",tone:l?"success":void 0,title:e.t("read.title"),rows:b.length>0?b:void 0,run:l?void 0:$}};e.add(gui$1.ComposerPill,()=>({id:"dictate",show:()=>E()})),e.add(gui$1.ComposerPill,()=>({id:"read",order:1,show:()=>A(),menu:l=>web.createComponent(ReadMenu,{actions:N,get close(){return l.close}})})),e.add(gui$1.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:v})),e.add(gui$1.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(gui$1.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:$})),e.add(gui$1.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:"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:()=>web.createComponent(VoiceSettings,{speaker:o})})},gui=gui$1.Plugin.define({...definition,setup,main});exports.default=gui;
190
+ `,Pe="powershell.exe";function Le(){const e=F.join(Z.tmpdir(),"opencode-voice-dictation");z.mkdirSync(e,{recursive:!0});const t=q.createHash("sha1").update(re).digest("hex").slice(0,12),n=F.join(e,`win-h-${t}.ps1`);return z.writeFileSync(n,re,"utf8"),n}function _e(e){return B.spawn(Pe,e,{windowsHide:!0,stdio:"ignore"})}function Ve(){return new Promise((e,t)=>{const n=_e(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",Le()]);n.on("error",d=>t(d)),n.on("close",d=>{d===0?e():t(new Error(`Win+H helper exited with code ${d??"unknown"}`))})})}const We=e=>{let t="idle",n="",d="",o,r=!1;const g=()=>{o=void 0,r=!1},a=e.provide(Y,{state:()=>({status:t,error:n,locale:d}),start(v,N){o&&(r=!0,o.stop(),g()),t="starting",n="",d=v.locale,a.changed();const S=N.window;try{o=Ce({locale:v.locale,minConfidence:v.minConfidence},{onReady(y){t="listening",d=y,a.changed()},onFinal(y,b){a.emit("final",{text:y,confidence:b},S)},onError(y,b,_){t="error",n=y,a.changed(),a.emit("error",{code:y,detail:b,available:_},S)},onExit(y,b){if(r){g(),t="idle",n="",d="",a.changed();return}g(),t!=="error"&&(t="error",n=y===null?"ENGINE_EXIT":`ENGINE_EXIT_${y}`,a.changed(),a.emit("error",{code:"ENGINE_EXIT",detail:b||(y===null?"":String(y)),available:[]},S))}})}catch(y){throw t="error",n="ENGINE_START_FAILED",a.changed(),y}},stop(){o&&(r=!0,o.stop(),g()),t="idle",n="",d="",a.changed()},diagnose(){return ke()}});let l;const p=e.provide(ce,{speak(v){l?.stop(),l=Ne(v,N=>{l=void 0,p.emit("done",{ok:N})})},stop(){l&&(l.stop(),l=void 0)},voices(){return Ae()}});e.provide(J,{toggle(){return Ve()}}),e.onCleanup(()=>{o&&(r=!0,o.stop(),g()),l?.stop(),l=void 0})},se={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"}}},events:{}},ze=250;function Oe(e,t){let n=e.replace(/\r\n/g,`
191
+ `);return t?n=n.replace(/```[\s\S]*?```/g," "):n=n.replace(/```[^\n]*\n([\s\S]*?)```/g,"$1"),n=n.replace(/`([^`]+)`/g,"$1"),n=n.replace(/!\[[^\]]*\]\([^)]*\)/g," "),n=n.replace(/\[([^\]]+)\]\([^)]*\)/g,"$1"),n=n.replace(/^\s*#{1,6}\s+/gm,""),n=n.replace(/^\s*>\s?/gm,""),n=n.replace(/^\s*[-*+]\s+/gm,""),n=n.replace(/^\s*\d+\.\s+/gm,""),n=n.replace(/(\*\*|__)(.*?)\1/g,"$2"),n=n.replace(/(\*|_)(.*?)\1/g,"$2"),n=n.replace(/^\s*\|.*\|\s*$/gm," "),n=n.replace(/\n{2,}/g,". "),n=n.replace(/\n/g," "),n=n.replace(/\s+/g," "),n=n.replace(/\s+([.,;:!?])/g,"$1"),n=n.replace(/([.,;:!?]){2,}/g,"$1"),n.trim()}function ae(e,t=ze){const n=e.trim();if(!n)return[];const d=n.split(new RegExp("(?<=[.!?…])\\s+")),o=[];let r="";const g=a=>{let l="";for(const p of a.split(/\s+/))p&&(l&&l.length+1+p.length>t?(o.push(l),l=p):l=l?`${l} ${p}`:p);return l};for(const a of d){if(a.length>t){r&&(o.push(r),r=""),r=g(a);continue}r&&r.length+1+a.length>t?(o.push(r),r=a):r=r?`${r} ${a}`:a}return r&&o.push(r),o}function Fe(e,t){const n=e.stores.speech,d=e.stores.tts.value,[o,r]=I.createSignal(d.engine==="edge"?"edge":"system"),[g,a]=I.createSignal(d.voice),l=f=>{(f.engine==="edge"||f.engine==="system")&&r(f.engine),f.voice!==void 0&&a(f.voice)},[p,v]=I.createSignal(!1),[N,S]=I.createSignal(!1),[y,b]=I.createSignal([]),[_,V]=I.createSignal(""),[R,P]=I.createSignal("none");let s=0,w=[],C="",$,W;const i=new Map,h=()=>typeof globalThis.speechSynthesis<"u"?globalThis.speechSynthesis:void 0,Q=()=>{const f=h()?.getVoices()??[];if(f.length===0)return;const m=n.value.voiceURI;if(m){const T=f.find(D=>D.voiceURI===m);if(T)return T}const E=e.locale.locale().split("-")[0]?.toLowerCase()??"es";return f.find(T=>T.lang.toLowerCase().startsWith(E))??f[0]},O=async()=>{if(o()==="edge"){try{const m=await e.client.rpc(se).edgeVoices({});m.length>0?(b(m.map(E=>({id:E.id,label:E.label}))),V("")):V(e.t("speech.edge.error"))}catch(m){V(m instanceof Error?m.message:String(m))}P("edge");return}V("");const f=h()?.getVoices()??[];if(f.length>0){b(f.map(m=>({id:m.voiceURI,label:`${m.name} — ${m.lang}`}))),P("web");return}try{const m=await t.voices();if(m.length>0){b(m.map(E=>({id:E,label:E}))),P("sapi");return}}catch{}P("none")},H=()=>{$&&($.onended=null,$.onerror=null,$.pause(),$=void 0),W&&(URL.revokeObjectURL(W),W=void 0)},x=()=>{s++,w=[],h()?.cancel(),H(),S(!1),v(!1),R()==="sapi"&&t.stop().catch(()=>{})},K=()=>R()==="web"||R()==="edge",ee=()=>{if(!(!p()||N()||!K())){if(R()==="edge"){$?.pause(),S(!0);return}h()?.pause(),S(!0)}},te=()=>{if(!(!p()||!N()||!K())){if(R()==="edge"){$?.play().catch(()=>{}),S(!1);return}h()?.resume(),S(!1)}},fe=()=>{N()?te():ee()},ve=()=>C?X(C):Promise.resolve(!1),Se=f=>{const m=g()||"es-ES-XimenaNeural",E=n.value.rate,T=ae(f,4e3),D=`${m}|${E}|${f}`,U=i.get(D)??[];i.clear(),i.set(D,U);const L=s;v(!0),S(!1);const j=A=>{if(L===s){if(A>=T.length){S(!1),v(!1);return}(async()=>{let M=U[A];try{M||(M=await e.client.rpc(se).edgeSpeak({text:T[A]??"",voice:m,rate:E}),U[A]=M)}catch{L===s&&(S(!1),v(!1),e.toast.show({variant:"error",title:e.t("read.title"),description:e.t("error.edge")}));return}if(L!==s)return;const ye=Uint8Array.from(atob(M),be=>be.charCodeAt(0)),ne=URL.createObjectURL(new Blob([ye],{type:"audio/mpeg"}));H(),W=ne;const G=new Audio(ne);$=G,G.onended=()=>{L===s&&(H(),j(A+1))},G.onerror=()=>{L===s&&(H(),S(!1),v(!1))},await G.play().catch(()=>{L===s&&(H(),S(!1),v(!1),e.toast.show({variant:"error",title:e.t("read.title"),description:e.t("error.edge")}))})})()}};return j(0),!0},X=async f=>{C=f;const m=Oe(f,n.value.skipCode);if(!m)return!1;if(x(),await O(),o()==="edge")return Se(m);if(R()==="web"){const E=h();if(!E)return!1;const T=Q(),D=n.value.rate,U=s;w=ae(m),v(!0),S(!1);const L=()=>{if(U!==s)return;const j=w.shift();if(!j){S(!1),v(!1);return}const A=new SpeechSynthesisUtterance(j);T&&(A.voice=T),T?.lang&&(A.lang=T.lang),A.rate=D,A.onend=L,A.onerror=L,E.speak(A)};return L(),!0}if(R()==="sapi"){v(!0),S(!1);try{await t.speak({text:m,rate:n.value.rate,voice:n.value.voiceURI})}catch(E){throw v(!1),E}return!0}throw v(!1),new Error(e.t("read.noVoices"))},we=async()=>{const f=e.sessions.current();if(!f)return"";const E=(await f.server.client.message.list({sessionID:f.id,type:"assistant",order:"desc",limit:1})).data[0];return!E||E.type!=="assistant"?"":E.content.filter(T=>T.type==="text").map(T=>T.text).join(`
192
+ `)};return t.on("done",()=>{S(!1),v(!1)}),h()?.addEventListener?.("voiceschanged",()=>{O()}),O(),{speaking:p,paused:N,backend:R,engine:o,voice:g,error:_,canPause:K,setConfig:l,voices:y,refreshVoices:O,lastAssistantText:we,speak:X,cancel:x,pause:ee,resume:te,togglePause:fe,restart:ve,preview:async()=>{const f=await X(e.t("speech.preview.text"));if(f){const m=o()==="edge"?`Edge · ${g()}`:`${R()} · ${n.value.voiceURI||"auto"}`;e.toast.show({variant:"default",title:e.t("read.title"),description:m})}return f}}}const De=["es-ES","es-MX","es-AR","es-CO","pt-BR","pt-PT","en-US","en-GB","fr-FR","de-DE","it-IT"];function He(e){const t=k.usePlugin(),n=t.ui,d=t.stores.prefs,o=t.stores.speech,r=t.stores.tts,g=t.stores.engine,a=t.main(Y),l=t.main(J),[p,v]=I.createSignal([]),[N,S]=I.createSignal(!1);I.onMount(()=>{e.speaker.setConfig({engine:r.value.engine,voice:r.value.voice}),a.diagnose().then(i=>{i.locales.length>0&&v(i.locales)}).catch(()=>{}),e.speaker.refreshVoices()});const y=()=>{const i=p(),h=d.value.locale;return(i.includes(h)?i:[h,...i]).map(O=>({value:O,label:O}))},b=()=>{const i=e.speaker.voices().map(h=>({value:h.id,label:h.label}));return r.value.engine==="edge"?i:[{value:"",label:t.t("speech.voice.auto")},...i]},_=()=>r.value.engine==="edge"?r.value.voice:o.value.voiceURI,V=i=>{if(r.value.engine==="edge"){r.update(h=>{h.voice=i}),e.speaker.setConfig({voice:i});return}P({voiceURI:i})},R=i=>{d.update(h=>{i.locale!==void 0&&(h.locale=i.locale),i.minConfidence!==void 0&&(h.minConfidence=i.minConfidence),i.trailingSpace!==void 0&&(h.trailingSpace=i.trailingSpace),i.autoFocus!==void 0&&(h.autoFocus=i.autoFocus)})},P=i=>{o.update(h=>{i.voiceURI!==void 0&&(h.voiceURI=i.voiceURI),i.rate!==void 0&&(h.rate=i.rate),i.skipCode!==void 0&&(h.skipCode=i.skipCode)})},s=i=>{g.update(h=>{h.mode=i})},w=i=>{r.update(h=>{h.engine=i}),e.speaker.setConfig({engine:i}),e.speaker.refreshVoices()},C=async()=>{try{await l.toggle(),t.toast.show({variant:"default",title:t.t("settings.winh"),description:t.t("settings.winh.sent")})}catch(i){t.toast.show({variant:"error",title:t.t("pill.title"),description:i instanceof Error?i.message:t.t("error.winh")})}},$=async()=>{try{await e.speaker.preview()}catch(i){t.toast.show({variant:"error",title:t.t("read.title"),description:i instanceof Error?i.message:t.t("read.error")})}},W=async()=>{S(!0);try{const i=await a.diagnose();if(i.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(!i.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}i.locales.length>0&&!p().length&&v(i.locales),t.toast.show({variant:"success",title:t.t("error.title"),description:t.t("settings.test.success",{locale:i.selected||i.locales[0]||d.value.locale})})}catch(i){t.toast.show({variant:"error",title:t.t("error.title"),description:i instanceof Error?i.message:String(i)})}finally{S(!1)}};return u.createComponent(n.SettingsLayout,{get title(){return t.t("settings.title")},get description(){return t.t("settings.description")},get children(){return[u.createComponent(n.SettingsSection,{get title(){return t.t("settings.section")},get children(){return[u.createComponent(n.SettingsRow,{id:"engine",get title(){return t.t("settings.engine")},get description(){return t.t("settings.engine.description")},get children(){return u.createComponent(n.Select,{get label(){return t.t("settings.engine")},get value(){return g.value.mode},get options(){return[{value:"windows",label:t.t("engine.windows")},{value:"system",label:t.t("engine.system")}]},onChange:i=>s(i)})}}),u.createComponent(I.Show,{get when(){return g.value.mode==="windows"},get children(){return u.createComponent(n.SettingsRow,{id:"winh",get title(){return t.t("settings.winh")},get description(){return t.t("settings.winh.description")},get children(){return u.createComponent(n.Button,{onClick:()=>{C()},get children(){return t.t("settings.winh.run")}})}})}}),u.createComponent(I.Show,{get when(){return g.value.mode==="system"},get children(){return[u.createComponent(n.SettingsRow,{id:"language",get title(){return t.t("settings.language")},get description(){return t.t("settings.language.description")},get children(){return u.createComponent(n.Select,{get label(){return t.t("settings.language")},get value(){return d.value.locale},get options(){return u.memo(()=>y().length>0)()?y():De.map(i=>({value:i,label:i}))},onChange:i=>R({locale:i})})}}),u.createComponent(n.SettingsRow,{id:"confidence",get title(){return t.t("settings.confidence")},get description(){return t.t("settings.confidence.description")},get children(){return u.createComponent(n.Select,{get label(){return t.t("settings.confidence")},get value(){return String(d.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:i=>R({minConfidence:Number(i)||.4})})}}),u.createComponent(n.SettingsRow,{id:"space",get title(){return t.t("settings.space")},get description(){return t.t("settings.space.description")},get children(){return u.createComponent(n.Switch,{get label(){return t.t("settings.space")},hideLabel:!0,get checked(){return d.value.trailingSpace},onChange:i=>R({trailingSpace:i})})}}),u.createComponent(n.SettingsRow,{id:"focus",get title(){return t.t("settings.focus")},get description(){return t.t("settings.focus.description")},get children(){return u.createComponent(n.Switch,{get label(){return t.t("settings.focus")},hideLabel:!0,get checked(){return d.value.autoFocus},onChange:i=>R({autoFocus:i})})}})]}})]}}),u.createComponent(n.SettingsSection,{get title(){return t.t("speech.section")},get children(){return[u.createComponent(n.SettingsRow,{id:"tts-engine",get title(){return t.t("speech.engine")},get description(){return t.t("speech.engine.description")},get children(){return u.createComponent(n.Select,{get label(){return t.t("speech.engine")},get value(){return r.value.engine},get options(){return[{value:"edge",label:t.t("speech.engine.edge")},{value:"system",label:t.t("speech.engine.system")}]},onChange:i=>w(i)})}}),u.createComponent(n.SettingsRow,{id:"voice",get title(){return t.t("speech.voice")},get description(){return t.t("speech.voice.description")},get children(){return u.createComponent(n.Select,{get label(){return t.t("speech.voice")},get value(){return _()},get options(){return b()},onChange:i=>V(i)})}}),u.createComponent(I.Show,{get when(){return e.speaker.error()},get children(){return u.createComponent(n.SettingsRow,{id:"tts-error",get title(){return t.t("speech.edge.error")},get description(){return e.speaker.error()}})}}),u.createComponent(n.SettingsRow,{id:"rate",get title(){return t.t("speech.rate")},get description(){return t.t("speech.rate.description")},get children(){return u.createComponent(n.Select,{get label(){return t.t("speech.rate")},get value(){return String(o.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:i=>P({rate:Number(i)||1})})}}),u.createComponent(n.SettingsRow,{id:"skip-code",get title(){return t.t("speech.skipCode")},get description(){return t.t("speech.skipCode.description")},get children(){return u.createComponent(n.Switch,{get label(){return t.t("speech.skipCode")},hideLabel:!0,get checked(){return o.value.skipCode},onChange:i=>P({skipCode:i})})}}),u.createComponent(n.SettingsRow,{id:"preview",get title(){return t.t("speech.preview")},get description(){return t.t("speech.preview.description")},get children(){return u.createComponent(n.Button,{onClick:()=>{$()},get children(){return t.t("read.idle")}})}})]}}),u.createComponent(n.SettingsSection,{get title(){return t.t("settings.test.section")},get children(){return u.createComponent(n.SettingsRow,{id:"test",get title(){return t.t("settings.test")},get description(){return t.t("settings.test.description")},get children(){return u.createComponent(n.Button,{get busy(){return N()},onClick:()=>{W()},get children(){return t.t("settings.test.run")}})}})}})]}})}function Ue(e){const t=k.usePlugin();return u.memo(()=>e.actions().map(n=>u.createComponent(t.ui.Button,{get variant(){return n.id==="stop"?"danger":"neutral"},get icon(){return n.icon},onClick:()=>{n.run(),e.close()},get children(){return n.label}})))}const je=e=>{const t=e.main(Y),n=e.main(ce),d=e.main(J),o=Fe(e,n),r=e.stores.prefs,g=e.stores.engine,[a,l]=I.createSignal(!1);let p;const v=(s,w,C)=>{switch(s){case"LANGUAGE_NOT_AVAILABLE":return e.t("error.language",{locale:w||r.value.locale,available:C.length>0?C.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:s})}},N=()=>{a()&&(l(!1),d.toggle().catch(()=>{})),o.cancel(),p=e.sessions.current()?.key,t.start({locale:r.value.locale,minConfidence:r.value.minConfidence}).catch(s=>{e.toast.show({variant:"error",title:e.t("error.title"),description:s instanceof Error?s.message:String(s)})})},S=()=>{t.stop().catch(()=>{})},y=()=>{o.cancel(),e.screen.current()?.composer.focus(),d.toggle().then(()=>l(s=>!s)).catch(s=>{l(!1),e.toast.show({variant:"error",title:e.t("pill.title"),description:s instanceof Error?`${e.t("error.winh")} ${s.message}`:e.t("error.winh")})})},b=()=>{if(g.value.mode==="windows"){y();return}const s=t.state()?.status??"idle";s==="starting"||s==="listening"?S():N()},_=()=>{if(o.speaking()){o.cancel();return}if(!e.sessions.current()){e.toast.show({variant:"error",title:e.t("read.title"),description:e.t("read.noSession")});return}const s=t.state()?.status??"idle";(s==="starting"||s==="listening")&&t.stop().catch(()=>{}),(async()=>{try{const w=await o.lastAssistantText();if(!w.trim()){e.toast.show({variant:"default",title:e.t("read.title"),description:e.t("read.empty")});return}await o.speak(w)||e.toast.show({variant:"default",title:e.t("read.title"),description:e.t("read.empty")})}catch(w){e.toast.show({variant:"error",title:e.t("read.title"),description:w instanceof Error?w.message:e.t("read.error")})}})()},V=()=>{if(!o.speaking())return[{id:"start",label:e.t("read.idle"),icon:"outline-audio",run:_}];const s=o.paused(),w=[{id:"restart",label:e.t("read.restart"),icon:"outline-reset",run:()=>{o.restart()}}];return o.canPause()&&w.push({id:"pause",label:s?e.t("read.resume"):e.t("read.pause"),icon:"outline-audio",run:()=>o.togglePause()}),w.push({id:"stop",label:e.t("read.stop"),icon:"xmark-small",run:()=>o.cancel()}),w};t.on("final",s=>{const w=e.sessions.current()?.key;if(p&&w&&w!==p){e.toast.show({variant:"error",title:e.t("error.title"),description:e.t("toast.discarded")});return}const C=e.screen.current()?.composer;if(!C)return;const $=C.text(),W=r.value.trailingSpace&&$.length>0&&!/\s$/.test($)?" ":"";C.insert(W+s.text),r.value.autoFocus&&C.focus()}),t.on("error",s=>{e.toast.show({variant:"error",title:e.t("error.title"),description:v(s.code,s.detail,s.available),persistent:s.code==="LANGUAGE_NOT_AVAILABLE"||s.code==="NO_RECOGNIZERS",duration:8e3})});const R=()=>{if(e.app.platform!=="desktop")return;if(g.value.mode==="windows")return a()?{label:e.t("pill.dictating"),icon:"outline-wave-audio",tone:"danger",title:e.t("pill.stop"),run:b}:{label:e.t("pill.idle"),icon:"outline-wave-audio",title:e.t("pill.winH"),run:b};const s=t.state()?.status??"idle";return s==="starting"?{label:e.t("pill.starting"),icon:"outline-wave-audio",title:e.t("pill.title"),run:b}:s==="listening"?{label:e.t("pill.listening"),icon:"outline-wave-audio",tone:"danger",title:e.t("pill.stop"),run:b}:s==="error"?{label:e.t("pill.error"),icon:"outline-wave-audio",tone:"warning",title:e.t("pill.title"),run:b}:{label:e.t("pill.idle"),icon:"outline-wave-audio",title:e.t("pill.title"),run:b}},P=()=>{if(e.app.platform!=="desktop")return;const s=o.speaking(),w=o.paused(),C=s?V().map($=>({id:$.id,label:$.label,icon:$.icon,tone:$.id==="stop"?"danger":void 0,run:$.run})):[];return{label:s?w?e.t("read.paused"):e.t("read.speaking"):e.t("read.idle"),icon:"outline-audio",tone:s?"success":void 0,title:e.t("read.title"),rows:C.length>0?C:void 0,run:s?void 0:_}};e.add(k.ComposerPill,()=>({id:"dictate",show:()=>R()})),e.add(k.ComposerPill,()=>({id:"read",order:1,show:()=>P(),menu:s=>u.createComponent(Ue,{actions:V,get close(){return s.close}})})),e.add(k.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:b})),e.add(k.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(k.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:_})),e.add(k.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:"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:()=>u.createComponent(He,{speaker:o})})},Be=k.Plugin.define({...$e,setup:je,main:We});exports.default=Be;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-voice-dictation",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Offline voice dictation and read-aloud for OpenCode Desktop on Windows",
5
5
  "license": "MIT",
6
6
  "author": "Fran Carlos <francarlos803@gmail.com>",
@@ -25,7 +25,8 @@
25
25
  "type": "module",
26
26
  "exports": {
27
27
  ".": "./src/index.ts",
28
- "./gui-experimental": "./dist/gui-experimental.cjs"
28
+ "./gui-experimental": "./dist/gui-experimental.cjs",
29
+ "./rpc": "./src/rpc.ts"
29
30
  },
30
31
  "files": [
31
32
  "src",
@@ -35,7 +36,7 @@
35
36
  ],
36
37
  "dependencies": {
37
38
  "msedge-tts": "^2.1.1",
38
- "opencode-voice-dictation": "^0.2.0"
39
+ "@opencode/plugin": "2.2.6"
39
40
  },
40
41
  "publishConfig": {
41
42
  "access": "public"
@@ -48,7 +49,6 @@
48
49
  "prepublishOnly": "npm run typecheck && npm run build && npm test"
49
50
  },
50
51
  "devDependencies": {
51
- "@opencode/plugin": "2.2.6",
52
52
  "@types/node": "^24.0.0",
53
53
  "effect": "4.1.0",
54
54
  "solid-js": "^1.9.17",
@@ -1,10 +1,4 @@
1
- /**
2
- * Edge neural text-to-speech.
3
- *
4
- * `msedge-tts` is loaded dynamically so the renderer process never evaluates
5
- * Node-only modules: only the Electron main process imports it.
6
- */
7
- type EdgeModule = typeof import("msedge-tts")
1
+ import { MsEdgeTTS, OUTPUT_FORMAT } from "msedge-tts"
8
2
 
9
3
  export interface EdgeVoiceInfo {
10
4
  id: string
@@ -20,18 +14,6 @@ interface RawVoice {
20
14
  Gender: string
21
15
  }
22
16
 
23
- let cached: EdgeModule | undefined
24
-
25
- function load(): EdgeModule {
26
- if (cached) return cached
27
-
28
- // Node-only package: Vite must not bundle it. Resolve it at runtime in the
29
- // Electron main process, where `require` is real.
30
- const runtimeRequire = eval("require") as NodeRequire
31
- cached = runtimeRequire("msedge-tts") as EdgeModule
32
- return cached
33
- }
34
-
35
17
  function shortName(voice: RawVoice): string {
36
18
  const friendly = voice.FriendlyName.replace(/^Microsoft\s+/i, "")
37
19
  .replace(/\s+Online.*$/i, "")
@@ -44,11 +26,10 @@ function shortName(voice: RawVoice): string {
44
26
  }
45
27
 
46
28
  export async function listEdgeVoices(): Promise<EdgeVoiceInfo[]> {
47
- const { MsEdgeTTS } = load()
48
29
  const tts = new MsEdgeTTS()
49
30
 
50
31
  try {
51
- const voices = (await tts.getVoices()) as RawVoice[]
32
+ const voices = (await tts.getVoices()) as unknown as RawVoice[]
52
33
 
53
34
  return voices
54
35
  .filter((voice) => voice.Locale.toLowerCase().startsWith("es-"))
@@ -69,7 +50,6 @@ export async function synthesizeEdge(input: {
69
50
  voice: string
70
51
  rate: number
71
52
  }): Promise<string> {
72
- const { MsEdgeTTS, OUTPUT_FORMAT } = load()
73
53
  const tts = new MsEdgeTTS()
74
54
 
75
55
  try {
@@ -69,24 +69,6 @@ export interface DiagnoseResult {
69
69
  voices: {
70
70
  output: Schema.Array(Schema.String),
71
71
  },
72
- edgeVoices: {
73
- output: Schema.Array(
74
- Schema.Struct({
75
- id: Schema.String,
76
- label: Schema.String,
77
- locale: Schema.String,
78
- gender: Schema.String,
79
- }),
80
- ),
81
- },
82
- edgeSpeak: {
83
- input: Schema.Struct({
84
- text: Schema.String,
85
- voice: Schema.String,
86
- rate: Schema.Number,
87
- }),
88
- output: Schema.String,
89
- },
90
72
  },
91
73
  events: {
92
74
  done: Schema.Struct({ ok: Schema.Boolean }),
@@ -127,7 +127,8 @@ export const definition = Plugin.define({
127
127
  "speech.engine.description": "Edge neural sounds much more natural but needs internet. System uses the installed Windows voices, offline.",
128
128
  "speech.engine.edge": "Edge neural (online)",
129
129
  "speech.engine.system": "System (offline)",
130
- "error.edge": "The Edge voice could not be used. Falling back to the system voice.",
130
+ "error.edge": "The Edge voice could not be used. Check the connection or switch to the system voice.",
131
+ "speech.edge.error": "Edge voices could not be loaded",
131
132
  "speech.voice": "Voice",
132
133
  "speech.voice.description": "The voice used for reading.",
133
134
  "speech.voice.auto": "Automatic",
@@ -212,7 +213,8 @@ export const definition = Plugin.define({
212
213
  "speech.engine.description": "Edge neuronal suena mucho más natural pero necesita internet. Sistema usa las voces instaladas de Windows, sin conexión.",
213
214
  "speech.engine.edge": "Edge neuronal (online)",
214
215
  "speech.engine.system": "Sistema (offline)",
215
- "error.edge": "No se pudo usar la voz de Edge. Se usará la voz del sistema.",
216
+ "error.edge": "No se pudo usar la voz de Edge. Revisa la conexión o cambia a la voz del sistema.",
217
+ "speech.edge.error": "No se pudieron cargar las voces de Edge",
216
218
  "speech.voice": "Voz",
217
219
  "speech.voice.description": "La voz que se usa para leer.",
218
220
  "speech.voice.auto": "Automática",
package/src/gui/main.ts CHANGED
@@ -3,7 +3,6 @@ import { Dictation, Speech, VoiceTyping } from "./contract"
3
3
  import { diagnose, startEngine, type EngineHandle } from "./engines/system-speech"
4
4
  import { listSapiVoices, speakSapi, type SpeakHandle } from "./engines/sapi-speech"
5
5
  import { sendVoiceTypingShortcut } from "./engines/win-h"
6
- import { listEdgeVoices, synthesizeEdge } from "./engines/edge-tts"
7
6
 
8
7
  type Status = "idle" | "starting" | "listening" | "error"
9
8
 
@@ -130,14 +129,6 @@ export const main: Plugin.MainSetup = (ctx) => {
130
129
  voices() {
131
130
  return listSapiVoices()
132
131
  },
133
-
134
- edgeVoices() {
135
- return listEdgeVoices()
136
- },
137
-
138
- edgeSpeak(input) {
139
- return synthesizeEdge(input)
140
- },
141
132
  })
142
133
 
143
134
  ctx.provide(VoiceTyping, {
@@ -286,6 +286,14 @@ export function VoiceSettings(props: { speaker: Speaker }) {
286
286
  />
287
287
  </ui.SettingsRow>
288
288
 
289
+ <Show when={props.speaker.error()}>
290
+ <ui.SettingsRow
291
+ id="tts-error"
292
+ title={ctx.t("speech.edge.error")}
293
+ description={props.speaker.error()}
294
+ />
295
+ </Show>
296
+
289
297
  <ui.SettingsRow
290
298
  id="rate"
291
299
  title={ctx.t("speech.rate")}
package/src/gui/speak.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createSignal } from "solid-js"
2
2
  import type { IpcClient, SetupContext } from "@opencode/plugin/gui"
3
+ import { VoiceRpc } from "../rpc"
3
4
  import { Speech } from "./contract"
4
5
  import type { definition } from "./definition"
5
6
 
@@ -14,6 +15,7 @@ export interface Speaker {
14
15
  backend(): "web" | "sapi" | "edge" | "none"
15
16
  engine(): "edge" | "system"
16
17
  voice(): string
18
+ error(): string
17
19
  canPause(): boolean
18
20
  setConfig(input: { engine?: string; voice?: string }): void
19
21
  voices(): VoiceOption[]
@@ -122,6 +124,7 @@ export function createSpeaker(
122
124
  const [speaking, setSpeaking] = createSignal(false)
123
125
  const [paused, setPaused] = createSignal(false)
124
126
  const [voices, setVoices] = createSignal<VoiceOption[]>([])
127
+ const [error, setError] = createSignal("")
125
128
  const [backend, setBackend] = createSignal<"web" | "sapi" | "edge" | "none">("none")
126
129
 
127
130
  let generation = 0
@@ -151,18 +154,24 @@ export function createSpeaker(
151
154
  const refreshVoices = async () => {
152
155
  if (engine() === "edge") {
153
156
  try {
154
- const list = await speech.edgeVoices()
157
+ const list = (await ctx.client.rpc(VoiceRpc).edgeVoices({})) as unknown as Array<{
158
+ id: string
159
+ label: string
160
+ }>
155
161
  if (list.length > 0) {
156
162
  setVoices(list.map((voice) => ({ id: voice.id, label: voice.label })))
163
+ setError("")
164
+ } else {
165
+ setError(ctx.t("speech.edge.error"))
157
166
  }
158
- } catch {
159
- // Keep the current list. The error surfaces when speaking, never as a
160
- // silent switch to the system voice.
167
+ } catch (cause) {
168
+ setError(cause instanceof Error ? cause.message : String(cause))
161
169
  }
162
170
  setBackend("edge")
163
171
  return
164
172
  }
165
173
 
174
+ setError("")
166
175
  const available = synth()?.getVoices() ?? []
167
176
 
168
177
  if (available.length > 0) {
@@ -274,7 +283,11 @@ export function createSpeaker(
274
283
 
275
284
  try {
276
285
  if (!encoded) {
277
- encoded = await speech.edgeSpeak({ text: blocks[index] ?? "", voice: selected, rate })
286
+ encoded = (await ctx.client.rpc(VoiceRpc).edgeSpeak({
287
+ text: blocks[index] ?? "",
288
+ voice: selected,
289
+ rate,
290
+ })) as string
278
291
  cached[index] = encoded
279
292
  }
280
293
  } catch {
@@ -444,6 +457,7 @@ export function createSpeaker(
444
457
  backend,
445
458
  engine,
446
459
  voice,
460
+ error,
447
461
  canPause,
448
462
  setConfig,
449
463
  voices,
package/src/index.ts CHANGED
@@ -1,19 +1,22 @@
1
- import type { Plugin } from "@opencode/plugin"
1
+ import { Plugin } from "@opencode/plugin"
2
+ import { listEdgeVoices, synthesizeEdge } from "./edge.js"
3
+ import { VoiceRpc } from "./rpc.js"
2
4
 
3
5
  /**
4
6
  * Server part of the plugin.
5
7
  *
6
- * It does nothing on its own: it exists so the desktop app assigns the plugin
7
- * the id `voice-dictation` (used for commands, settings pages and the Ipc
8
- * contract) and so the GUI part is loaded from the global `opencode.json`.
9
- *
10
- * `@opencode/plugin` is a type-only import on purpose: the server loads this
11
- * file directly, and a runtime import would make the plugin depend on a
12
- * `node_modules` tree next to it.
8
+ * It registers the Edge TTS RPC. Running it here (instead of in the Electron
9
+ * main process) lets `msedge-tts` resolve from the plugin's own node_modules.
13
10
  */
14
- const plugin: Plugin.Plugin = {
11
+ export default Plugin.define({
15
12
  id: "voice-dictation",
16
- setup() {},
17
- }
18
-
19
- export default plugin
13
+ async setup(ctx) {
14
+ await ctx.rpc.register(VoiceRpc, {
15
+ edgeVoices: async () => listEdgeVoices(),
16
+ edgeSpeak: async (input) => {
17
+ const { text, voice, rate } = input as { text: string; voice: string; rate: number }
18
+ return synthesizeEdge({ text, voice, rate })
19
+ },
20
+ })
21
+ },
22
+ })
package/src/rpc.ts ADDED
@@ -0,0 +1,45 @@
1
+ import type { Rpc } from "@opencode/plugin"
2
+
3
+ /**
4
+ * Edge TTS runs in the OpenCode server, where the plugin's own `node_modules`
5
+ * resolves. The GUI only calls these methods and plays the returned MP3.
6
+ *
7
+ * The definition is a plain object (Rpc.define is the identity function), so
8
+ * the GUI bundle does not need to load the RPC module at runtime.
9
+ */
10
+ export const VoiceRpc = {
11
+ id: "voice-dictation.voice",
12
+ methods: {
13
+ edgeVoices: {
14
+ input: { type: "object", additionalProperties: false },
15
+ output: {
16
+ type: "array",
17
+ items: {
18
+ type: "object",
19
+ properties: {
20
+ id: { type: "string" },
21
+ label: { type: "string" },
22
+ locale: { type: "string" },
23
+ gender: { type: "string" },
24
+ },
25
+ required: ["id", "label", "locale", "gender"],
26
+ additionalProperties: false,
27
+ },
28
+ },
29
+ },
30
+ edgeSpeak: {
31
+ input: {
32
+ type: "object",
33
+ properties: {
34
+ text: { type: "string" },
35
+ voice: { type: "string" },
36
+ rate: { type: "number" },
37
+ },
38
+ required: ["text", "voice", "rate"],
39
+ additionalProperties: false,
40
+ },
41
+ output: { type: "string" },
42
+ },
43
+ },
44
+ events: {},
45
+ } as const satisfies Rpc.PortableDefinition