// ==UserScript== // @name Save Unsent AI Prompts // @namespace Violent Monkey Scripts // @version 1.0 // @description Auto-save unsent AI prompts as drafts // @match https://chatgpt.com/* // @grant none // ==/UserScript== (async function(){ for await (let newTarget of targetDivs()) { applyHandler(newTarget); } })(); function timeout(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Continuously looks for new #prompt-textarea divs async function* targetDivs() { let found = new Set(); while(true) { let divs = document.querySelectorAll('#prompt-textarea'); for(let div of divs) { if(!found.has(div)) { found.add(div); yield div; } } await timeout(500); } } function shouldSaveDraft(oldVal, newVal) { // Save if text was added or replaced (not pure deletion). if (newVal.length > oldVal.length) return true; if (newVal.length === oldVal.length && newVal !== oldVal) return true; return false; } function saveDraft(id, text) { localStorage.setItem('draft-'+id, text); } function applyHandler(div) { let currentId = null; let oldValue = ''; // Called whenever mutations occur function onDivMutate() { let newValue = div.innerHTML.trim(); if (!newValue) { // If there's nothing left, reset currentId = null; oldValue = ''; return; } // If we had no oldValue, this is the first time we're typing => new ID if (!oldValue || !currentId) { currentId = Date.now(); } // Only save if something was added (newValue got longer than oldValue) if(shouldSaveDraft(oldValue,newValue)) { saveDraft(currentId,newValue); } // Update the known current content oldValue = newValue; } // Observe changes in the div (childList & characterData changes) let observer = new MutationObserver(onDivMutate); observer.observe(div, {childList: true, characterData: true, subtree: true}); }