Skip to content

Instantly share code, notes, and snippets.

@rynomad
Created April 17, 2023 18:08
Show Gist options
  • Save rynomad/4d9ae2afb6b13534c1cd51b0ee28f881 to your computer and use it in GitHub Desktop.
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
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