mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			111 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			111 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // Borrowed from Agnai (AGPLv3)
 | |
| // https://github.com/agnaistic/agnai/blob/dev/web/pages/Chat/components/SpeechRecognitionRecorder.tsx
 | |
| function capitalizeInterim(interimTranscript) {
 | |
|     let capitalizeIndex = -1;
 | |
|     if (interimTranscript.length > 2 && interimTranscript[0] === ' ') capitalizeIndex = 1;
 | |
|     else if (interimTranscript.length > 1) capitalizeIndex = 0;
 | |
|     if (capitalizeIndex > -1) {
 | |
|         const spacing = capitalizeIndex > 0 ? ' '.repeat(capitalizeIndex - 1) : '';
 | |
|         const capitalized = interimTranscript[capitalizeIndex].toLocaleUpperCase();
 | |
|         const rest = interimTranscript.substring(capitalizeIndex + 1);
 | |
|         interimTranscript = spacing + capitalized + rest;
 | |
|     }
 | |
|     return interimTranscript;
 | |
| }
 | |
| 
 | |
| function composeValues(previous, interim) {
 | |
|     let spacing = '';
 | |
|     if (previous.endsWith('.')) spacing = ' ';
 | |
|     return previous + spacing + interim;
 | |
| }
 | |
| 
 | |
| (function ($) {
 | |
|     $.fn.speechRecognitionPlugin = function (options) {
 | |
|         const settings = $.extend({
 | |
|             grammar: '' // Custom grammar
 | |
|         }, options);
 | |
| 
 | |
|         const speechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
 | |
|         const speechRecognitionList = window.SpeechGrammarList || window.webkitSpeechGrammarList;
 | |
| 
 | |
|         if (!speechRecognition) {
 | |
|             console.warn('Speech recognition is not supported in this browser.');
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         const recognition = new speechRecognition();
 | |
| 
 | |
|         if (settings.grammar && speechRecognitionList) {
 | |
|             speechRecognitionList.addFromString(settings.grammar, 1);
 | |
|             recognition.grammars = speechRecognitionList;
 | |
|         }
 | |
| 
 | |
|         recognition.continuous = true;
 | |
|         recognition.interimResults = true;
 | |
|         // TODO: This should be configurable.
 | |
|         recognition.lang = 'en-US'; // Set the language to English (US).
 | |
| 
 | |
|         const $textarea = this;
 | |
|         const $button = $('<div class="fa-solid fa-microphone speech-toggle" title="Click to speak"></div>');
 | |
|         $('#send_but_sheld').prepend($button);
 | |
| 
 | |
|         let listening = false;
 | |
|         $button.on('click', function () {
 | |
|             if (listening) {
 | |
|                 recognition.stop();
 | |
|             } else {
 | |
|                 recognition.start();
 | |
|             }
 | |
|             listening = !listening;
 | |
|         });
 | |
| 
 | |
|         let initialText = '';
 | |
| 
 | |
|         recognition.onresult = function (speechEvent) {
 | |
|             let finalTranscript = '';
 | |
|             let interimTranscript = ''
 | |
| 
 | |
|             for (let i = speechEvent.resultIndex; i < speechEvent.results.length; ++i) {
 | |
|               const transcript = speechEvent.results[i][0].transcript;
 | |
| 
 | |
|               if (speechEvent.results[i].isFinal) {
 | |
|                 let interim = capitalizeInterim(transcript);
 | |
|                 if (interim != '') {
 | |
|                   let final = finalTranscript;
 | |
|                   final = composeValues(final, interim) + '.';
 | |
|                   finalTranscript = final;
 | |
|                   recognition.abort();
 | |
|                   listening = false;
 | |
|                 }
 | |
|                 interimTranscript = ' ';
 | |
|               } else {
 | |
|                 interimTranscript += transcript;
 | |
|               }
 | |
|             }
 | |
| 
 | |
|             interimTranscript = capitalizeInterim(interimTranscript);
 | |
| 
 | |
|             $textarea.val(initialText + finalTranscript + interimTranscript);
 | |
|         };
 | |
| 
 | |
|         recognition.onerror = function (event) {
 | |
|             console.error('Error occurred in recognition:', event.error);
 | |
|         };
 | |
| 
 | |
|         recognition.onend = function () {
 | |
|             listening = false;
 | |
|             $button.toggleClass('fa-microphone fa-microphone-slash');
 | |
|         };
 | |
| 
 | |
|         recognition.onstart = function () {
 | |
|             initialText = $textarea.val();
 | |
|             $button.toggleClass('fa-microphone fa-microphone-slash');
 | |
|         };
 | |
|     };
 | |
| }(jQuery));
 | |
| 
 | |
| jQuery(() => {
 | |
|     const $textarea = $('#send_textarea');
 | |
|     $textarea.speechRecognitionPlugin();
 | |
| });
 |