Skip to content

Instantly share code, notes, and snippets.

@kesor
Created December 13, 2022 03:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kesor/3e309931b0056d9f1b658b1ce3354435 to your computer and use it in GitHub Desktop.
Save kesor/3e309931b0056d9f1b658b1ce3354435 to your computer and use it in GitHub Desktop.
This is a voice Synthesis for ChatGPT that unfortunately doesn't work right now
// ==UserScript==
// @name ChatGPT Speak and Listen
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author https://gist.github.com/kesor
// @downloadURL https://gist.github.com/kesor/fc0d1a9b285011b74670109f22a59670
// @match https://chat.openai.com/chat
// @grant unsafeWindow
// ==/UserScript==
/**
* You need the Chrome TamperMonkey extension for this --
* https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=en
*/
(function() {
'use strict';
// Function to create a clickable <a> button with the given label
function createButton(label) {
let button = document.createElement('a');
button.className = "btn btn-sm text-center p-1 border-1";
button.innerHTML = label;
button.href = "#";
return button;
}
// Function to create an indicator that can be either visible or hidden
function createIndicator(text) {
let indicator = document.createElement('p');
indicator.className = 'btn btn-sm text-center p-1 border-1';
indicator.innerHTML = text;
indicator.style.visibility = 'hidden';
return indicator;
}
// Function to create a checkbox with the given label
function createCheckbox(label) {
let checkbox = document.createElement('input');
checkbox.className = 'btn btn-sm text-center p-1 border-1';
checkbox.type = "checkbox";
checkbox.checked = true;
return checkbox;
}
// Create the buttons
var listenButton = createButton("Listen");
var listeningIndicator = createIndicator("Listening");
var speaksCheckbox = createCheckbox("It speaks!");
var shutItButton = createButton("Shut it!");
const buttonContainer = document.createElement('div')
buttonContainer.className = 'btn-group'
// Add the buttons to the container
buttonContainer.appendChild(listenButton);
buttonContainer.appendChild(listeningIndicator);
buttonContainer.appendChild(speaksCheckbox);
buttonContainer.appendChild(shutItButton);
// add container to the bottom of the page
document.getElementsByTagName("textarea")[0].parentElement.parentElement.appendChild(buttonContainer);
shutItButton.addEventListener("click", (e) => {
e.preventDefault();
speechSynthesis.cancel();
return false;
});
/** --- Speech Synthesis section --- */
function getUtterance() {
// Check that the speech synthesis object is available
if (!('speechSynthesis' in window)) {
console.error('Speech synthesis is not supported in this browser.')
return
}
let utterance = new SpeechSynthesisUtterance()
utterance.voice = speechSynthesis.getVoices().find(
(v) => v.lang === "en-GB" && v.name.includes("Male")
);
utterance.rate = 1.2;
utterance.text = ''
speechSynthesis.cancel()
return utterance
}
let utterance = getUtterance()
let minChars = 40
let totalText = ''
function speakContent(newContent) {
const newText = newContent.substring(totalText.length)
if (speaksCheckbox.checked && !speechSynthesis.speaking && newText.length > 0) {
utterance.text = newText
speechSynthesis.speak(utterance)
totalText = newContent
}
}
function readChunks(reader) {
return {
async* [Symbol.asyncIterator]() {
let readResult = await reader.read();
while (!readResult.done) {
const strChunk = new TextDecoder('utf-8').decode(readResult.value)
yield strChunk
readResult = await reader.read();
}
},
}
}
async function speakTheTruth(url, res1) {
let previousContent = ''
for await (const chunk of readChunks(res1.body.getReader())) {
let strChunk
try {
strChunk = chunk.replace(/^data: /, '').trim();
if (strChunk.match(/^\[DONE\]$/)) {
console.log('previous chunk was', chunk)
console.log('returning because str chunk was: ', strChunk)
continue
}
const cleanChunk = strChunk.replaceAll(/```.*```/sig,'').replaceAll(/(`|\n)/g,' ')
const jsonChunk = JSON.parse(cleanChunk)
const newContent = jsonChunk.message.content.parts.join(' ');
speakContent(newContent)
} catch (err) {
console.log('could not parse chunk as json: ', strChunk)
}
}
}
// overwrite window.fetch()
const originalFetch = unsafeWindow.fetch;
const patchedFetch = async (...args) => {
const [ url, options ] = args
const ogResponse = await originalFetch(...args);
if (/conversation$/.test(url)) {
const res1 = ogResponse.clone();
speakTheTruth(url, res1).then(console.log).catch(console.error);
}
return ogResponse
};
unsafeWindow.fetch = patchedFetch;
/** --- Speech Recognition section --- */
listenButton.addEventListener("click", (e) => {
e.preventDefault();
// Initialize the SpeechRecognition object
const recognition = new (window.webkitSpeechRecognition || window.SpeechRecognition)();
recognition.lang = "en-US";
recognition.addEventListener("result", recognitionResult);
// Start the recognition process
recognition.start();
listeningIndicator.style.visibility = 'visible';
return false;
});
// Handle the recognition result event
function recognitionResult(event) {
let final = "";
for (let i = 0; i < event.results.length; ++i) {
if (event.results[i].isFinal) {
final += event.results[i][0].transcript;
}
}
listeningIndicator.style.visibility = 'hidden';
document.getElementsByTagName("textarea")[0].value = final;
const buttons = document.getElementsByTagName("button");
buttons[buttons.length - 1].click();
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment