🏠 Home 

Click ChatGPT Last Sentence to Continue

To continue the last response with one click.

// ==UserScript==
// @name         Click ChatGPT Last Sentence to Continue
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  To continue the last response with one click.
// @author       CY Fung
// @match        https://chat.openai.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=openai.com
// @grant        none
// @license      MIT
// @run-at       document-idle
// @noframes
// ==/UserScript==
'use strict';
(function () {
'use strict';
let allLen = 0;
let cid1 = 0;
function lastOfArr(elements) {
return elements && elements.length >= 1 ? elements[elements.length - 1] : null;
}
function addStyleText(t) {
let s = document.createElement('style');
s.textContent = t;
document.head.appendChild(s);
return s;
}
function getSelectableRows(element) {
let childNodes = [];
let nodeA = element.firstChild;
while (nodeA instanceof Node) {
let node = nodeA;
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.tagName === 'P') {
//
} else if (node.tagName === 'OL' || node.tagName === 'UL') {
node = lastOfArr(node.querySelectorAll('li'));
} else if (node.tagName === 'PRE') {
node = lastOfArr(node.querySelectorAll('code'));
} else {
node = null;
}
if (node) {
let textContent = node.textContent;
if (textContent.length >= 1 && textContent.trim().length >= 1) {
childNodes.push(node);
}
}
}
nodeA = nodeA.nextSibling;
}
return childNodes;
}
let previousRow = null;
const cssStyle = `
.last-row-message.message-stopped:hover{
background: rgba(125,125,125,0.25);
cursor:pointer;
}
`;
async function setTextAreaMessage(textarea, message) {
textarea.focus();
await new Promise(window.requestAnimationFrame);
textarea.value = '';
if (message) {
await new Promise(window.requestAnimationFrame);
textarea.value = message;
}
await new Promise(window.requestAnimationFrame);
try {
document.execCommand("insertText", false, " ")
} catch (e) { }
}
function getRandomElement(array) {
let randomIndex = Math.floor(Math.random() * array.length);
return array[randomIndex];
}
function preMultilineTextProcess(text) {
let lines = text.split('\n');
for(let i =lines.length-1; i>=0; i--){
let noSymbolLine = lines[i].replace(/[\x00-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F\x80-\xFF]+/g,'').trim();
// exclude symbols
// remains number, english letters, chinese, ....
if(noSymbolLine.length>0){
let foundAt = i;
text = lines.slice(foundAt).join('\n').trim();
return text;
}
}
return text;
}
function howToSay(s) {
if (!s) return null;
s = s.trim().replace(/(\r\n|\n\r|\r)/g,'\n');
s = preMultilineTextProcess(s);
let sk = 0;
if (s.includes('\n')) sk = 3;
else if (s.includes('```') || s.includes('`')) sk = 3;
else if (s.includes('\"')) sk = 2;
else sk = 1;
if (sk == 3) {
s = '```\n' + s.replace(/\`\`\`/g, '\n\n') + '\n```';
} else if (sk == 2) {
s = '`' + s + '`';
} else if (sk == 1) {
s = '"' + s + '"';
}
let arr = [
// `Your last response stopped at ${s}. Please continue.`,
`Please continue on your last response which stopped at ${s}.`,
`Continue on your last response from ${s}.`,
`Please r###me your last response which paused at ${s}.`,
`R###me your last response from ${s}.`,
// `Your previous response stopped at ${s}. Please continue.`,
`Please continue on your previous response which stopped at ${s}.`,
`Continue on your previous response from ${s}.`,
`Please r###me your previous response which paused at ${s}.`,
`R###me your previous response from ${s}.`,
];
return getRandomElement(arr);
}
function howToSayCode() {
let arr = [
`Please continue the coding with the last incomplete line that you left off in your last response.`,
`Please continue the coding with the last incomplete line that you left off in your previous response.`,
`Please continue the coding with the last incomplete line that you abruptly ended in your last response.`,
`Please continue the coding with the last incomplete line that you abruptly ended in your previous response.`
];
return getRandomElement(arr);
}
function clickLastRowMessageHandler(evt) {
if (!evt || !evt.target) return;
let target = evt.target.closest('.last-row-message.message-stopped');
if (!target) return;
let p = target;
let textarea = null;
while (p = p.parentNode) {
if (textarea = p.querySelector('textarea[placeholder]')) break;
}
if (textarea !== null) {
evt.preventDefault();
let display = '';
if (target.closest('code')) {
display = howToSayCode();
} else {
display = howToSay(target.textContent);
}
setTextAreaMessage(textarea, display);
}
}
let newMessageFoundR###lt = null;
const newMessageFoundTFunc = () => {
let message = newMessageFoundR###lt;
if(message && message.isConnected === false){
newMessageFoundR###lt = null;
clearCid1(); // removed
allLen = 0;
checkNewMessage();
return;
}
if (message === null) return;
let selectableRows = getSelectableRows(message);
let lastRow = selectableRows[selectableRows.length - 1] || null;
if(lastRow === null && message.lastChild instanceof Text){
// this could happen when GPT's response is purely a text without formatting. (Least Chance)
lastRow = message;
}
if (lastRow !== previousRow && lastRow) {
let mRow = previousRow;
previousRow = lastRow;
if (mRow !== null) {
mRow.classList.remove('last-row-message', 'message-stopped', 'message-checking');
mRow.removeAttribute('title');
mRow.removeEventListener('click', clickLastRowMessageHandler, false);
}
lastRow.classList.add('last-row-message', 'message-checking');
lastRow.removeAttribute('title');
lastRow.classList.remove('message-stopped');
lastRow.addEventListener('click', clickLastRowMessageHandler, false);
if (mRow === null) {
let lastRowCss= document.querySelector('#last-row-css');
if(lastRowCss === null){
let style = addStyleText(cssStyle);
style.id = 'last-row-css'
}
}
rowText = '';
}
};
function clearCid1(){
if (cid1 > 0) clearInterval(cid1);
cid1 = 0;
}
function newMessageFound(message) {
clearCid1();
if (!message) return;
newMessageFoundR###lt = message;
cid1 = setInterval(newMessageFoundTFunc, 100);
}
let counter = 0;
let rowText = '';
let di = 0;
let lastStreamState = false; // as there are some bugs for the detection, reset once r###lt-streaming is flipped.
function checkNewMessage() {
let all = document.querySelectorAll('div.markdown.prose:not(:empty)');
let cLen = all.length;
if (cLen !== allLen) {
allLen = cLen;
if (allLen >= 1) newMessageFound(all[all.length - 1]);
}
}
function reset() {
previousRow = null;
rowText = '';
counter = 0;
}
setInterval(() => { // 30ms
let oldLastStreamState = lastStreamState;
let newLastStreamState = false;
di++;
if ((di % 10) === 0) { // 300ms
if (di > 900) di = 0;
checkNewMessage();
}
if (previousRow !== null && previousRow.isConnected === false) {
previousRow = null;
}
if (previousRow !== null) {
if (previousRow.closest('.r###lt-streaming')) {
newLastStreamState = true;
} else {
let text = previousRow.textContent;
if (rowText !== text) {
let mText = rowText;
rowText = text;
counter = 0;
let k = 3;
let g = mText.length - k;
if (g > 0 && text.length > mText.length && text.substring(0, g) === mText.substring(0, g) && previousRow.classList.contains('message-checking')) {
previousRow.classList.remove('message-checking');
}
}
if (counter < 96) counter++;
if (counter === 20) {
previousRow.classList.add('message-stopped');
previousRow.classList.remove('message-checking');
previousRow.setAttribute('title', 'click to ask continue');
}
}
}
if (oldLastStreamState !== newLastStreamState) {
lastStreamState = newLastStreamState;
reset();
}
}, 30);
// Your code here...
})();