Created
December 13, 2022 03:50
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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