// DOM references needed for voice assistant const transcriptContainer = document.getElementById('transcript-container'); const transcriptText = document.getElementById('transcript-text'); const toggle = document.getElementById('toggle'); // Voice assistant state variables let isProcessingAPI = false; let speechSynthesis = window.speechSynthesis; let speaking = false; let recognition = null; let silenceTimeout = null; let isFinalResult = false; let isListening = false; // Speech Recognition Implementation function initializeSpeechRecognition() { if (!('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)) { console.error('Speech recognition not supported in this browser'); return null; } const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; const newRecognition = new SpeechRecognition(); newRecognition.continuous = true; newRecognition.interimResults = true; newRecognition.maxAlternatives = 1; newRecognition.lang = 'en-US'; return newRecognition; } function startListening() { // Don't start if already listening, toggle is off, or API is processing if (isListening || !toggle.checked || isProcessingAPI) return; try { if (recognition) { recognition.stop(); } recognition = initializeSpeechRecognition(); if (!recognition) { console.error('Could not initialize speech recognition'); return; } // Show transcript container transcriptContainer.style.opacity = '1'; transcriptText.textContent = 'Listening...'; // Set up event handlers recognition.onstart = function() { isListening = true; setEmotion('listening'); console.log('Speech recognition started'); }; recognition.onresult = function(event) { clearTimeout(silenceTimeout); const lastResult = event.results[event.results.length - 1]; const transcript = lastResult[0].transcript.trim(); isFinalResult = lastResult.isFinal; transcriptText.textContent = transcript; // If it's a final result, process it if (isFinalResult) { processFinalTranscript(transcript); } else { // Set a timeout for silence detection (2 seconds) silenceTimeout = setTimeout(() => { processFinalTranscript(transcript); }, 2000); } }; recognition.onerror = function(event) { console.error('Speech recognition error:', event.error); if (event.error === 'no-speech') { // No speech detected, continue listening restartRecognition(); } else if (event.error === 'aborted' || event.error === 'network') { stopListening(); } }; recognition.onend = function() { // If ended but we still want to be listening, restart if (isListening && toggle.checked && !isProcessingAPI) { restartRecognition(); } else { transcriptContainer.style.opacity = '0'; setTimeout(() => { if (!isListening) { transcriptText.textContent = ''; } }, 300); setEmotion('default'); } }; recognition.onspeechend = function() { // When speech ends but recognition is still active console.log('Speech ended, still listening for more'); // Check if there's transcript to process const transcript = transcriptText.textContent; if (transcript && transcript !== 'Listening...') { processFinalTranscript(transcript); } }; recognition.start(); } catch (error) { console.error('Failed to start speech recognition:', error); isListening = false; } } function stopListening() { if (recognition) { isListening = false; recognition.stop(); setEmotion('default'); console.log('Speech recognition stopped'); // Hide transcript transcriptContainer.style.opacity = '0'; setTimeout(() => { transcriptText.textContent = ''; }, 300); } } function restartRecognition() { if (isListening && toggle.checked && !isProcessingAPI) { setTimeout(() => { try { recognition.start(); } catch (error) { console.error('Error restarting recognition:', error); isListening = false; setEmotion('default'); } }, 200); } } function processFinalTranscript(transcript) { if (!transcript || transcript === 'Listening...') return; console.log('Processing final transcript:', transcript); // Set the processing flag to true - this indicates we're calling the API isProcessingAPI = true; // Stop listening since we're now processing if (isListening) { stopListening(); } // Cancel any ongoing speech if (speaking) { speechSynthesis.cancel(); speaking = false; } // Send the transcript to the API query({ question: transcript }) .then(response => { console.log('API response:', response); // Display the response transcriptText.textContent = response.text; // Speak the response using text-to-speech speakResponse(response.text); // Reset the processing flag when done isProcessingAPI = false; // Clear transcript after the speech ends or after a delay if speech fails setTimeout(() => { if (!isListening && !speaking) { transcriptContainer.style.opacity = '0'; setTimeout(() => { transcriptText.textContent = ''; }, 300); } }, 5000); }) .catch(error => { console.error('API error:', error); transcriptText.textContent = 'Sorry, there was an error processing your request.'; speakResponse('Sorry, there was an error processing your request.'); setEmotion('default'); // Reset the processing flag even on error isProcessingAPI = false; }); } // Text-to-speech function function speakResponse(text) { // Don't attempt to speak if speech synthesis is not available if (!speechSynthesis) { console.error('Speech synthesis not supported in this browser'); setEmotion('default'); return; } // Create a new speech synthesis utterance const utterance = new SpeechSynthesisUtterance(text); // Optional: Set voice, rate, pitch, etc. utterance.rate = 1.0; // Speech rate (0.1 to 10) utterance.pitch = 1.0; // Speech pitch (0 to 2) utterance.volume = 1.0; // Speech volume (0 to 1) // Optionally select a specific voice // Get all available voices const voices = speechSynthesis.getVoices(); // You can choose a specific voice if available // Find a voice that sounds good - preferably female and English const preferredVoice = voices.find(voice => voice.name.includes('Female') && (voice.lang.includes('en-US') || voice.lang.includes('en-GB')) ) || voices[0]; // Fallback to first available voice if (preferredVoice) { utterance.voice = preferredVoice; } // Event listeners utterance.onstart = () => { speaking = true; setEmotion('listening'); // Reuse the listening animation for speaking console.log('Speech started'); }; utterance.onend = () => { speaking = false; setEmotion('default'); console.log('Speech ended'); // Hide transcript if not listening if (!isListening) { setTimeout(() => { transcriptContainer.style.opacity = '0'; setTimeout(() => { transcriptText.textContent = ''; }, 300); }, 1000); } }; utterance.onerror = (event) => { speaking = false; setEmotion('default'); console.error('Speech synthesis error:', event); }; // Start speaking speechSynthesis.speak(utterance); } // API query function async function query(data) { try { const response = await fetch( "https://srivatsavdamaraju-flowise.hf.space/api/v1/prediction/2875301a-c26f-4bd5-ab10-71fa13393541", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) } ); if (!response.ok) { throw new Error(`API responded with status: ${response.status}`); } const result = await response.json(); return result; } catch (error) { console.error('API query error:', error); throw error; } } // Connect listen button functionality const listenButton = document.querySelector('button[onclick="setEmotion(\'listening\')"]'); if (listenButton) { listenButton.addEventListener('click', () => { // Don't allow starting listening if we're processing an API call if (!isListening && !isProcessingAPI) { startListening(); } else { stopListening(); } }); } // Connect toggle button to speech recognition toggle.addEventListener('change', function() { if (toggle.checked) { // When turning on, request microphone permission navigator.mediaDevices.getUserMedia({ audio: true }) .then(() => { console.log('Microphone permission granted'); }) .catch(error => { console.error('Microphone permission denied:', error); }); } else { // When turning off, stop listening if active if (isListening) { stopListening(); } } }); // Ensure voices are loaded speechSynthesis.onvoiceschanged = () => console.log('Voices loaded:', speechSynthesis.getVoices().length);