TalkGPT

enjoy Hands-Free Communication with ChatGPT

  1. // ==UserScript==
  2. // @name TalkGPT
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2
  5. // @description enjoy Hands-Free Communication with ChatGPT
  6. // @author temberature@mail.com
  7. // @include https://chat.openai.com/chat*
  8. // @grant none
  9. // @license GNU GPLv3
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. addTalkBtn();
  14. let VoiceList;
  15.  
  16. if (typeof speechSynthesis !== 'undefined' && speechSynthesis.onvoiceschanged !== undefined) {
  17. speechSynthesis.onvoiceschanged = generateVoiceList;
  18. }
  19. let voice = "Microsoft Aria Online (Natural) - English (United States)", lang = "en-US";
  20. const observer = new MutationObserver(mutations => {
  21. mutations.forEach(mutation => {
  22. if (mutation.type === 'childList') {
  23. mutation.addedNodes.forEach(addedNode => {
  24. if (addedNode.nodeType === Node.ELEMENT_NODE && ((addedNode.classList.contains('overflow-hidden') && addedNode.classList.contains('w-full') && addedNode.classList.contains('h-full') && addedNode.classList.contains('relative')) || addedNode.classList.contains('px-2') &&
  25. addedNode.classList.contains('py-10') &&
  26. addedNode.classList.contains('relative') &&
  27. addedNode.classList.contains('w-full') &&
  28. addedNode.classList.contains('flex') &&
  29. addedNode.classList.contains('flex-col') &&
  30. addedNode.classList.contains('h-full') &&
  31. addedNode.classList.contains('list')) ) {
  32.  
  33.  
  34. addTalkBtn();
  35. let stretchElement = document.querySelector('.stretch');
  36. let justifyCenterDiv = stretchElement.querySelector('.justify-center');
  37. justifyCenterDiv.appendChild(VoiceList);
  38.  
  39. }
  40.  
  41. if (addedNode.nodeType === Node.ELEMENT_NODE && addedNode.classList.contains('btn') && addedNode.classList.contains('flex') && addedNode.classList.contains('justify-center') && addedNode.classList.contains('gap-2') && addedNode.classList.contains('btn-neutral') && addedNode.classList.contains('border-0') && addedNode.classList.contains('md:border') && addedNode.textContent.includes('Regenerate response')) {
  42. const proseElements = document.querySelectorAll('.prose');
  43. const lastProseElement = proseElements[proseElements.length - 1];
  44.  
  45. window.utterances = [];
  46. const msg = new SpeechSynthesisUtterance(lastProseElement.textContent);
  47. utterances.push(msg);
  48. msg.voice = window.speechSynthesis.getVoices().find(v => {
  49. return v.name === voice;
  50. });
  51. msg.onend = () => {
  52. recognize();
  53. };
  54. window.speechSynthesis.speak(msg);
  55. }
  56. });
  57. }
  58. });
  59. });
  60.  
  61. observer.observe(document.body, { childList: true, subtree: true });
  62.  
  63.  
  64. function recognize() {
  65. const recognition = new webkitSpeechRecognition();
  66. recognition.interimResults = true;
  67. recognition.lang = lang; // Set the language to Mandarin Chinese
  68.  
  69. recognition.start();
  70.  
  71. let transcript = '';
  72.  
  73. recognition.onresult = (event) => {
  74. for (let i = event.resultIndex; i < event.results.length; i++) {
  75. if (event.results[i].isFinal) {
  76. transcript += event.results[i][0].transcript;
  77. }
  78. }
  79. };
  80.  
  81. recognition.onend = () => {
  82. if(!transcript){
  83. return;
  84. }
  85. const textarea = document.querySelector('textarea');
  86. textarea.value = textarea.value + "" + transcript;
  87.  
  88. // Find the sibling button and click it
  89. const button = textarea.nextElementSibling;
  90. button.click();
  91. };
  92. }
  93. function generateVoiceList() {
  94. let stretchElement = document.querySelector('.stretch');
  95. let justifyCenterDiv = stretchElement.querySelector('.justify-center');
  96.  
  97. if(justifyCenterDiv.querySelector('select')) {
  98. return;
  99. }
  100. const select = document.createElement('select');
  101.  
  102. select.style = "width: 5rem;border: 0;"
  103. select.onchange = function(event) {
  104. const values = event.target.value.split(';');
  105. voice = values[0];
  106. lang = values[1];
  107. }
  108. if (typeof speechSynthesis === 'undefined') {
  109. return;
  110. }
  111.  
  112. const voices = speechSynthesis.getVoices();
  113.  
  114. for (let i = 0; i < voices.length; i++) {
  115. const option = document.createElement('option');
  116. option.textContent = `${voices[i].name};${voices[i].lang}`;
  117.  
  118. if (voices[i].default) {
  119. option.textContent += '; — DEFAULT';
  120. }
  121.  
  122. option.setAttribute('data-lang', voices[i].lang);
  123. option.setAttribute('data-name', voices[i].name);
  124.  
  125. select.appendChild(option);
  126. select.value = `${voice};${lang}`;
  127. }
  128.  
  129.  
  130. VoiceList = select;
  131. justifyCenterDiv.appendChild(select);
  132.  
  133. }
  134.  
  135.  
  136. function addTalkBtn() {
  137. let stretchElement = document.querySelector('.stretch');
  138. let justifyCenterDiv = stretchElement.querySelector('.justify-center');
  139. const button = document.createElement('button');
  140. button.classList.add('btn', 'flex', 'gap-2', 'justify-center', 'btn-neutral');
  141. button.textContent = 'Talk';
  142. button.addEventListener('click', function(e) {
  143. e.preventDefault();
  144. recognize();
  145. });
  146. justifyCenterDiv.appendChild(button);
  147. }
  148.  
  149. })();