aboutsummaryrefslogtreecommitdiff
path: root/modules/ext/voice.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-06-22 18:00:43 +0200
committerAgando <roduccomm@gmail.com>2026-06-22 18:00:43 +0200
commit6f6161562c5f1028256f3163083b665719741646 (patch)
treeccc9ab59e8433555557b6db8b6c1d4940b8652a4 /modules/ext/voice.js
parent82270d6f2685d84d5c9b4a454ae7260de75d34cf (diff)
auto: 2026-06-22_18:00 changes
Diffstat (limited to 'modules/ext/voice.js')
-rw-r--r--modules/ext/voice.js59
1 files changed, 59 insertions, 0 deletions
diff --git a/modules/ext/voice.js b/modules/ext/voice.js
index de0caed..b243ace 100644
--- a/modules/ext/voice.js
+++ b/modules/ext/voice.js
@@ -27,6 +27,65 @@ class VoiceModule {
}
registerCommands() {
this.commands['voice'] = (args) => this.handle(args);
+ this.commands['speak'] = (args) => this.speak(args);
+ }
+
+ getHelpInfo() {
+ return { 'voice': 'voice [stop] — Start voice recording (Whisper STT), or type "voice stop" to cancel', 'speak': 'speak [text] — Text-to-speech. No args = toggle auto-speak for AI responses' };
+ }
+
+ speak(args) {
+ const text = args.join(' ').trim();
+ if (!text) {
+ // If no text, check if speechSynthesis is available
+ if (typeof speechSynthesis === 'undefined') {
+ this.terminal.print('🔊 TTS not supported in this browser', 'error');
+ return;
+ }
+ // Toggle auto-speak mode
+ this._autoSpeak = !this._autoSpeak;
+ this.terminal.print(this._autoSpeak ? '🔊 Auto-speak ON — AI responses will be spoken' : '🔊 Auto-speak OFF', 'info');
+ return;
+ }
+ if (typeof speechSynthesis === 'undefined') {
+ this.terminal.print('🔊 TTS not supported in this browser', 'error');
+ return;
+ }
+ speechSynthesis.cancel();
+ const utter = new SpeechSynthesisUtterance(text);
+ utter.rate = parseFloat(localStorage.getItem('webx11_tts_rate') || '1.0');
+ utter.pitch = 1.0;
+ utter.volume = 1.0;
+ // Try to use a good English voice
+ const voices = speechSynthesis.getVoices();
+ const enVoice = voices.find(v => v.lang.startsWith('en') && v.name.includes('Google')) || voices.find(v => v.lang.startsWith('en'));
+ if (enVoice) utter.voice = enVoice;
+ utter.onstart = () => this._updateButton(true);
+ utter.onend = () => this._updateButton(false);
+ utter.onerror = () => this._updateButton(false);
+ speechSynthesis.speak(utter);
+ this.terminal.print('🔊 Speaking: "' + text.substring(0, 80) + (text.length > 80 ? '...' : '') + '"', 'info');
+ }
+
+ speakText(text) {
+ // Called by AI assistant to auto-speak responses
+ if (!this._autoSpeak || typeof speechSynthesis === 'undefined') return;
+ speechSynthesis.cancel();
+ // Strip markdown/code blocks for cleaner speech
+ const clean = text.replace(/```[\s\S]*?```/g, ' code block ').replace(/[*_#|`>]/g, '').replace(/\n+/g, '. ').trim();
+ if (clean.length > 500) {
+ // Split long text into chunks
+ const chunks = clean.match(/.{1,500}/g);
+ chunks.forEach(c => {
+ const u = new SpeechSynthesisUtterance(c);
+ u.rate = parseFloat(localStorage.getItem('webx11_tts_rate') || '1.0');
+ speechSynthesis.speak(u);
+ });
+ } else {
+ const u = new SpeechSynthesisUtterance(clean);
+ u.rate = parseFloat(localStorage.getItem('webx11_tts_rate') || '1.0');
+ speechSynthesis.speak(u);
+ }
}
handle(args) {
const sub = args[0] && args[0].toLowerCase();