From 4baa484b84c5184286cac2743575251454a5ee2b Mon Sep 17 00:00:00 2001 From: Agando Date: Mon, 25 May 2026 18:02:02 +0200 Subject: auto: 2026-05-25_18:02 changes --- modules/ext/voice.js | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 modules/ext/voice.js (limited to 'modules/ext/voice.js') 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'); +} -- cgit v1.2.3