Created
April 17, 2023 18:08
-
-
Save rynomad/4d9ae2afb6b13534c1cd51b0ee28f881 to your computer and use it in GitHub Desktop.
simple chat driver class for chatGPT. it handles chunking on the way in and out, and returns all the codeElements in the response
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
class ChatBot { | |
async start() { | |
const newChatLink = Array.from(document.querySelectorAll("a")).find( | |
(anchor) => anchor.innerText === "New Chat" | |
); | |
if (newChatLink) { | |
newChatLink.click(); | |
} else { | |
console.error('Could not find the "New Chat" link.'); | |
} | |
} | |
async request(text, timeout = 3000, skipChunking = false) { | |
if (!skipChunking) { | |
const words = text.split(" "); | |
if (words.length > 4000) { | |
const chunks = []; | |
while (words.length > 0) { | |
const chunk = words.splice(0, 4000).join(" "); | |
chunks.push(chunk); | |
} | |
return await this.sendChunks(chunks); | |
} | |
text += " finish your response with 'EOF' so I know you're done"; | |
} | |
return new Promise(async (resolve) => { | |
const textbox = document.querySelector("textarea"); | |
const sendButton = textbox.nextElementSibling; | |
if (sendButton.hasAttribute("disabled")) { | |
sendButton.removeAttribute("disabled"); | |
} | |
textbox.value = text; | |
sendButton.click(); | |
let lastContent = ""; | |
let checkInterval; | |
const checkLastGroup = () => { | |
const lastGroup = Array.from( | |
document.querySelectorAll("div.group") | |
).pop(); | |
const raw = lastGroup.textContent || ""; | |
if (lastContent !== raw) { | |
lastContent = raw; | |
} else { | |
clearInterval(checkInterval); | |
const codeElements = lastGroup.querySelectorAll("code"); | |
resolve({ raw, codeElements }); | |
} | |
}; | |
checkInterval = setInterval(checkLastGroup, timeout); | |
}); | |
} | |
async sendChunks(chunks) { | |
let combinedResponse = { raw: "", codeElements: [] }; | |
for (let i = 0; i < chunks.length; i++) { | |
const chunk = chunks[i]; | |
const preamble = | |
i === chunks.length - 1 | |
? "This is the final chunk, please respond to all chunks." | |
: `This is chunk ${i + 1} of ${ | |
chunks.length | |
}, please respond with 'ACK' and wait for the rest.`; | |
const postscript = | |
i === chunks.length - 1 | |
? "This is the final chunk, please respond to all chunks." | |
: `End chunk ${i + 1} of ${ | |
chunks.length | |
}, remember to respond with 'ACK' for more chunks.`; | |
const response = await this.request( | |
`${preamble} ${chunk} ${postscript}`, | |
3000, | |
true | |
); | |
if (response.raw.trim() !== "ACK" || i === chunks.length - 1) { | |
combinedResponse.raw += response.raw + " "; | |
combinedResponse.codeElements = combinedResponse.codeElements.concat( | |
response.codeElements | |
); | |
} | |
} | |
return combinedResponse; | |
} | |
mergeText(text1, text2) { | |
// Remove whitespace from the beginning and end of the strings | |
const trimmedText1 = text1.trim(); | |
const trimmedText2 = text2.trim(); | |
for (let overlapLength = 200; overlapLength > 0; overlapLength--) { | |
if ( | |
trimmedText1.slice(-overlapLength) === | |
trimmedText2.slice(0, overlapLength) | |
) { | |
// Reconstruct the merged string using the original text1 and text2 | |
return trimmedText1 + trimmedText2.slice(overlapLength).trim(); | |
} | |
} | |
return text1 + text2; | |
} | |
combineCodeElementsWithClass(classStrs, codeElements) { | |
const filteredElements = Array.from(codeElements).filter((el) => | |
classStrs.some((classStr) => el.classList.contains(classStr)) | |
); | |
const combinedText = filteredElements.reduce((acc, el, idx) => { | |
if (idx === 0) { | |
return el.textContent; | |
} | |
return this.mergeText(acc, el.textContent); | |
}, ""); | |
return combinedText; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment