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.

当前为 2023-09-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name ChatGPTHelper
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.10
  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.  
  21.  
  22. function load(key, callback) {
  23. key = encodeURIComponent(key);
  24. GM_xmlhttpRequest({
  25. method: "GET",
  26. url: `${WEBREDIS_ENDPOINT}/GET/${key}`,
  27. onload: function(response) {
  28. callback(null, JSON.parse(response.responseText));
  29. },
  30. onerror: function(error) {
  31. callback(error, null);
  32. }
  33. });
  34. }
  35.  
  36. function save(nameprefix, data, callback) {
  37. var strdata;
  38. var dname;
  39. var redisname;
  40. var date = new Date();
  41. var currentTimestamp = date.toLocaleString().substring(0, 19);
  42. if (Array.isArray(data)) {// history
  43. strdata = JSON.stringify(data.map(function(element) {
  44. return element.innerHTML;
  45. }));
  46. dname = data[0].innerText.substring(0, 50).trim();
  47. if (redisHistoryName == ""){
  48. redisHistoryName = encodeURIComponent(nameprefix + currentTimestamp + "\n" + dname);
  49. }
  50. redisname = redisHistoryName
  51. } else {//prompt
  52. strdata = JSON.stringify(data);
  53. dname = data.substring(0, 50).trim();
  54. redisname = encodeURIComponent(nameprefix + currentTimestamp + "\n" + dname);
  55. }
  56. if (strdata && strdata.length < 3){
  57. return;
  58. }
  59.  
  60. console.log(redisname);
  61. GM_xmlhttpRequest({
  62. method: "GET",
  63. url: `${WEBREDIS_ENDPOINT}/SET/${redisname}/${encodeURIComponent(strdata)}`,
  64. onload: function(response) {
  65. console.log(response);
  66. callback(null, response.responseText);
  67. },
  68. onerror: function(error) {
  69. console.log(error);
  70. callback(error, null);
  71. }
  72. });
  73. }
  74.  
  75. function remove(key, callback) {
  76. key = encodeURIComponent(key);
  77. GM_xmlhttpRequest({
  78. method: "GET",
  79. url: `${WEBREDIS_ENDPOINT}/DEL/${key}`,
  80. onload: function(response) {
  81. callback(null, JSON.parse(response.responseText));
  82. },
  83. onerror: function(error) {
  84. callback(error, null);
  85. }
  86. });
  87. }
  88.  
  89.  
  90. function getAllRedisKeys(callback) {
  91. GM_xmlhttpRequest({
  92. method: "GET",
  93. url: `${WEBREDIS_ENDPOINT}/KEYS/*`, // Update this to your actual endpoint
  94. onload: function(response) {
  95. if (response.responseText == undefined){
  96. redisError();
  97. return;
  98. }
  99. callback(null, JSON.parse(response.responseText));
  100. },
  101. onerror: function(error) {
  102. callback(error, null);
  103. }
  104. });
  105. }
  106.  
  107.  
  108. function getCurrentDialogContent() {
  109. // Get all div elements with the specified class
  110. const divsWithSpecificClass = document.querySelectorAll('div.flex.flex-grow.flex-col.gap-3.max-w-full');
  111. // Create an array to store the text content of each div
  112. const divTexts = [];
  113.  
  114. // Loop through the NodeList and get the text content of each div
  115. divsWithSpecificClass.forEach(div => {
  116. var textContent = [];
  117. divTexts.push(div);
  118. });
  119.  
  120. // Return the array of text contents
  121. return divTexts;
  122. }
  123.  
  124. function sleep(ms) {
  125. return new Promise(resolve => setTimeout(resolve, ms));
  126. }
  127.  
  128. function showHistory(dataList) {
  129. var targetDiv = document.querySelector('div.flex-1.overflow-hidden > div > div > div');
  130.  
  131.  
  132. dataList.forEach(data => {
  133. var newDiv = document.createElement('div');
  134. newDiv.textContent = data;
  135. targetDiv.appendChild(newDiv);
  136. });
  137. }
  138.  
  139. function makeAGroup(name, keys, elementfilter, clickcallback){
  140. const div = document.createElement('div');
  141. div.style.padding = "5px"
  142. const ul = document.createElement('ul');
  143. ul.style.overflowY = 'auto';
  144. ul.style.maxHeight = '500px';
  145. var eid = "myUl" + name
  146. div.id = eid; // Setting an ID to the ul element
  147. var h2 = document.createElement('h2');
  148. h2.innerText = name;
  149. h2.style.color = 'white';
  150. h2.style.textAlign = "center";
  151. div.append(h2);
  152. for (let i = 0; i < keys.length; i++) {
  153. const li = document.createElement('li');
  154. if(!keys[i].startsWith(elementfilter)){
  155. continue;
  156. }
  157. var parts = keys[i].substring(elementfilter.length, keys[i].length).split("\n");
  158. var p = document.createElement('p');
  159. p.innerText = parts[1];
  160. p.style.lineHeight = '0.9';
  161. //p.style.whiteSpace = 'pre-line';
  162. //p.style.wordWrap = 'break-word';
  163. p.style.wordBreak = 'break-all';
  164. li.innerHTML = `<p style="color: grey; font-size: 5pt;">${parts[0]}</p>`;
  165. li.appendChild(p);
  166. li.style.color = '#dddddd';
  167. // Apply CSS styles for the rounded rectangle
  168. li.style.border = '1px solid #333333'; // Add a border
  169. li.style.borderRadius = '10px'; // Adjust the horizontal and vertical radii to create rounded corners
  170. li.style.padding = '10px'; // Add some padding to make it visually appealing
  171. li.style.position = 'relative';
  172. li.style.marginBottom = '4px';
  173. li.addEventListener('mouseenter', function() {
  174. li.style.backgroundColor = 'rgba(100, 100, 100, 0.8)'; // Change to your desired background color
  175. });
  176.  
  177. li.addEventListener('mouseleave', function() {
  178. li.style.backgroundColor = ''; // Reset to the original background color
  179. });
  180. li.addEventListener('click', (event) => {clickcallback(event, keys[i]);});
  181.  
  182. // add close
  183. // Create close button
  184. var closeButton = document.createElement('span');
  185. 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>';
  186. closeButton.style.position = 'absolute';
  187. closeButton.style.top = '6px';
  188. closeButton.style.right = '5px';
  189. closeButton.style.color = 'white';
  190. closeButton.style.cursor = 'pointer'; // Set cursor to pointer to indicate it's clickable
  191.  
  192. // Add event listener for the close button
  193. closeButton.addEventListener('click', async (event) => {
  194. // Your callback function here
  195. event.stopPropagation();
  196. remove(keys[i], function (){});
  197. await sleep(500);
  198. InitPanel();
  199. });
  200.  
  201. // Add close button to li
  202. li.appendChild(closeButton);
  203. ul.appendChild(li);
  204. }
  205. div.append(ul);
  206. return div;
  207. }
  208.  
  209.  
  210. async function InitPanel() {
  211.  
  212. getAllRedisKeys(function(error, data) {
  213. if (error) {
  214. redisError();
  215. console.error('An error occurred:', error);
  216. return;
  217. }
  218.  
  219. const ol = document.querySelectorAll('ol')[2];
  220. var div = document.querySelectorAll('div.flex-shrink-0.overflow-x-hidden')[0];
  221. const ulExisting = document.getElementById('myUlHistory');
  222. if (ulExisting) {
  223. div.removeChild(ulExisting, function (){});
  224. }
  225. if (data.KEYS.length == 0){
  226. redisError();
  227. }
  228. var ul = makeAGroup("History", data.KEYS.sort().reverse(), HistoryPrefix, function(event, key) {
  229. //console.log('Item clicked:', data.KEYS[i]);
  230. // Load data after saving
  231. load(key, function(err, data) {
  232. if (err) return console.error(err);
  233. var myList = JSON.parse(data.GET);
  234. if (Array.isArray(myList)){
  235. for (let i = 0; i < myList.length; i++) {
  236. if (i % 2 == 0) {
  237. myList[i] = "👨: " + myList[i].replace(/\n/g, '<br>');
  238. } else {
  239. myList[i] = "🤖: " + myList[i].replace(/\n/g, '<br>');
  240. }
  241. }
  242. showHistoryLog(myList.join("<br>"));
  243. }
  244. });
  245. });
  246. div.prepend(ul);
  247.  
  248. /*---Prompt---*/
  249. var ulPrompt = document.getElementById('myUlPrompt');
  250. if (ulPrompt) {
  251. div.removeChild(ulPrompt);
  252. }
  253. var prompt = makeAGroup("Prompt", data.KEYS.sort().reverse(), PromptPrefix, function(event, key) {
  254. //console.log('Item clicked:', data.KEYS[i]);
  255. // Load data after saving
  256. load(key, function(err, data) {
  257. if (err) return console.error(err);
  258. var prompt = JSON.parse(data.GET);
  259. var textarea = document.getElementById('prompt-textarea');
  260. textarea.textContent = prompt;
  261. });
  262. });
  263. div.prepend(prompt);
  264. if (!ulPrompt) {
  265. var textarea = document.getElementById('prompt-textarea');
  266. var button = document.createElement('button');
  267. 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>';
  268. //button.style.color = 'white';
  269. button.style.position = 'absolute';
  270. button.style.right = '50px';
  271. button.style.top = '20px';
  272. button.style.textAlign = 'center';
  273. //button.style.border = '1px solid grey';
  274. button.style.marginLeft = '10px';
  275. //button.style.backgroundColor = '#268BD2';
  276. //var bottomdiv = document.querySelectorAll('div.relative.pb-3.pt-2.text-center.text-xs.text-gray-600')[0];
  277. //bottomdiv.appendChild(button);
  278. button.title = 'Save the message as a prompt';
  279.  
  280. textarea.parentNode.appendChild(button);
  281.  
  282. button.addEventListener('click', function() {
  283. var textarea = document.getElementById('prompt-textarea');
  284. save(PromptPrefix, textarea.textContent, function(err, response) {
  285. if (err) return console.error(err);
  286. });
  287.  
  288. InitPanel();
  289. });
  290. }
  291.  
  292.  
  293. });
  294. /*Remote Offical*/
  295. await sleep(2000);
  296. const olElements = document.querySelectorAll('ol');
  297. olElements.forEach(ol => {
  298. // First remove all existing children
  299. while (ol.firstChild) {
  300. ol.removeChild(ol.firstChild);
  301. }
  302. });
  303.  
  304. }
  305.  
  306. function redisError(){
  307. var div = document.querySelectorAll('div.flex-shrink-0.overflow-x-hidden')[0];
  308. const ul = document.createElement('ul');
  309. div.prepend(ul);
  310. const li = document.createElement('li');
  311. li.textContent = "There is no record. Please verify if webdis AND redis-server has been started! Just run `webdis.sh start` to start.";
  312. li.style.color = 'white';
  313. ul.appendChild(li);
  314. }
  315.  
  316. function showHistoryLog(text) {
  317. // Check if the div with a specific id already exists
  318. var existingDiv = document.getElementById('historyLog');
  319.  
  320. if (existingDiv) {
  321. // If the div exists, update the messageSpan's HTML content
  322. var messageSpan = existingDiv.querySelector('.message-span');
  323. if (messageSpan) {
  324. messageSpan.innerHTML = text;
  325. }
  326. existingDiv.style.display = '';
  327. } else {
  328. // If the div doesn't exist, create a new div and append it to the body
  329. var hoverBox = document.createElement('div');
  330. hoverBox.id = 'historyLog'; // Set a unique id for the div
  331. hoverBox.style.position = 'fixed';
  332. hoverBox.style.top = '50%';
  333. hoverBox.style.left = '50%';
  334. hoverBox.style.transform = 'translate(-50%, -50%)';
  335. hoverBox.style.zIndex = '10000';
  336. hoverBox.style.padding = '10px';
  337. hoverBox.style.width = '1000px';
  338. hoverBox.style.height = '800px';
  339. hoverBox.style.backgroundColor = 'white';
  340. hoverBox.style.border = '1px solid black';
  341. hoverBox.style.borderRadius = '5px';
  342. hoverBox.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.5)';
  343. hoverBox.style.overflow = 'hidden'; // Hide content overflow
  344.  
  345. // Create a container div for the content and close button
  346. var contentContainer = document.createElement('div');
  347. contentContainer.style.overflowY = 'auto'; // Make content scrollable
  348. //contentContainer.style.resize = 'both'; // Enable resizing
  349. contentContainer.style.height = 'calc(100% - 40px)'; // Adjust height for close button
  350.  
  351. // Create a span element to hold the message
  352. var messageSpan = document.createElement('span');
  353. messageSpan.innerHTML = text;
  354. messageSpan.className = 'message-span'; // Add a class for easy selection
  355. messageSpan.style.display = 'block';
  356. messageSpan.style.marginTop = '20px';
  357.  
  358. // Create a button element to close the hover box
  359. var closeButton = document.createElement('button');
  360. closeButton.textContent = '✖';
  361. closeButton.style.position = 'absolute';
  362. closeButton.style.top = '10px';
  363. closeButton.style.right = '10px';
  364. closeButton.addEventListener('click', function () {
  365. hoverBox.style.display = 'none';
  366. });
  367.  
  368. // Add the message span and close button to the content container
  369. contentContainer.appendChild(messageSpan);
  370. contentContainer.appendChild(closeButton);
  371.  
  372. // Add the content container to the hover box
  373. hoverBox.appendChild(contentContainer);
  374.  
  375. document.addEventListener('click', function (event) {
  376. if (!hoverBox.contains(event.target) && event.target !== hoverBox) {
  377. hoverBox.style.display = 'none';
  378. event.stopPropagation();
  379. }
  380. });
  381.  
  382. // Add the hover box to the body of the document
  383. document.body.appendChild(hoverBox);
  384. }
  385. }
  386.  
  387.  
  388.  
  389.  
  390.  
  391.  
  392.  
  393. InitPanel();
  394.  
  395.  
  396.  
  397.  
  398. document.addEventListener('keydown', async function(event) {
  399. if (event.key === 'Enter' || (event.metaKey && event.key === 'r')) {
  400. // Usage examples
  401. while(true){
  402. var thisdialog = getCurrentDialogContent();
  403. save(HistoryPrefix, thisdialog, function(err, response) {
  404. if (err) return console.error(err);
  405. console.log('Save response:', response);
  406. if(!thisInHistory){
  407. InitPanel();
  408. thisInHistory = true;
  409. }
  410. });
  411. const divElement = document.querySelector('div.flex.items-center.md\\:items-end');
  412. if (divElement == undefined || divElement.textContent == undefined || divElement.textContent != 'Stop generating'){
  413. break;
  414. }
  415. await sleep(2000);
  416. }
  417. }
  418. if (event.key === 'Escape') {
  419. var existingDiv = document.getElementById('historyLog');
  420. if (existingDiv) {
  421. existingDiv.style.display = 'none';
  422. }
  423. }
  424. });
  425.  
  426.  
  427.  
  428.  
  429. })();