diff options
| author | Agando <roduccomm@gmail.com> | 2026-05-25 18:02:02 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-05-25 18:02:02 +0200 |
| commit | 4baa484b84c5184286cac2743575251454a5ee2b (patch) | |
| tree | cb9002f93644f363d0f35831dc97e6492bbb0ed6 /modules/ext/voice.js | |
auto: 2026-05-25_18:02 changes
Diffstat (limited to 'modules/ext/voice.js')
| -rw-r--r-- | modules/ext/voice.js | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/modules/ext/voice.js b/modules/ext/voice.js new file mode 100644 index 0000000..0fd96cd --- /dev/null +++ b/modules/ext/voice.js @@ -0,0 +1,102 @@ +class VoiceModule { + constructor() { + this.commands = {}; + this.terminal = null; + this.recognition = null; + this.listening = false; + this._lastError = ''; + } + requirements() { + if (typeof SpeechRecognition !== 'undefined' || typeof webkitSpeechRecognition !== 'undefined') { + return { met: true, missing: [] }; + } + return { met: false, missing: ['SpeechRecognition not available in this browser'] }; + } + getHelpInfo() { + return { 'voice': 'voice [stop] — Start voice recognition, or type "voice stop" to cancel' }; + } + registerCommands() { + this.commands['voice'] = (args) => this.handle(args); + } + handle(args) { + const sub = args[0] && args[0].toLowerCase(); + if (sub === 'stop') return this.stopListening(); + if (this.listening) return this.terminal.print('Already listening. Say something or type "voice stop"', 'warning'); + this.startListening(); + } + _updateButton(listening, error) { + const btn = document.getElementById('voice-toggle-btn'); + if (!btn) return; + if (listening) { + btn.style.borderColor = 'var(--success-color)'; + btn.style.color = 'var(--success-color)'; + btn.style.animation = 'pulse 1.5s ease-in-out infinite'; + btn.textContent = '🔴 Listening'; + } else { + btn.style.borderColor = ''; + btn.style.color = ''; + btn.style.animation = ''; + if (error) { + btn.style.borderColor = 'var(--error-color)'; + btn.style.color = 'var(--error-color)'; + } else { + btn.style.borderColor = ''; + btn.style.color = ''; + } + btn.textContent = '🎤 Mic'; + } + } + startListening() { + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + if (!SpeechRecognition) { + this._updateButton(false, true); + return this.terminal.print('Speech recognition not supported in this browser', 'error'); + } + this.recognition = new SpeechRecognition(); + this.recognition.lang = 'en-US'; + this.recognition.interimResults = false; + this.recognition.maxAlternatives = 1; + this.recognition.continuous = false; + this.listening = true; + this._updateButton(true); + this.terminal.print('🎤 Listening... (say a command, or tap mic button to cancel)', 'info'); + this.recognition.onresult = (event) => { + const transcript = event.results[0][0].transcript; + this.terminal.print(`🎤 Recognized: "${transcript}"`, 'success'); + const input = this.terminal.inputField; + input.value = transcript; + input.focus(); + this.listening = false; + this._updateButton(false); + }; + this.recognition.onerror = (event) => { + this.terminal.print(`🎤 Error: ${event.error}`, 'error'); + this.listening = false; + this._updateButton(false, true); + }; + this.recognition.onend = () => { + if (this.listening) { + this.listening = false; + this._updateButton(false); + this.terminal.print('🎤 Voice input ended', 'info'); + } + }; + try { this.recognition.start(); } + catch (e) { + this.terminal.print(`🎤 Failed to start: ${e.message}`, 'error'); + this.listening = false; + this._updateButton(false, true); + } + } + stopListening() { + if (this.recognition && this.listening) { + this.recognition.stop(); + this.listening = false; + this._updateButton(false); + this.terminal.print('🎤 Listening stopped', 'info'); + } + } +} +if (typeof window.webx11 !== 'undefined' && window.webx11) { + webx11.moduleManager.registerModule(new VoiceModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/voice.js'); +} |