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.

As of 15.09.2023. See ბოლო ვერსია.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         ChatGPTHelper
// @namespace    http://tampermonkey.net/
// @version      0.6
// @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.
// @author       maple
// @match        https://chat.openai.com/*
// @grant        GM_xmlhttpRequest
// @license      GPL-3.0-or-later
// ==/UserScript==

(function() {
    'use strict';

    const WEBREDIS_ENDPOINT = "http://127.0.0.1:7379";
    var thisInHistory = false;
    var HistoryPrefix = "HISTORY";
    var PromptPrefix = "USERPROMPT";
    var redisHistoryName = "";


    function load(key, callback) {
        key = encodeURIComponent(key);
        GM_xmlhttpRequest({
            method: "GET",
            url: `${WEBREDIS_ENDPOINT}/GET/${key}`,
            onload: function(response) {
                callback(null, JSON.parse(response.responseText));
            },
            onerror: function(error) {
                callback(error, null);
            }
        });
    }

    function save(nameprefix, data, callback) {
        var strdata;
        var dname;
        var currentTimestamp = "";
        var redisname;
        if (Array.isArray(data)) {// history
            strdata = JSON.stringify(data.map(function(element) {
                return element.innerHTML;
            }));
            dname = data[0].innerText.substring(0, 50).trim();
            var date = new Date();
            currentTimestamp = date.toLocaleString().substring(0, 19);
            if (redisHistoryName == ""){
                console.log("Make a name")
                redisHistoryName = encodeURIComponent(nameprefix + currentTimestamp + "\n" + dname);
            }
            redisname = redisHistoryName
        } else {//prompt
            strdata = JSON.stringify(data);
            dname = data.substring(0, 50).trim();
            redisname = encodeURIComponent(nameprefix + currentTimestamp + "\n" + dname);
        }
        if (strdata && strdata.length < 3){
            return;
        }

        console.log(redisname);
        GM_xmlhttpRequest({
            method: "GET",
            url: `${WEBREDIS_ENDPOINT}/SET/${redisname}/${encodeURIComponent(strdata)}`,
            onload: function(response) {
                console.log(response);
                callback(null, response.responseText);
            },
            onerror: function(error) {
                console.log(error);
                callback(error, null);
            }
        });
    }

    function remove(key, callback) {
        key = encodeURIComponent(key);
        GM_xmlhttpRequest({
            method: "GET",
            url: `${WEBREDIS_ENDPOINT}/DEL/${key}`,
            onload: function(response) {
                callback(null, JSON.parse(response.responseText));
            },
            onerror: function(error) {
                callback(error, null);
            }
        });
    }


    function getAllRedisKeys(callback) {
        GM_xmlhttpRequest({
            method: "GET",
            url: `${WEBREDIS_ENDPOINT}/KEYS/*`, // Update this to your actual endpoint
            onload: function(response) {
                if (response.responseText == undefined){
                    redisError();
                    return;
                }
                callback(null, JSON.parse(response.responseText));
            },
            onerror: function(error) {
                callback(error, null);
            }
        });
    }


    function getCurrentDialogContent() {
        // Get all div elements with the specified class
        const divsWithSpecificClass = document.querySelectorAll('div.flex.flex-grow.flex-col.gap-3.max-w-full');
        // Create an array to store the text content of each div
        const divTexts = [];

        // Loop through the NodeList and get the text content of each div
        divsWithSpecificClass.forEach(div => {
            var textContent = [];
            divTexts.push(div);
        });

        // Return the array of text contents
        return divTexts;
    }

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function showHistory(dataList) {
        var targetDiv = document.querySelector('div.flex-1.overflow-hidden > div > div > div');


        dataList.forEach(data => {
            var newDiv = document.createElement('div');
            newDiv.textContent = data;
            targetDiv.appendChild(newDiv);
        });
    }

    function makeAGroup(name, keys, elementfilter, clickcallback){
        const div = document.createElement('div');
        const ul = document.createElement('ul');
        ul.style.overflowY = 'auto';
        ul.style.maxHeight = '500px';
        var eid = "myUl" + name
        div.id = eid; // Setting an ID to the ul element
        var h2 = document.createElement('h2');
        h2.innerText = name;
        h2.style.color = 'white';
        h2.style.textAlign = "center";
        div.append(h2);
        for (let i = 0; i < keys.length; i++) {
            const li = document.createElement('li');
            if(!keys[i].startsWith(elementfilter)){
                continue;
            }
            li.innerHTML = keys[i].substring(elementfilter.length, keys[i].length).replace(/\n/g, '<br>');;
            li.style.color = 'white';
            // Apply CSS styles for the rounded rectangle
            li.style.border = '1px solid grey'; // Add a border
            li.style.borderRadius = '10px'; // Adjust the horizontal and vertical radii to create rounded corners
            li.style.padding = '5px'; // Add some padding to make it visually appealing
            li.style.position = 'relative';
            li.addEventListener('mouseenter', function() {
                li.style.backgroundColor = 'grey'; // Change to your desired background color
            });

            li.addEventListener('mouseleave', function() {
                li.style.backgroundColor = ''; // Reset to the original background color
            });
            li.addEventListener('click', (event) => {clickcallback(event, keys[i]);});

            // add close
            // Create close button
            const closeButton = document.createElement('span');
            closeButton.textContent = '✖';
            closeButton.style.position = 'absolute';
            closeButton.style.top = '5px';
            closeButton.style.right = '5px';
            closeButton.style.color = 'white';
            closeButton.style.cursor = 'pointer'; // Set cursor to pointer to indicate it's clickable

            // Add event listener for the close button
            closeButton.addEventListener('click', async (event) => {
                // Your callback function here
                event.stopPropagation();
                remove(keys[i], function (){});
                await sleep(500);
                InitPanel();
            });

            // Add close button to li
            li.appendChild(closeButton);
            ul.appendChild(li);
        }
        div.append(ul);
        return div;
    }


    async function InitPanel() {

        getAllRedisKeys(function(error, data) {
            if (error) {
                redisError();
                console.error('An error occurred:', error);
                return;
            }

            const ol = document.querySelectorAll('ol')[2];
            var div = document.querySelectorAll('div.flex-shrink-0.overflow-x-hidden')[0];
            const ulExisting = document.getElementById('myUlHistory');
            if (ulExisting) {
                div.removeChild(ulExisting, function (){});
            }
            if (data.KEYS.length == 0){
                redisError();
            }
            var ul = makeAGroup("History", data.KEYS.sort().reverse(), HistoryPrefix, function(event, key) {
                //console.log('Item clicked:', data.KEYS[i]);
                // Load data after saving
                load(key, function(err, data) {
                    if (err) return console.error(err);
                    var myList = JSON.parse(data.GET);
                    if (Array.isArray(myList)){
                        for (let i = 0; i < myList.length; i++) {
                            if (i % 2 == 0) {
                                myList[i] = "👨: " + myList[i].replace(/\n/g, '<br>');
                            } else {
                                myList[i] = "🤖: " + myList[i].replace(/\n/g, '<br>');
                            }
                        }
                        showHistoryLog(myList.join("<br>"));
                    }
                });
            });
            div.prepend(ul);

            /*---Prompt---*/
            var ulPrompt = document.getElementById('myUlPrompt');
            if (ulPrompt) {
                div.removeChild(ulPrompt);
            }
            var prompt = makeAGroup("Prompt", data.KEYS.sort().reverse(), PromptPrefix, function(event, key) {
                //console.log('Item clicked:', data.KEYS[i]);
                // Load data after saving
                load(key, function(err, data) {
                    if (err) return console.error(err);
                    var prompt = JSON.parse(data.GET);
                    var textarea = document.getElementById('prompt-textarea');
                    textarea.value = prompt;
                });
            });
            div.prepend(prompt);
            if (!ulPrompt) {
                var button = document.createElement('button');
                button.innerText = 'Save Message As Prompt';
                button.style.color = 'white';
                button.style.position = 'relative';
                button.style.textAlign = 'center';
                button.style.border = '1px solid white';
                button.style.backgroundColor = '#268BD2';
                var bottomdiv = document.querySelectorAll('div.relative.pb-3.pt-2.text-center.text-xs.text-gray-600')[0];
                bottomdiv.appendChild(button);

                button.addEventListener('click', function() {
                var textarea = document.getElementById('prompt-textarea');
                save(PromptPrefix, textarea.textContent, function(err, response) {
                    if (err) return console.error(err);
                });
                InitPanel();
            });
            }


        });
        /*Remote Offical*/
        await sleep(2000);
        const olElements = document.querySelectorAll('ol');
        olElements.forEach(ol => {
            // First remove all existing children
            while (ol.firstChild) {
                ol.removeChild(ol.firstChild);
            }
        });

    }

    function redisError(){
        var div = document.querySelectorAll('div.flex-shrink-0.overflow-x-hidden')[0];
        const ul = document.createElement('ul');
        div.prepend(ul);
        const li = document.createElement('li');
        li.textContent = "There is no record. Please verify if webdis AND redis-server has been started! Just run `webdis.sh start` to start.";
        li.style.color = 'white';
        ul.appendChild(li);
    }

    function showHistoryLog(text) {
        // Check if the div with a specific id already exists
        var existingDiv = document.getElementById('historyLog');

        if (existingDiv) {
            // If the div exists, update the messageSpan's HTML content
            var messageSpan = existingDiv.querySelector('.message-span');
            if (messageSpan) {
                messageSpan.innerHTML = text;
            }
            existingDiv.style.display = '';
        } else {
            // If the div doesn't exist, create a new div and append it to the body
            var hoverBox = document.createElement('div');
            hoverBox.id = 'historyLog'; // Set a unique id for the div
            hoverBox.style.position = 'fixed';
            hoverBox.style.top = '50%';
            hoverBox.style.left = '50%';
            hoverBox.style.transform = 'translate(-50%, -50%)';
            hoverBox.style.zIndex = '10000';
            hoverBox.style.padding = '10px';
            hoverBox.style.width = '1000px';
            hoverBox.style.height = '800px';
            hoverBox.style.backgroundColor = 'white';
            hoverBox.style.border = '1px solid black';
            hoverBox.style.borderRadius = '5px';
            hoverBox.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.5)';
            hoverBox.style.overflow = 'hidden'; // Hide content overflow

            // Create a container div for the content and close button
            var contentContainer = document.createElement('div');
            contentContainer.style.overflowY = 'auto'; // Make content scrollable
            //contentContainer.style.resize = 'both'; // Enable resizing
            contentContainer.style.height = 'calc(100% - 40px)'; // Adjust height for close button

            // Create a span element to hold the message
            var messageSpan = document.createElement('span');
            messageSpan.innerHTML = text;
            messageSpan.className = 'message-span'; // Add a class for easy selection
            messageSpan.style.display = 'block';
            messageSpan.style.marginTop = '20px';

            // Create a button element to close the hover box
            var closeButton = document.createElement('button');
            closeButton.textContent = '✖';
            closeButton.style.position = 'absolute';
            closeButton.style.top = '10px';
            closeButton.style.right = '10px';
            closeButton.addEventListener('click', function () {
                hoverBox.style.display = 'none';
            });

            // Add the message span and close button to the content container
            contentContainer.appendChild(messageSpan);
            contentContainer.appendChild(closeButton);

            // Add the content container to the hover box
            hoverBox.appendChild(contentContainer);

            document.addEventListener('click', function (event) {
                if (!hoverBox.contains(event.target) && event.target !== hoverBox) {
                    hoverBox.style.display = 'none';
                }
            });

            // Add the hover box to the body of the document
            document.body.appendChild(hoverBox);
        }
    }







    InitPanel();




    document.addEventListener('keydown', async function(event) {
        if (event.key === 'Enter' || (event.metaKey && event.key === 'r')) {
            // Usage examples
            while(true){
                var thisdialog = getCurrentDialogContent();
                save(HistoryPrefix, thisdialog, function(err, response) {
                    if (err) return console.error(err);
                    console.log('Save response:', response);
                    if(!thisInHistory){
                        InitPanel();
                        thisInHistory = true;
                    }
                });
                const divElement = document.querySelector('div.flex.items-center.md\\:items-end');
                if (divElement == undefined || divElement.textContent == undefined || divElement.textContent != 'Stop generating'){
                    break;
                }
                await sleep(2000);
            }
        }
        if (event.key === 'Escape') {
            var existingDiv = document.getElementById('historyLog');
            if (existingDiv) {
                existingDiv.style.display = 'none';
            }
        }
    });




})();