1jQuery(document).ready(function($) { 2 const fields = [ 3 { id: 'your-name', selector: 'input[name="your-name"]', label: bhashiniAjax.nameFieldError }, 4 { id: 'your-message', selector: 'textarea[name="your-message"]', label: bhashiniAjax.messageFieldError } 5 ]; 6 7 fields.forEach(field => { 8 const $input = $(field.selector); 9 if ($input.length) { 10 $input.wrap('<div class="form-group" style="position: relative; margin-bottom: 15px;"></div>'); 11 $input.after(` 12 <button type="button" 13 title="${field.label}" 14 aria-label="${field.label}" 15 class="mic-button" 16 data-target="${field.id}" 17 style="position: absolute; right: 0px; top: 0; padding: 0; width: 32px; height: 30px; line-height: 32px; background: none; border: none; cursor: pointer; font-size: 16px;"> 18 <i class="fa fa-microphone"></i> 19 </button> 20 `); 21 } 22 }); 23 24 const overlay = $('#voice-fullscreen-overlay'); 25 const voiceMsg = $('.voice-message'); 26 const voiceClose = $('.voice-close'); 27 let mediaRecorder = null; 28 let audioChunks = []; 29 let recordingTimeout = null; 30 let targetInput = null; 31 let mediaStream = null; 32 33 function trapFocus(element) { 34 const focusableEls = element.find('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); 35 const firstFocusableEl = focusableEls[0]; 36 const lastFocusableEl = focusableEls[focusableEls.length - 1]; 37 38 element.off('keydown.trapFocus').on('keydown.trapFocus', function(e) { 39 if (e.key === 'Tab' || e.keyCode === 9) { 40 if (e.shiftKey) { 41 if (document.activeElement === firstFocusableEl) { 42 e.preventDefault();
43 lastFocusableEl && lastFocusableEl.focus(); 44 } 45 } else { 46 if (document.activeElement === lastFocusableEl) { 47 e.preventDefault(); 48 firstFocusableEl && firstFocusableEl.focus(); 49 } 50 } 51 } 52 }); 53 54 if (firstFocusableEl) { 55 setTimeout(() => firstFocusableEl.focus(), 100); 56 } 57 } 58 59 $('.mic-button').on('click', function() { 60 const targetId = $(this).data('target'); 61 const field = fields.find(f => f.id === targetId); 62 targetInput = field ? $(field.selector) : null; 63 64 if (!targetInput || !targetInput.length) { 65 console.error('Target input not found for ID:', targetId); 66 return; 67 } 68 69 overlay.fadeIn(200, function() { 70 // overlay.attr('tabindex', '0'); 71 // overlay.attr({ 72 // 'role': 'dialog', 73 // 'aria-modal': 'true', 74 // 'tabindex': '0' 75 // }); 76 trapFocus(overlay); 77 }); 78 79 voiceMsg.text(bhashiniAjax.speak_now_text); 80 startRecording(); 81 }); 82 83 voiceClose.on('click', function() { 84 stopRecording(true); 85 overlay.fadeOut(200); 86 if (targetInput) targetInput.focus(); 87 targetInput = null; 88 }); 89 $(document).on('keydown', function(e) { 90 if (e.key === 'Escape' && overlay.is(':visible')) { 91 voiceClose.trigger('click'); 92 } 93 }); 94 95 function startRecording() { 96 let mimeType = 'audio/webm'; 97 if (MediaRecorder.isTypeSupported('audio/wav')) { 98 mimeType = 'audio/wav'; 99 } else if (MediaRecorder.isTypeSupported('audio/webm')) { 100 mimeType = 'audio/webm'; 101 } else { 102 console.error('No supported audio format (WAV or WebM) found.'); 103 voiceMsg.text('Your browser does not support required audio formats.'); 104 setTimeout(() => { 105 overlay.fadeOut(300); 106 targetInput = null; 107 }, 2000); 108 return; 109 } 110 111 const language = localStorage.getItem('preferredLanguage') || 'en'; 112 // console.log('Selected language:', language); 113 114 navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { 115 mediaStream = stream; 116 mediaRecorder = new MediaRecorder(stream, { mimeType: mimeType }); 117 audioChunks = []; 118 119 setTimeout(() => { 120 voiceMsg.text(bhashiniAjax.listening_text); 121 }, 1000); 122 123 mediaRecorder.ondataavailable = e => { 124 audioChunks.push(e.data); 125 }; 126 127 mediaRecorder.onstop = () => { 128 clearTimeout(recordingTimeout); 129 stopMediaStream(); 130 131 const audioBlob = new Blob(audioChunks, { type: mimeType }); 132 const reader = new FileReader(); 133 reader.readAsDataURL(audioBlob); 134 135 reader.onloadend = () => { 136 const base64Audio = reader.result.split(',')[1]; 137 const audioFormat = mimeType.includes('wav') ? 'wav' : 'webm'; 138 139 $.ajax({ 140 url: bhashiniAjax.ajaxurl, 141 type: 'POST', 142 data: { 143 action: 'bhashini_asr', 144 audio_content: base64Audio, 145 language: language, 146 audio_format: audioFormat 147 }, 148 success: (response) => { 149 let text = response?.data?.text?.trim()?.toLowerCase(); 150 const excludePhrases = [ 151 "close voice search dialogue button listening", 152 "this page is accessing your microphone change the setting", 153 "this page is accessing your microphone change this", 154 "listening", 155 "speak now", 156 "search my voice button, dialogue speak now, listening", 157 "firefox sharing indicator alert", 158 "dialogue box", 159 "dialogue speed now listening okay query", 160 "you", 161 "firebox sharing educator alert", 162 "close button", 163 "dialogue speed now listening", 164 "dialogue speed, listen", 165 "dialogue's too pale listen", 166 "dialogue speak" 167 ]; 168
169 const isExcluded = excludePhrases.some(phrase => text.includes(phrase)); 170 171 if (response.success && text && !isExcluded) { 172 if (targetInput.attr('name') === 'your-name') { 173 text = text.replace(/\./g, ''); 174 targetInput.val(text).focus(); 175 } else if (targetInput.attr('name') === 'your-message') { 176 const currentText = targetInput.val(); 177 const newText = currentText ? currentText + ' ' + text : text; 178 targetInput.val(newText).focus(); 179 } 180 setTimeout(() => { 181 overlay.fadeOut(1000); 182 targetInput = null; 183 }, 1000); 184 } else { 185 console.warn('Text excluded (screen reader or invalid):', text); 186 voiceMsg.text(bhashiniAjax.client_error); 187 setTimeout(() => { 188 overlay.fadeOut(300); 189 targetInput = null; 190 }, 2000); 191 } 192 }, 193 error: (xhr, status, error) => { 194 console.error('AJAX error details:', { 195 status: status, 196 error: error, 197 responseText: xhr.responseText, 198 statusCode: xhr.status, 199 url: bhashiniAjax.ajaxurl, 200 headers: xhr.getAllResponseHeaders() 201 }); 202 voiceMsg.text(bhashiniAjax.server_error_text); 203 setTimeout(() => { 204 overlay.fadeOut(300); 205 targetInput = null; 206 }, 2000); 207 } 208 }); 209 }; 210 }; 211 212 mediaRecorder.start(); 213 const timeoutDuration = targetInput.attr('name') === 'your-name' ? 2000 : 5000; 214 recordingTimeout = setTimeout(() => { 215 if (mediaRecorder.state !== 'inactive') { 216 mediaRecorder.stop(); 217 } 218 }, timeoutDuration); 219 }).catch((err) => { 220 console.error('Microphone error:', err); 221 voiceMsg.text(bhashiniAjax.no_mic_detected); 222 setTimeout(() => { 223 overlay.fadeOut(300); 224 targetInput = null; 225 }, 2000); 226 }); 227 } 228 229 function stopRecording(cancel = false) { 230 if (mediaRecorder && mediaRecorder.state !== 'inactive') { 231 mediaRecorder.stop(); 232 } 233 if (cancel) { 234 audioChunks = []; 235 clearTimeout(recordingTimeout); 236 } 237 stopMediaStream(); 238 } 239 240 function stopMediaStream() { 241 if (mediaStream) { 242 mediaStream.getTracks().forEach(track => { 243 track.stop(); 244 }); 245 mediaStream = null; 246 } 247 } 248});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.