🔰 ADK - ChatGPT QoL Buttons 🔰

Add navigation buttons to move between responses, display conversation numbers, auto-continue feature, and more QoL improvements with a glassy RetrAsh theme

目前為 2024-08-31 提交的版本,檢視 最新版本

// AsheshDevelopment
// Filename: ADK_ChatGPT_QoL_Buttons.js

// ==UserScript==
// @name         🔰 ADK - ChatGPT QoL Buttons 🔰
// @namespace    http://tampermonkey.net/
// @version      2024-08-31
// @description  Add navigation buttons to move between responses, display conversation numbers, auto-continue feature, and more QoL improvements with a glassy RetrAsh theme
// @author       Ashesh Development
// @match        https://chatgpt.com/*
// @icon         https://media.chatgptautocontinue.com/images/icons/openai/black/icon48.png?07bf6b7
// @icon64       https://media.chatgptautocontinue.com/images/icons/openai/black/icon64.png?07bf6b7
// @grant        none
// ==/UserScript==

(async () => {
    'use strict';

    function logDebug(message, type = 'info') {
        const styles = {
            info: 'color: #57C7FF; font-weight: bold; background: #1E1E1E; padding: 2px 4px; border-radius: 4px;',  // Soft Blue on Dark Gray
            warn: 'color: #F3F99D; font-weight: bold; background: #1E1E1E; padding: 2px 4px; border-radius: 4px;', // Pale Yellow on Dark Gray
            error: 'color: #FF5C57; font-weight: bold; background: #1E1E1E; padding: 2px 4px; border-radius: 4px;', // Soft Red on Dark Gray
            success: 'color: #5AF78E; font-weight: bold; background: #1E1E1E; padding: 2px 4px; border-radius: 4px;' // Bright Green on Dark Gray
        };
        console.log(`%c${message}`, styles[type]);
    }

    logDebug('Script initialized: ADK - ChatGPT QoL Buttons', 'success');

    const style = document.createElement('style');
    style.innerHTML = `
        [data-testid^="conversation-turn-"] {
            padding-top: 50px !important;
            position: relative;
        }
        .navigation-buttons {
            position: fixed;
            margin: -9px 0 0 14px;
        }
        .navigation-buttons button {
            margin: 5px;
            padding: 5px 15px;
            font-size: 16px;
            cursor: pointer;
            background: #1E1E1E; /* RetrAsh background */
            color: #E0E0E0; /* RetrAsh foreground */
            border: none;
            border-radius: 5px;
            transition: background 0.3s;
        }
        .navigation-buttons button:hover {
            background: #57C7FF; /* RetrAsh Soft Blue */
        }
        #continueBtn {
            background: #FF6AC1; /* RetrAsh Bright Pink */
            color: #1E1E1E; /* RetrAsh background */
        }
        #fullCodeBtn, #fullCodePlusBtn {
            background: #FF9F43; /* RetrAsh Warm Orange */
            color: #1E1E1E; /* RetrAsh background */
        }
        #copyLastCodeBlockBtn {
            background: #5AF78E; /* RetrAsh Bright Green */
            color: #1E1E1E; /* RetrAsh background */
        }
        .conversation-number {
            position: absolute;
            left: 18px;
            top: 60px;
            font-size: 16px;
            font-weight: bold;
            color: #FF5C57; /* RetrAsh Soft Red */
            z-index: 1;
            background: rgba(30, 30, 30, 0.8); /* RetrAsh background with transparency */
            padding: 9px 12px;
            border-radius: 7px;
            border: 1px solid #FF5C57; /* RetrAsh Soft Red */
            cursor: pointer;
        }
        .xl\\:max-w-\\[48rem\\] {
            max-width: 100%;
        }
        [class^="react-scroll-to-bottom--"]::-webkit-scrollbar {
            width: 40px;
        }
        [class^="react-scroll-to-bottom--"]::-webkit-scrollbar-thumb {
            min-height: 10%;
            background: #FF9F43; /* RetrAsh Warm Orange */
        }
        button.btn.relative.btn-neutral.group.w-full.whitespace-nowrap.rounded-xl.text-left.text-gray-700.dark\\:text-gray-300.md\\:whitespace-normal {
            display: none;
        }
        .md\\:items-end {
            align-items: flex-end;
            position: absolute;
            left: 0;
            bottom: -45px;
        }
        button.btn.relative.btn-neutral.whitespace-nowrap.-z-0.border-0.md\\:border {
            border-radius: 10px;
            background: #FF5C57; /* RetrAsh Soft Red */
            border: 1px solid #FF5C57; /* RetrAsh Soft Red */
            color: #E0E0E0; /* RetrAsh Foreground */
        }
        button.btn.relative.btn-neutral.whitespace-nowrap.-z-0.border-0.md\\:border:hover {
            background: #FF6E67; /* RetrAsh Light Red */
            border: 1px solid #FF6E67; /* RetrAsh Light Red */
            transition: 0.25s;
        }
        button.btn.relative.btn-neutral.whitespace-nowrap.-z-0.border-0.md\\:border .flex.w-full.gap-2.items-center.justify-center:after {
            content: "last response";
        }
        .dark.bg-gray-950.rounded-md.border-\\[0\\.5px\\].border-token-border-medium {
            display: flex;
            flex-direction: column;
            height: 100%;
        }
        .overflow-y-auto.p-4 {
            order: 1; /* Content comes first */
            flex-grow: 1; /* Ensure content takes up available space */
        }
        .flex.items-center.relative.text-token-text-secondary.bg-token-main-surface-secondary.px-4.py-2.text-xs.font-sans.justify-between.rounded-t-md {
            order: 2; /* Top bar comes second */
            direction: rtl;
            background: darkslateblue;
            color: #F3F99D; /* RetrAsh Pale Yellow */
        }
        button.flex.gap-1.items-center {
            width: 200px;
            display: flex;
            justify-content: center;
            background: #D3A52D; /* Dark Yellow */
            padding: 10px;
            position: relative;
            color: #000; /* Contrast text color */
        }
    `;
    document.head.appendChild(style);

    let responses = [];

    function logTestIdElements() {
        responses = Array.from(document.querySelectorAll('[data-testid^="conversation-turn-"]'));
        console.log('Total responses found:', responses.length);
        responses.forEach((response, index) => {
            console.log('Response', index, response.getAttribute('data-testid'));
            addConversationNumber(response, index + 1);
        });
    }

    function addConversationNumber(element, number) {
        let numberElement = element.querySelector('.conversation-number');
        if (!numberElement) {
            numberElement = document.createElement('div');
            numberElement.className = 'conversation-number';
            numberElement.textContent = number;
            element.insertBefore(numberElement, element.firstChild);
        }
        logDebug(`Conversation number ${number} added to the response element.`, 'info');
    }

    function addNavigationButtons() {
        const targetDiv = document.querySelector('.relative.px-2.py-2.text-center.text-xs.text-token-text-secondary.md\\:px-\\[60px\\]');
        if (targetDiv) {
            let navContainer = document.querySelector('.navigation-buttons');
            if (!navContainer) {
                navContainer = document.createElement('div');
                navContainer.className = 'navigation-buttons';
                navContainer.innerHTML = `
                    <button id="prevBtn"><</button>
                    <button id="nextBtn">></button>
                    <button id="fullCodeBtn">Full Code</button>
                    <button id="fullCodePlusBtn">Full Code+</button>
                    <button id="continueBtn">Continue>></button>
                    <button id="copyLastCodeBlockBtn">Copy Last Code Block</button>
                `;
                targetDiv.insertBefore(navContainer, targetDiv.firstChild);

                document.getElementById("nextBtn").addEventListener("click", function () {
                    logTestIdElements();
                    const closestIndex = findClosestResponseIndex();
                    if (closestIndex < responses.length - 1) {
                        const nextIndex = closestIndex + 1;
                        responses[nextIndex].scrollIntoView({ behavior: "smooth" });
                    }
                });

                document.getElementById("prevBtn").addEventListener("click", function () {
                    logTestIdElements();
                    const closestIndex = findClosestResponseIndex();
                    if (closestIndex > 0) {
                        const prevIndex = closestIndex - 1;
                        responses[prevIndex].scrollIntoView({ behavior: "smooth" });
                    }
                });

                document.getElementById("continueBtn").addEventListener("click", function () {
                    typeAndSendMessage("continue");
                });

                document.getElementById("fullCodeBtn").addEventListener("click", function () {
                    typeAndSendMessage("Write the full script without the placeholders and no commented lines. Just write everything without extra line and no explanations. I want the script ready to copy and paste into my IDE and I need it production ready.");
                });

                document.getElementById("fullCodePlusBtn").addEventListener("click", function () {
                    typeAndSendMessage("Write the full script with debug logs colorised using DRACULA neon vibrant color scheme and base it on functions and usages, the debug logs must be very detailed and easy to understand and the script must include documentations and guides within the script and also no explanations and ready to copy and paste into my Jetbrains Rider IDE and I need it production ready. Always indicate 'AsheshDevelopment' in the first line of the script as comment and the filename also as comment on the 2nd line of the script");
                });

                document.getElementById("copyLastCodeBlockBtn").addEventListener("click", function () {
                    simulateCopyLastCodeBlock();
                });

                logDebug('Navigation buttons added and event listeners attached.', 'success');
            }
        } else {
            console.error('Target div not found');
        }
    }

    function typeAndSendMessage(message) {
        const textarea = document.querySelector('textarea');
        if (textarea) {
            textarea.value = message;
            const inputEvent = new Event('input', { bubbles: true });
            textarea.dispatchEvent(inputEvent);

            setTimeout(() => {
                const enterEvent = new KeyboardEvent('keydown', {
                    bubbles: true,
                    cancelable: true,
                    key: 'Enter',
                    code: 'Enter',
                    keyCode: 13,
                    which: 13,
                });
                textarea.dispatchEvent(enterEvent);
                logDebug(`Message sent: ${message}`, 'info');
            }, 250); // 250ms delay before pressing Enter
        } else {
            logDebug('Textarea not found.', 'error');
        }
    }

    function findClosestResponseIndex() {
        let closestIndex = 0;
        let minDistance = Infinity;
        const viewportTop = window.scrollY;
        const viewportBottom = viewportTop + window.innerHeight;
        responses.forEach((response, index) => {
            const rect = response.getBoundingClientRect();
            const responseTop = rect.top + window.scrollY;
            const responseBottom = rect.bottom + window.scrollY;
            const distance = Math.min(
                Math.abs(viewportTop - responseTop),
                Math.abs(viewportBottom - responseBottom)
            );
            if (distance < minDistance) {
                minDistance = distance;
                closestIndex = index;
            }
        });
        return closestIndex;
    }

    function simulateCopyLastCodeBlock() {
        const event = new KeyboardEvent('keydown', {
            bubbles: true,
            cancelable: true,
            key: 'Shift',
            code: 'ShiftLeft',
            keyCode: 16,
            ctrlKey: true,
            shiftKey: true,
        });
        document.dispatchEvent(event);

        const event2 = new KeyboardEvent('keydown', {
            bubbles: true,
            cancelable: true,
            key: ';',
            code: 'Semicolon',
            keyCode: 186,
            ctrlKey: true,
            shiftKey: true,
        });
        document.dispatchEvent(event2);

        logDebug('Simulated copy last code block action.', 'info');
    }

    function initialize() {
        logTestIdElements();
        addNavigationButtons();
    }

    setTimeout(() => {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initialize);
        } else {
            initialize();
        }
    }, 2000);

    setInterval(addNavigationButtons, 2000);

})();