class ChatBot { constructor() { this.voiceEnabled = false; this.isListening = false; this.synthesis = window.speechSynthesis; this.recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)(); this.currentUtterance = null; this.isPaused = false; this.audioQueue = []; this.mediaRecorder = null; this.audioChunks = []; this.isRecording = false; this.setupRecognition(); this.setupEventListeners(); } setupRecognition() { this.recognition.continuous = true; this.recognition.interimResults = true; this.recognition.lang = 'en-US'; this.recognition.onstart = () => { this.isListening = true; this.toggleVoiceInputClass(true); }; this.recognition.onend = () => { this.isListening = false; this.toggleVoiceInputClass(false); }; this.recognition.onerror = (event) => { console.error('Speech recognition error:', event.error); this.isListening = false; this.toggleVoiceInputClass(false); }; this.recognition.onresult = (event) => { let finalTranscript = ''; let interimTranscript = ''; for (let i = event.resultIndex; i < event.results.length; i++) { const transcript = event.results[i][0].transcript; if (event.results[i].isFinal) { finalTranscript += transcript + ' '; } else { interimTranscript += transcript; } } const input = document.getElementById('messageInput'); input.value = finalTranscript + interimTranscript; }; } setupEventListeners() { // Send button click event document.getElementById('sendMessage').addEventListener('click', () => this.handleSendMessage()); // Voice input button document.getElementById('voiceInput').addEventListener('mousedown', () => { this.startRecording(); }); document.getElementById('voiceInput').addEventListener('mouseup', () => { this.stopRecording(); }); // Input field key events document.getElementById('messageInput').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.handleSendMessage(); } }); // Message speaker button click event document.addEventListener('click', (e) => { if (e.target.closest('.message-speaker')) { const messageContent = e.target.closest('.message-content'); const text = messageContent.textContent; if (this.isPaused) { this.resumeSpeaking(); } else { this.stopSpeaking(); this.speak(text); } } }); } async startRecording() { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); this.mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' // Use webm format for better compatibility }); this.audioChunks = []; this.isRecording = true; this.mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) { this.audioChunks.push(event.data); } }; this.mediaRecorder.onstop = async () => { const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' }); await this.sendAudioToServer(audioBlob); stream.getTracks().forEach(track => track.stop()); }; this.mediaRecorder.start(); this.toggleVoiceInputClass(true); } catch (error) { console.error('Error starting recording:', error); } } stopRecording() { if (this.mediaRecorder && this.isRecording) { this.mediaRecorder.stop(); this.isRecording = false; this.toggleVoiceInputClass(false); } } async sendAudioToServer(audioBlob) { const formData = new FormData(); formData.append('audio', audioBlob, 'recording.webm'); try { const response = await fetch('/api/voice', { method: 'POST', body: formData }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Failed to process audio'); } if (data.text) { document.getElementById('messageInput').value = data.text; } if (data.response) { this.addMessage(data.response, 'bot'); if (this.voiceEnabled) { this.speak(data.response); } } } catch (error) { console.error('Error sending audio:', error); this.addMessage(`Error: ${error.message}`, 'bot'); } } handleSendMessage() { const input = document.getElementById('messageInput'); const message = input.value.trim(); if (message) { this.stopSpeaking(); this.sendMessage(message); input.value = ''; } } async sendMessage(message) { this.addMessage(message, 'user'); this.showTypingIndicator(); try { const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message }) }); if (!response.ok) throw new Error('Failed to send message'); const data = await response.json(); this.removeTypingIndicator(); this.addMessage(data.response, 'bot'); if (this.voiceEnabled) { this.speak(data.response); } } catch (error) { console.error('Error:', error); this.removeTypingIndicator(); this.addMessage('Sorry, there was an error processing your request.', 'bot'); } } speak(text) { if (this.synthesis.speaking) { this.synthesis.cancel(); } const utterance = new SpeechSynthesisUtterance(text); this.currentUtterance = utterance; utterance.onend = () => { this.currentUtterance = null; this.isPaused = false; if (this.audioQueue.length > 0) { const nextText = this.audioQueue.shift(); this.speak(nextText); } }; this.synthesis.speak(utterance); } stopSpeaking() { if (this.synthesis.speaking) { this.synthesis.cancel(); this.currentUtterance = null; this.isPaused = false; this.audioQueue = []; } } toggleVoiceInputClass(isActive) { const button = document.getElementById('voiceInput'); button.classList.toggle('active', isActive); } addMessage(message, sender) { const messagesContainer = document.getElementById('chatMessages'); const messageDiv = document.createElement('div'); messageDiv.className = `message ${sender}`; const content = document.createElement('div'); content.className = 'message-content'; content.textContent = message; if (sender === 'bot') { const speakerButton = document.createElement('button'); speakerButton.className = 'message-speaker'; speakerButton.innerHTML = ''; content.appendChild(speakerButton); } messageDiv.appendChild(content); messagesContainer.appendChild(messageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; } showTypingIndicator() { const indicator = document.createElement('div'); indicator.className = 'message bot typing-indicator'; indicator.innerHTML = '
'.repeat(3); document.getElementById('chatMessages').appendChild(indicator); } removeTypingIndicator() { const indicator = document.querySelector('.typing-indicator'); if (indicator) indicator.remove(); } } document.addEventListener('DOMContentLoaded', () => { window.chatBot = new ChatBot(); });