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-15 提交的版本,查看 最新版本

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