opencode-voice-dictation 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 a=!1,s="",o="";const d=r=>{switch(r.type){case"ready":t.onReady(r.locale??e.locale);break;case"final":r.text&&t.onFinal(r.text,r.confidence??0);break;case"error":t.onError(r.code??"UNKNOWN",r.detail??"",r.available??[]);break}};return n.stdout?.setEncoding("utf8"),n.stdout?.on("data",r=>{o+=r;let c=o.indexOf(`
109
- `);for(;c>=0;){const u=o.slice(0,c).replace(/\r$/,"").trim();if(o=o.slice(c+1),u)try{d(JSON.parse(u))}catch{}c=o.indexOf(`
110
- `)}}),n.stderr?.setEncoding("utf8"),n.stderr?.on("data",r=>{s=(s+r).slice(-2e3)}),n.on("error",r=>{a||t.onError("ENGINE_START_FAILED",r.message,[])}),n.on("close",r=>{a||t.onExit(r,s.trim().slice(-500))}),{stop(){a=!0,killTree$1(n)}}}function diagnose(){return new Promise((e,t)=>{const n=spawnPowershell$2(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",scriptFile$2(),"-Diagnose"]);let a="",s="",o=!1;const d=(c,u)=>{o||(o=!0,clearTimeout(r),c?t(c):e(u))},r=setTimeout(()=>{killTree$1(n),d(new Error("diagnose timed out"))},2e4);n.stdout?.setEncoding("utf8"),n.stdout?.on("data",c=>{a+=c}),n.stderr?.setEncoding("utf8"),n.stderr?.on("data",c=>{s+=c}),n.on("error",c=>d(c)),n.on("close",()=>{const c=a.split(/\r?\n/).map(u=>u.trim()).filter(u=>u.startsWith("{")).at(-1);if(!c){d(new Error(s.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
+ `,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(
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),a=spawnPowershell$1(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",scriptFile$1(),"-TextFile",n,"-Rate",String(sapiRate(e.rate)),"-Voice",e.voice]);let s=!1;const o=()=>{try{node_fs.rmSync(n,{force:!0})}catch{}};return a.on("error",()=>{o(),s||t(!1)}),a.on("close",d=>{o(),s||t(d===0)}),{stop(){s=!0,killTree(a),o()}}}function listSapiVoices(){return new Promise(e=>{const t=spawnPowershell$1(["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-File",scriptFile$1(),"-ListVoices"]);let n="",a=!1;const s=d=>{a||(a=!0,clearTimeout(o),e(d))},o=setTimeout(()=>{killTree(t),s([])},1e4);t.stdout?.setEncoding("utf8"),t.stdout?.on("data",d=>{n+=d}),t.on("error",()=>s([])),t.on("close",()=>{const d=n.split(/\r?\n/).map(r=>r.trim()).filter(r=>r.length>0);s(d)})})}const WIN_H_SCRIPT=String.raw`$ErrorActionPreference = "Stop"
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"
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",a=>t(a)),n.on("close",a=>{a===0?e():t(new Error(`Win+H helper exited with code ${a??"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(a=>a.Locale.toLowerCase().startsWith("es-")).map(a=>({id:a.ShortName,label:`${a.Locale} · ${shortName(a)}`,locale:a.Locale,gender:a.Gender})).sort((a,s)=>a.label.localeCompare(s.label))}finally{t.close()}}async function synthesizeEdge(e){const{MsEdgeTTS:t,OUTPUT_FORMAT:n}=load(),a=new t;try{await a.setMetadata(e.voice,n.AUDIO_24KHZ_48KBITRATE_MONO_MP3);const{audioStream:s}=a.toStream(e.text,{rate:e.rate}),o=[];return await new Promise((d,r)=>{s.on("data",c=>o.push(Buffer.from(c))),s.on("end",()=>d(Buffer.concat(o).toString("base64"))),s.on("error",r)})}finally{a.close()}}const main=e=>{let t="idle",n="",a="",s,o=!1;const d=()=>{s=void 0,o=!1},r=e.provide(Dictation,{state:()=>({status:t,error:n,locale:a}),start(f,T){s&&(o=!0,s.stop(),d()),t="starting",n="",a=f.locale,r.changed();const S=T.window;try{s=startEngine({locale:f.locale,minConfidence:f.minConfidence},{onReady(h){t="listening",a=h,r.changed()},onFinal(h,w){r.emit("final",{text:h,confidence:w},S)},onError(h,w,$){t="error",n=h,r.changed(),r.emit("error",{code:h,detail:w,available:$},S)},onExit(h,w){if(o){d(),t="idle",n="",a="",r.changed();return}d(),t!=="error"&&(t="error",n=h===null?"ENGINE_EXIT":`ENGINE_EXIT_${h}`,r.changed(),r.emit("error",{code:"ENGINE_EXIT",detail:w||(h===null?"":String(h)),available:[]},S))}})}catch(h){throw t="error",n="ENGINE_START_FAILED",r.changed(),h}},stop(){s&&(o=!0,s.stop(),d()),t="idle",n="",a="",r.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(()=>{s&&(o=!0,s.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 a=n.split(new RegExp("(?<=[.!?…])\\s+")),s=[];let o="";const d=r=>{let c="";for(const u of r.split(/\s+/))u&&(c&&c.length+1+u.length>t?(s.push(c),c=u):c=c?`${c} ${u}`:u);return c};for(const r of a){if(r.length>t){o&&(s.push(o),o=""),o=d(r);continue}o&&o.length+1+r.length>t?(s.push(o),o=r):o=o?`${o} ${r}`:r}return o&&s.push(o),s}function createSpeaker(e,t){const n=e.stores.speech,a=e.stores.tts,[s,o]=solidJs.createSignal(!1),[d,r]=solidJs.createSignal(!1),[c,u]=solidJs.createSignal([]),[f,T]=solidJs.createSignal("none");let S=0,h=[],w="",$,N;const R=new Map,C=()=>typeof globalThis.speechSynthesis<"u"?globalThis.speechSynthesis:void 0,l=()=>{const b=C()?.getVoices()??[];if(b.length===0)return;const m=n.value.voiceURI;if(m){const E=b.find(P=>P.voiceURI===m);if(E)return E}const y=e.locale.locale().split("-")[0]?.toLowerCase()??"es";return b.find(E=>E.lang.toLowerCase().startsWith(y))??b[0]},p=async()=>{if(a.value.engine==="edge")try{const m=await t.edgeVoices();if(m.length>0){u(m.map(y=>({id:y.id,label:y.label}))),T("edge");return}}catch{}const b=C()?.getVoices()??[];if(b.length>0){u(b.map(m=>({id:m.voiceURI,label:`${m.name} — ${m.lang}`}))),T("web");return}try{const m=await t.voices();if(m.length>0){u(m.map(y=>({id:y,label:y}))),T("sapi");return}}catch{}T("none")},v=()=>{$&&($.onended=null,$.onerror=null,$.pause(),$=void 0),N&&(URL.revokeObjectURL(N),N=void 0)},k=()=>{S++,h=[],C()?.cancel(),v(),r(!1),o(!1),f()==="sapi"&&t.stop().catch(()=>{})},A=()=>f()==="web"||f()==="edge",i=()=>{if(!(!s()||d()||!A())){if(f()==="edge"){$?.pause(),r(!0);return}C()?.pause(),r(!0)}},g=()=>{if(!(!s()||!d()||!A())){if(f()==="edge"){$?.play().catch(()=>{}),r(!1);return}C()?.resume(),r(!1)}},D=()=>{d()?g():i()},O=()=>w?z(w):Promise.resolve(!1),U=b=>{const m=a.value.voice||"es-ES-XimenaNeural",y=n.value.rate,E=chunkForSpeech(b,4e3),P=`${m}|${y}|${b}`,L=R.get(P)??[];R.clear(),R.set(P,L);const I=S;o(!0),r(!1);const V=_=>{if(I===S){if(_>=E.length){r(!1),o(!1);return}(async()=>{let W=L[_];try{W||(W=await t.edgeSpeak({text:E[_]??"",voice:m,rate:y}),L[_]=W)}catch{I===S&&(r(!1),o(!1),e.toast.show({variant:"error",title:e.t("read.title"),description:e.t("error.edge")}));return}if(I!==S)return;const B=Uint8Array.from(atob(W),j=>j.charCodeAt(0)),H=URL.createObjectURL(new Blob([B],{type:"audio/mpeg"}));v(),N=H;const F=new Audio(H);$=F,F.onended=()=>{I===S&&(v(),V(_+1))},F.onerror=()=>{I===S&&(v(),r(!1),o(!1))},await F.play().catch(()=>{I===S&&(v(),r(!1),o(!1),e.toast.show({variant:"error",title:e.t("read.title"),description:e.t("error.edge")}))})})()}};return V(0),!0},z=async b=>{w=b;const m=cleanForSpeech(b,n.value.skipCode);if(!m)return!1;if(k(),await p(),f()==="edge")return U(m);if(f()==="web"){const y=C();if(!y)return!1;const E=l(),P=n.value.rate,L=S;h=chunkForSpeech(m),o(!0),r(!1);const I=()=>{if(L!==S)return;const V=h.shift();if(!V){r(!1),o(!1);return}const _=new SpeechSynthesisUtterance(V);E&&(_.voice=E),E?.lang&&(_.lang=E.lang),_.rate=P,_.onend=I,_.onerror=I,y.speak(_)};return I(),!0}if(f()==="sapi"){o(!0),r(!1);try{await t.speak({text:m,rate:n.value.rate,voice:n.value.voiceURI})}catch(y){throw o(!1),y}return!0}throw o(!1),new Error(e.t("read.noVoices"))},M=async()=>{const b=e.sessions.current();if(!b)return"";const y=(await b.server.client.message.list({sessionID:b.id,type:"assistant",order:"desc",limit:1})).data[0];return!y||y.type!=="assistant"?"":y.content.filter(E=>E.type==="text").map(E=>E.text).join(`
192
- `)};return t.on("done",()=>{r(!1),o(!1)}),C()?.addEventListener?.("voiceschanged",()=>{p()}),p(),{speaking:s,paused:d,backend:f,canPause:A,voices:c,refreshVoices:p,lastAssistantText:M,speak:z,cancel:k,pause:i,resume:g,togglePause:D,restart:O,preview:()=>z(e.t("speech.preview.text"))}}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,a=t.stores.prefs,s=t.stores.speech,o=t.stores.tts,d=t.stores.engine,r=t.main(Dictation),c=t.main(VoiceTyping),[u,f]=solidJs.createSignal([]),[T,S]=solidJs.createSignal(!1);solidJs.onMount(()=>{r.diagnose().then(i=>{i.locales.length>0&&f(i.locales)}).catch(()=>{}),e.speaker.refreshVoices()});const h=()=>{const i=u(),g=a.value.locale;return(i.includes(g)?i:[g,...i]).map(O=>({value:O,label:O}))},w=()=>{const i=e.speaker.voices().map(g=>({value:g.id,label:g.label}));return o.value.engine==="edge"?i:[{value:"",label:t.t("speech.voice.auto")},...i]},$=()=>o.value.engine==="edge"?o.value.voice:s.value.voiceURI,N=i=>{if(o.value.engine==="edge"){o.update(g=>{g.voice=i});return}C({voiceURI:i})},R=i=>{a.update(g=>{i.locale!==void 0&&(g.locale=i.locale),i.minConfidence!==void 0&&(g.minConfidence=i.minConfidence),i.trailingSpace!==void 0&&(g.trailingSpace=i.trailingSpace),i.autoFocus!==void 0&&(g.autoFocus=i.autoFocus)})},C=i=>{s.update(g=>{i.voiceURI!==void 0&&(g.voiceURI=i.voiceURI),i.rate!==void 0&&(g.rate=i.rate),i.skipCode!==void 0&&(g.skipCode=i.skipCode)})},l=i=>{d.update(g=>{g.mode=i})},p=i=>{o.update(g=>{g.engine=i}),e.speaker.refreshVoices()},v=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")})}},A=async()=>{S(!0);try{const i=await r.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]||a.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 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:()=>{v()},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 a.value.locale},get options(){return web.memo(()=>h().length>0)()?h():FALLBACK_LOCALES.map(i=>({value:i,label:i}))},onChange:i=>R({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(a.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})})}}),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 a.value.trailingSpace},onChange:i=>R({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 a.value.autoFocus},onChange:i=>R({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 o.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 w()},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(s.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=>C({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 s.value.skipCode},onChange:i=>C({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:()=>{A()},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),a=e.main(VoiceTyping),s=createSpeaker(e,n),o=e.stores.prefs,d=e.stores.engine,[r,c]=solidJs.createSignal(!1);let u;const f=(l,p,v)=>{switch(l){case"LANGUAGE_NOT_AVAILABLE":return e.t("error.language",{locale:p||o.value.locale,available:v.length>0?v.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=()=>{r()&&(c(!1),a.toggle().catch(()=>{})),s.cancel(),u=e.sessions.current()?.key,t.start({locale:o.value.locale,minConfidence:o.value.minConfidence}).catch(l=>{e.toast.show({variant:"error",title:e.t("error.title"),description:l instanceof Error?l.message:String(l)})})},S=()=>{t.stop().catch(()=>{})},h=()=>{s.cancel(),e.screen.current()?.composer.focus(),a.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")})})},w=()=>{if(d.value.mode==="windows"){h();return}const l=t.state()?.status??"idle";l==="starting"||l==="listening"?S():T()},$=()=>{if(s.speaking()){s.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 s.lastAssistantText();if(!p.trim()){e.toast.show({variant:"default",title:e.t("read.title"),description:e.t("read.empty")});return}await s.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(!s.speaking())return[{id:"start",label:e.t("read.idle"),icon:"outline-audio",run:$}];const l=s.paused(),p=[{id:"restart",label:e.t("read.restart"),icon:"outline-reset",run:()=>{s.restart()}}];return s.canPause()&&p.push({id:"pause",label:l?e.t("read.resume"):e.t("read.pause"),icon:"outline-audio",run:()=>s.togglePause()}),p.push({id:"stop",label:e.t("read.stop"),icon:"xmark-small",run:()=>s.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 v=e.screen.current()?.composer;if(!v)return;const k=v.text(),A=o.value.trailingSpace&&k.length>0&&!/\s$/.test(k)?" ":"";v.insert(A+l.text),o.value.autoFocus&&v.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 R=()=>{if(e.app.platform!=="desktop")return;if(d.value.mode==="windows")return r()?{label:e.t("pill.dictating"),icon:"outline-wave-audio",tone:"danger",title:e.t("pill.stop"),run:w}:{label:e.t("pill.idle"),icon:"outline-wave-audio",title:e.t("pill.winH"),run:w};const l=t.state()?.status??"idle";return l==="starting"?{label:e.t("pill.starting"),icon:"outline-wave-audio",title:e.t("pill.title"),run:w}:l==="listening"?{label:e.t("pill.listening"),icon:"outline-wave-audio",tone:"danger",title:e.t("pill.stop"),run:w}:l==="error"?{label:e.t("pill.error"),icon:"outline-wave-audio",tone:"warning",title:e.t("pill.title"),run:w}:{label:e.t("pill.idle"),icon:"outline-wave-audio",title:e.t("pill.title"),run:w}},C=()=>{if(e.app.platform!=="desktop")return;const l=s.speaking(),p=s.paused(),v=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:v.length>0?v:void 0,run:l?void 0:$}};e.add(gui$1.ComposerPill,()=>({id:"dictate",show:()=>R()})),e.add(gui$1.ComposerPill,()=>({id:"read",order:1,show:()=>C(),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:w})),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:s})})},gui=gui$1.Plugin.define({...definition,setup,main});exports.default=gui;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-voice-dictation",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
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>",
@@ -36,6 +36,7 @@ export function VoiceSettings(props: { speaker: Speaker }) {
36
36
  const [busy, setBusy] = createSignal(false)
37
37
 
38
38
  onMount(() => {
39
+ props.speaker.setConfig({ engine: ttsPrefs.value.engine, voice: ttsPrefs.value.voice })
39
40
  void dictation
40
41
  .diagnose()
41
42
  .then((result) => {
@@ -66,6 +67,7 @@ export function VoiceSettings(props: { speaker: Speaker }) {
66
67
  ttsPrefs.update((draft) => {
67
68
  draft.voice = value
68
69
  })
70
+ props.speaker.setConfig({ voice: value })
69
71
  return
70
72
  }
71
73
  patchSpeech({ voiceURI: value })
@@ -98,6 +100,7 @@ export function VoiceSettings(props: { speaker: Speaker }) {
98
100
  ttsPrefs.update((draft) => {
99
101
  draft.engine = engine
100
102
  })
103
+ props.speaker.setConfig({ engine })
101
104
  void props.speaker.refreshVoices()
102
105
  }
103
106
 
package/src/gui/speak.ts CHANGED
@@ -12,7 +12,10 @@ export interface Speaker {
12
12
  speaking(): boolean
13
13
  paused(): boolean
14
14
  backend(): "web" | "sapi" | "edge" | "none"
15
+ engine(): "edge" | "system"
16
+ voice(): string
15
17
  canPause(): boolean
18
+ setConfig(input: { engine?: string; voice?: string }): void
16
19
  voices(): VoiceOption[]
17
20
  refreshVoices(): Promise<void>
18
21
  lastAssistantText(): Promise<string>
@@ -106,7 +109,15 @@ export function createSpeaker(
106
109
  speech: IpcClient<typeof Speech.spec>,
107
110
  ): Speaker {
108
111
  const prefs = ctx.stores.speech
109
- const tts = ctx.stores.tts
112
+ const initial = ctx.stores.tts.value
113
+
114
+ const [engine, setEngine] = createSignal<"edge" | "system">(initial.engine === "edge" ? "edge" : "system")
115
+ const [voice, setVoice] = createSignal(initial.voice)
116
+
117
+ const setConfig = (input: { engine?: string; voice?: string }) => {
118
+ if (input.engine === "edge" || input.engine === "system") setEngine(input.engine)
119
+ if (input.voice !== undefined) setVoice(input.voice)
120
+ }
110
121
 
111
122
  const [speaking, setSpeaking] = createSignal(false)
112
123
  const [paused, setPaused] = createSignal(false)
@@ -138,17 +149,18 @@ export function createSpeaker(
138
149
  }
139
150
 
140
151
  const refreshVoices = async () => {
141
- if (tts.value.engine === "edge") {
152
+ if (engine() === "edge") {
142
153
  try {
143
154
  const list = await speech.edgeVoices()
144
155
  if (list.length > 0) {
145
156
  setVoices(list.map((voice) => ({ id: voice.id, label: voice.label })))
146
- setBackend("edge")
147
- return
148
157
  }
149
158
  } catch {
150
- // No internet or Edge changed: fall back to the system voices.
159
+ // Keep the current list. The error surfaces when speaking, never as a
160
+ // silent switch to the system voice.
151
161
  }
162
+ setBackend("edge")
163
+ return
152
164
  }
153
165
 
154
166
  const available = synth()?.getVoices() ?? []
@@ -236,10 +248,10 @@ export function createSpeaker(
236
248
  }
237
249
 
238
250
  const speakEdge = (cleaned: string) => {
239
- const voice = tts.value.voice || "es-ES-XimenaNeural"
251
+ const selected = voice() || "es-ES-XimenaNeural"
240
252
  const rate = prefs.value.rate
241
253
  const blocks = chunkForSpeech(cleaned, 4000)
242
- const key = `${voice}|${rate}|${cleaned}`
254
+ const key = `${selected}|${rate}|${cleaned}`
243
255
  const cached = edgeCache.get(key) ?? []
244
256
 
245
257
  edgeCache.clear()
@@ -262,7 +274,7 @@ export function createSpeaker(
262
274
 
263
275
  try {
264
276
  if (!encoded) {
265
- encoded = await speech.edgeSpeak({ text: blocks[index] ?? "", voice, rate })
277
+ encoded = await speech.edgeSpeak({ text: blocks[index] ?? "", voice: selected, rate })
266
278
  cached[index] = encoded
267
279
  }
268
280
  } catch {
@@ -326,7 +338,7 @@ export function createSpeaker(
326
338
  cancel()
327
339
  await refreshVoices()
328
340
 
329
- if (backend() === "edge") {
341
+ if (engine() === "edge") {
330
342
  return speakEdge(cleaned)
331
343
  }
332
344
  if (backend() === "web") {
@@ -410,15 +422,30 @@ export function createSpeaker(
410
422
  setSpeaking(false)
411
423
  })
412
424
 
413
- const engine = synth()
414
- engine?.addEventListener?.("voiceschanged", () => void refreshVoices())
425
+ const synthEngine = synth()
426
+ synthEngine?.addEventListener?.("voiceschanged", () => void refreshVoices())
415
427
  void refreshVoices()
416
428
 
429
+ const preview = async () => {
430
+ const started = await speak(ctx.t("speech.preview.text"))
431
+ if (started) {
432
+ const detail =
433
+ engine() === "edge"
434
+ ? `Edge · ${voice()}`
435
+ : `${backend()} · ${prefs.value.voiceURI || "auto"}`
436
+ ctx.toast.show({ variant: "default", title: ctx.t("read.title"), description: detail })
437
+ }
438
+ return started
439
+ }
440
+
417
441
  return {
418
442
  speaking,
419
443
  paused,
420
444
  backend,
445
+ engine,
446
+ voice,
421
447
  canPause,
448
+ setConfig,
422
449
  voices,
423
450
  refreshVoices,
424
451
  lastAssistantText,
@@ -428,6 +455,6 @@ export function createSpeaker(
428
455
  resume,
429
456
  togglePause,
430
457
  restart,
431
- preview: () => speak(ctx.t("speech.preview.text")),
458
+ preview,
432
459
  }
433
460
  }