ChatGPTHelper

1) Real-Time Local Disk Storage: ChatGPTHelper automatically saves all your chat history and predefined prompts on your local disk as you go. 2) No Official History Required: You won't need to fine-tune it with official history data. Your information remains confidential, never used to train the model. 3) Offline Functionality: ChatGPTHelper makes the history still available when offline.

  1. // ==UserScript==
  2. // @name ChatGPTHelper
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.16
  5. // @description 1) Real-Time Local Disk Storage: ChatGPTHelper automatically saves all your chat history and predefined prompts on your local disk as you go. 2) No Official History Required: You won't need to fine-tune it with official history data. Your information remains confidential, never used to train the model. 3) Offline Functionality: ChatGPTHelper makes the history still available when offline.
  6. // @author maple
  7. // @match https://chat.openai.com/*
  8. // @grant GM_xmlhttpRequest
  9. // @license GPL-3.0-or-later
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. const WEBREDIS_ENDPOINT = "http://127.0.0.1:7379";
  16. var thisInHistory = false;
  17. var HistoryPrefix = "HISTORY";
  18. var PromptPrefix = "USERPROMPT";
  19. var redisHistoryName = "";
  20. var generateButton = null;
  21.  
  22.  
  23. function load(key, callback) {
  24. key = encodeURIComponent(key);
  25. GM_xmlhttpRequest({
  26. method: "GET",
  27. url: `${WEBREDIS_ENDPOINT}/GET/${key}`,
  28. onload: function(response) {
  29. callback(null, JSON.parse(response.responseText));
  30. },
  31. onerror: function(error) {
  32. callback(error, null);
  33. }
  34. });
  35. }
  36.  
  37. function getTime() {
  38. var d = new Date();
  39. var year = d.getFullYear();
  40. var month = (d.getMonth() + 1).toString().padStart(2, '0'); // Months are 0-based, so we add 1
  41. var day = d.getDate().toString().padStart(2, '0');
  42. var hours = d.getHours().toString().padStart(2, '0');
  43. var minutes = d.getMinutes().toString().padStart(2, '0');
  44. var seconds = d.getSeconds().toString().padStart(2, '0');
  45.  
  46. return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
  47. }
  48.  
  49. function save(nameprefix, data, callback) {
  50. var strdata;
  51. var dname;
  52. var redisname;
  53. var currentTimestamp = getTime();
  54. if (Array.isArray(data)) {// history
  55. strdata = JSON.stringify(data.map(function(element) {
  56. return element.innerHTML;
  57. }));
  58. dname = data[0].innerText.substring(0, 100).trim();
  59. if (redisHistoryName == ""){
  60. redisHistoryName = encodeURIComponent(nameprefix + currentTimestamp + "\n" + dname);
  61. }
  62. redisname = redisHistoryName
  63. } else {//prompt
  64. strdata = JSON.stringify(data);
  65. dname = data.substring(0, 100).trim();
  66. redisname = encodeURIComponent(nameprefix + currentTimestamp + "\n" + dname);
  67. }
  68. if (strdata && strdata.length < 3){
  69. return;
  70. }
  71.  
  72. console.log(redisname);
  73. GM_xmlhttpRequest({
  74. method: "GET",
  75. url: `${WEBREDIS_ENDPOINT}/SET/${redisname}/${encodeURIComponent(strdata)}`,
  76. onload: function(response) {
  77. console.log(response);
  78. callback(null, response.responseText);
  79. },
  80. onerror: function(error) {
  81. console.log(error);
  82. callback(error, null);
  83. }
  84. });
  85. }
  86.  
  87. function remove(key, callback) {
  88. key = encodeURIComponent(key);
  89. GM_xmlhttpRequest({
  90. method: "GET",
  91. url: `${WEBREDIS_ENDPOINT}/DEL/${key}`,
  92. onload: function(response) {
  93. callback(null, JSON.parse(response.responseText));
  94. },
  95. onerror: function(error) {
  96. callback(error, null);
  97. }
  98. });
  99. }
  100.  
  101.  
  102. function getAllRedisKeys(callback) {
  103. GM_xmlhttpRequest({
  104. method: "GET",
  105. url: `${WEBREDIS_ENDPOINT}/KEYS/*`, // Update this to your actual endpoint
  106. onload: function(response) {
  107. if (response.responseText == undefined){
  108. redisError();
  109. return;
  110. }
  111. callback(null, JSON.parse(response.responseText));
  112. },
  113. onerror: function(error) {
  114. callback(error, null);
  115. }
  116. });
  117. }
  118.  
  119.  
  120. function getCurrentDialogContent() {
  121. // Get all div elements with the specified class
  122. const divsWithSpecificClass = document.querySelectorAll('div.flex.flex-grow.flex-col.gap-3.max-w-full');
  123. // Create an array to store the text content of each div
  124. const divTexts = [];
  125.  
  126. // Loop through the NodeList and get the text content of each div
  127. divsWithSpecificClass.forEach(div => {
  128. var textContent = [];
  129. divTexts.push(div);
  130. });
  131.  
  132. // Return the array of text contents
  133. return divTexts;
  134. }
  135.  
  136. function sleep(ms) {
  137. return new Promise(resolve => setTimeout(resolve, ms));
  138. }
  139.  
  140. function showHistory(dataList) {
  141. var targetDiv = document.querySelector('div.flex-1.overflow-hidden > div > div > div');
  142.  
  143.  
  144. dataList.forEach(data => {
  145. var newDiv = document.createElement('div');
  146. newDiv.textContent = data;
  147. targetDiv.appendChild(newDiv);
  148. });
  149. }
  150.  
  151. function makeAGroup(name, keys, elementfilter, clickcallback){
  152. const div = document.createElement('div');
  153. div.style.padding = "5px"
  154. const ul = document.createElement('ul');
  155. ul.style.overflowY = 'auto';
  156. ul.style.maxHeight = '500px';
  157. var eid = "myUl" + name
  158. div.id = eid; // Setting an ID to the ul element
  159. var h2 = document.createElement('h2');
  160. h2.innerText = name;
  161. h2.style.color = 'white';
  162. h2.style.textAlign = "center";
  163. div.append(h2);
  164. for (let i = 0; i < keys.length; i++) {
  165. const li = document.createElement('li');
  166. if(!keys[i].startsWith(elementfilter)){
  167. continue;
  168. }
  169. var parts = keys[i].substring(elementfilter.length, keys[i].length).split("\n");
  170. var p = document.createElement('p');
  171. p.innerText = parts[1];
  172. p.style.lineHeight = '0.9';
  173. p.style.fontSize = '10pt';
  174. //p.style.whiteSpace = 'pre-line';
  175. //p.style.wordWrap = 'break-word';
  176. p.style.wordBreak = 'break-all';
  177. li.innerHTML = `<p style="color: grey; font-size: 5pt;">${parts[0]}</p>`;
  178. li.appendChild(p);
  179. li.style.color = '#dddddd';
  180. // Apply CSS styles for the rounded rectangle
  181. li.style.border = '1px solid #333333'; // Add a border
  182. li.style.borderRadius = '10px'; // Adjust the horizontal and vertical radii to create rounded corners
  183. li.style.padding = '10px'; // Add some padding to make it visually appealing
  184. li.style.position = 'relative';
  185. li.style.marginBottom = '4px';
  186. li.addEventListener('mouseenter', function() {
  187. li.style.backgroundColor = 'rgba(100, 100, 100, 0.8)'; // Change to your desired background color
  188. });
  189.  
  190. li.addEventListener('mouseleave', function() {
  191. li.style.backgroundColor = ''; // Reset to the original background color
  192. });
  193. li.addEventListener('click', (event) => {clickcallback(event, keys[i]);});
  194.  
  195. // add close
  196. // Create close button
  197. var closeButton = document.createElement('span');
  198. closeButton.innerHTML = '<svg width="20" height="20" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg" <g style="fill:none;stroke:#aaa;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-width:2"><path d="m31 16v-4h10v4"/><path d="m51 25v31c0 2.2091-1.7909 4-4 4h-22c-2.2091 0-4-1.7909-4-4v-31"/><path d="m17 16h38v4h-38z"/><path d="m41 28.25v26.75"/><path d="m31 28.25v26.75"/></g></svg>';
  199. closeButton.style.position = 'absolute';
  200. closeButton.style.top = '6px';
  201. closeButton.style.right = '5px';
  202. closeButton.style.color = 'white';
  203. closeButton.style.cursor = 'pointer'; // Set cursor to pointer to indicate it's clickable
  204.  
  205. // Add event listener for the close button
  206. closeButton.addEventListener('click', async (event) => {
  207. // Your callback function here
  208. event.stopPropagation();
  209. remove(keys[i], function (){});
  210. await sleep(500);
  211. InitPanel();
  212. });
  213.  
  214. // Add close button to li
  215. li.appendChild(closeButton);
  216. ul.appendChild(li);
  217. }
  218. div.append(ul);
  219. return div;
  220. }
  221.  
  222.  
  223. async function InitPanel() {
  224.  
  225. getAllRedisKeys(function(error, data) {
  226. if (error) {
  227. redisError();
  228. console.error('An error occurred:', error);
  229. return;
  230. }
  231. const ol = document.querySelectorAll('ol')[2];
  232. var div = document.querySelectorAll('div.flex-shrink-0.overflow-x-hidden')[0];
  233. const ulExisting = document.getElementById('myUlHistory');
  234. if (ulExisting) {
  235. div.removeChild(ulExisting, function (){});
  236. }
  237. if (data.KEYS.length == 0){
  238. redisError();
  239. }
  240. var ul = makeAGroup("History", data.KEYS.sort().reverse(), HistoryPrefix, function(event, key) {
  241. //console.log('Item clicked:', data.KEYS[i]);
  242. // Load data after saving
  243. load(key, function(err, data) {
  244. if (err) return console.error(err);
  245. var myList = JSON.parse(data.GET);
  246. if (Array.isArray(myList)){
  247. for (let i = 0; i < myList.length; i++) {
  248. if (i % 2 == 0) {
  249. myList[i] = "👨: " + myList[i].replace(/\n/g, '<br>');
  250. } else {
  251. myList[i] = "🤖: " + myList[i].replace(/\n/g, '<br>');
  252. }
  253. }
  254. showHistoryLog(myList.join("<br>"));
  255. }
  256. });
  257. });
  258. div.prepend(ul);
  259.  
  260. /*---Prompt---*/
  261. var ulPrompt = document.getElementById('myUlPrompt');
  262. if (ulPrompt) {
  263. div.removeChild(ulPrompt);
  264. }
  265. var prompt = makeAGroup("Prompt", data.KEYS.sort().reverse(), PromptPrefix, function(event, key) {
  266. //console.log('Item clicked:', data.KEYS[i]);
  267. // Load data after saving
  268. load(key, function(err, data) {
  269. if (err) return console.error(err);
  270. var prompt = JSON.parse(data.GET);
  271. var textarea = document.getElementById('prompt-textarea');
  272. textarea.value = prompt;
  273. var event = new Event('input', { bubbles: true });
  274. textarea.dispatchEvent(event);
  275. textarea.focus();
  276. textarea.setSelectionRange(textarea.value.length, textarea.value.length);
  277. textarea.scrollTop = textarea.scrollHeight;
  278. console.log("in chaning", prompt, textarea);
  279. });
  280. });
  281. div.prepend(prompt);
  282. if (!ulPrompt) {
  283. var textarea = document.getElementById('prompt-textarea');
  284. var button = document.createElement('button');
  285. button.innerHTML = '<svg height="25" viewBox="0 0 1792 1792" width="25" xmlns="http://www.w3.org/2000/svg" stroke="#bbbbbb"><path d="M512 1536h768v-384h-768v384zm896 0h128v-896q0-14-10-38.5t-20-34.5l-281-281q-10-10-34-20t-39-10v416q0 40-28 68t-68 28h-576q-40 0-68-28t-28-68v-416h-128v1280h128v-416q0-40 28-68t68-28h832q40 0 68 28t28 68v416zm-384-928v-320q0-13-9.5-22.5t-22.5-9.5h-192q-13 0-22.5 9.5t-9.5 22.5v320q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5-9.5t9.5-22.5zm640 32v928q0 40-28 68t-68 28h-1344q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h928q40 0 88 20t76 48l280 280q28 28 48 76t20 88z" fill="#bbbbbb"/></svg>';
  286. //button.style.color = 'white';
  287. button.style.position = 'absolute';
  288. button.style.right = '60px';
  289. button.style.top = '20px';
  290. button.style.textAlign = 'center';
  291. //button.style.border = '1px solid grey';
  292. button.style.marginLeft = '10px';
  293. //button.style.backgroundColor = '#268BD2';
  294. //var bottomdiv = document.querySelectorAll('div.relative.pb-3.pt-2.text-center.text-xs.text-gray-600')[0];
  295. //bottomdiv.appendChild(button);
  296. button.title = 'Save the message as a prompt';
  297.  
  298. textarea.parentNode.appendChild(button);
  299.  
  300. button.addEventListener('click', function() {
  301. var textarea = document.getElementById('prompt-textarea');
  302. save(PromptPrefix, textarea.textContent, function(err, response) {
  303. if (err) return console.error(err);
  304. });
  305.  
  306. InitPanel();
  307. });
  308. }
  309.  
  310.  
  311. });
  312. /*Remote Offical*/
  313. await sleep(2000);
  314. const olElements = document.querySelectorAll('ol');
  315. olElements.forEach(ol => {
  316. // First remove all existing children
  317. while (ol.firstChild) {
  318. ol.removeChild(ol.firstChild);
  319. }
  320. });
  321.  
  322. }
  323.  
  324. function redisError(){
  325. var div = document.querySelectorAll('div.flex-shrink-0.overflow-x-hidden')[0];
  326. const ul = document.createElement('ul');
  327. div.prepend(ul);
  328. const li = document.createElement('li');
  329. li.textContent = "There is no record. Please verify if webdis AND redis-server has been started! Just run `webdis.sh start` to start.";
  330. li.style.color = 'white';
  331. ul.appendChild(li);
  332. }
  333.  
  334. function showHistoryLog(text) {
  335. // Check if the div with a specific id already exists
  336. var existingDiv = document.getElementById('historyLog');
  337.  
  338. if (existingDiv) {
  339. // If the div exists, update the messageSpan's HTML content
  340. var messageSpan = existingDiv.querySelector('.message-span');
  341. if (messageSpan) {
  342. messageSpan.innerHTML = text;
  343. }
  344. existingDiv.style.display = '';
  345. } else {
  346. // If the div doesn't exist, create a new div and append it to the body
  347. var hoverBox = document.createElement('div');
  348. hoverBox.id = 'historyLog'; // Set a unique id for the div
  349. hoverBox.style.position = 'fixed';
  350. hoverBox.style.top = '50%';
  351. hoverBox.style.left = '50%';
  352. hoverBox.style.transform = 'translate(-50%, -50%)';
  353. hoverBox.style.zIndex = '10000';
  354. hoverBox.style.padding = '10px';
  355. hoverBox.style.width = '1000px';
  356. hoverBox.style.height = '800px';
  357. hoverBox.style.backgroundColor = 'white';
  358. hoverBox.style.border = '1px solid black';
  359. hoverBox.style.borderRadius = '5px';
  360. hoverBox.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.5)';
  361. hoverBox.style.overflow = 'hidden'; // Hide content overflow
  362.  
  363. // Create a container div for the content and close button
  364. var contentContainer = document.createElement('div');
  365. contentContainer.style.overflowY = 'auto'; // Make content scrollable
  366. //contentContainer.style.resize = 'both'; // Enable resizing
  367. contentContainer.style.height = 'calc(100% - 40px)'; // Adjust height for close button
  368.  
  369. // Create a span element to hold the message
  370. var messageSpan = document.createElement('span');
  371. messageSpan.innerHTML = text;
  372. messageSpan.className = 'message-span'; // Add a class for easy selection
  373. messageSpan.style.display = 'block';
  374. messageSpan.style.marginTop = '20px';
  375.  
  376. // Create a button element to close the hover box
  377. var closeButton = document.createElement('button');
  378. closeButton.textContent = '✖';
  379. closeButton.style.position = 'absolute';
  380. closeButton.style.top = '10px';
  381. closeButton.style.right = '10px';
  382. closeButton.addEventListener('click', function () {
  383. hoverBox.style.display = 'none';
  384. });
  385.  
  386. // Add the message span and close button to the content container
  387. contentContainer.appendChild(messageSpan);
  388. contentContainer.appendChild(closeButton);
  389.  
  390. // Add the content container to the hover box
  391. hoverBox.appendChild(contentContainer);
  392.  
  393. document.addEventListener('click', function (event) {
  394. if (!hoverBox.contains(event.target) && event.target !== hoverBox) {
  395. hoverBox.style.display = 'none';
  396. event.stopPropagation();
  397. }
  398. });
  399.  
  400. // Add the hover box to the body of the document
  401. document.body.appendChild(hoverBox);
  402. }
  403. }
  404.  
  405. async function saveHistory(){
  406. while(true){
  407. var thisdialog = getCurrentDialogContent();
  408. save(HistoryPrefix, thisdialog, function(err, response) {
  409. if (err) return console.error(err);
  410. console.log('Save response:', response);
  411. if(!thisInHistory){
  412. InitPanel();
  413. thisInHistory = true;
  414. }
  415. });
  416. const divElement = document.querySelector('div.flex.items-center.md\\:items-end');
  417. if (divElement != undefined && divElement.textContent != undefined && divElement.textContent == 'Continue generating'){
  418. var continueButton = divElement.getElementsByTagName('button')[0];
  419. continueButton.click();
  420. }
  421. else if (divElement == undefined || divElement.textContent == undefined || divElement.textContent != 'Stop generating'){
  422. break;
  423. }
  424. if (divElement != undefined && generateButton == null){
  425. generateButton = divElement;
  426. generateButton.addEventListener('click', () => {
  427. saveHistory();
  428. });
  429. }
  430. await sleep(2000);
  431. }
  432. }
  433.  
  434.  
  435.  
  436.  
  437.  
  438.  
  439. InitPanel();
  440.  
  441.  
  442.  
  443.  
  444. document.addEventListener('keydown', function(event) {
  445. if (event.key === 'Enter' || (event.metaKey && event.key === 'r')) {
  446. // Usage examples
  447. saveHistory();
  448. }
  449. if (event.key === 'Escape') {
  450. var existingDiv = document.getElementById('historyLog');
  451. if (existingDiv) {
  452. existingDiv.style.display = 'none';
  453. }
  454. }
  455. });
  456.  
  457.  
  458.  
  459.  
  460.  
  461.  
  462. })();