ChatGPT Visual Enhancements with message Navigation

Enhances the ChatGPT interface by visually highlighting the active chat link, styling message boxes, and adding top-centered navigation buttons to traverse messages.

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         ChatGPT Visual Enhancements with message Navigation
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  Enhances the ChatGPT interface by visually highlighting the active chat link, styling message boxes, and adding top-centered navigation buttons to traverse messages.
// @author       noushadBug
// @match        https://chatgpt.com/*
// @icon         https://chatgpt.com/favicon.ico
// @license      MIT
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // Initialize variables for navigation
    let messageElements = [];
    let currentIndex = -1;

    // Create and style navigation buttons
    function createNavigationButtons() {
        const navContainer = document.createElement('div');
        navContainer.id = 'custom-nav-container';
        navContainer.style.position = 'absolute';
        navContainer.style.top = '10px';
        navContainer.style.left = '50%';
        navContainer.style.transform = 'translateX(-50%)';
        navContainer.style.display = 'flex';
        navContainer.style.gap = '10px';
        navContainer.style.zIndex = '1000';

        const prevButton = document.createElement('button');
        prevButton.id = 'custom-prev-button';
        prevButton.innerHTML = '‹';
        styleNavButton(prevButton, 'Previous Message');

        const nextButton = document.createElement('button');
        nextButton.id = 'custom-next-button';
        nextButton.innerHTML = '›';
        styleNavButton(nextButton, 'Next Message');

        navContainer.appendChild(prevButton);
        navContainer.appendChild(nextButton);
        document.body.appendChild(navContainer);

        // Add event listeners
        prevButton.addEventListener('click', () => navigateMessages(-1));
        nextButton.addEventListener('click', () => navigateMessages(1));
    }

    // Style individual navigation buttons
    function styleNavButton(button, ariaLabel) {
        // Apply the new styles as per your specifications
        button.style.padding = '4px 19px';
        button.style.fontSize = '20px';
        button.style.border = 'none';
        button.style.borderRadius = '50px';
        button.style.cursor = 'pointer';
        button.style.backgroundColor = 'rgb(65, 76, 59)';
        button.style.color = 'rgb(255, 255, 255)';
        button.style.boxShadow = 'rgba(0, 0, 0, 0.3) 0px 2px 5px';
        button.style.transition = 'background-color 0.3s, transform 0.2s';
        button.style.transform = 'scale(1)';

        // Accessibility
        button.setAttribute('aria-label', ariaLabel);

        // Hover effects
        button.addEventListener('mouseover', () => {
            button.style.backgroundColor = '#5a6e57';
            button.style.transform = 'scale(1.1)';
        });

        button.addEventListener('mouseout', () => {
            button.style.backgroundColor = 'rgb(65, 76, 59)';
            button.style.transform = 'scale(1)';
        });
    }

    // Function to update the list of message elements
    function updateMessageElements() {
        // Select all message containers; adjust the selector if necessary
        messageElements = Array.from(document.querySelectorAll('[data-message-author-role]'));
    }

    // Function to navigate through messages
    function navigateMessages(direction) {
        if (messageElements.length === 0) return;

        // Remove highlight from the current message
        if (currentIndex >= 0 && currentIndex < messageElements.length) {
            const currentMessage = messageElements[currentIndex];
            currentMessage.style.border = '';
            currentMessage.style.boxShadow = '';
        }

        // Update the current index
        currentIndex += direction;
        if (currentIndex < 0) {
            currentIndex = messageElements.length - 1; // Wrap to last
        } else if (currentIndex >= messageElements.length) {
            currentIndex = 0; // Wrap to first
        }

        const targetMessage = messageElements[currentIndex];

        // Ensure the targetMessage exists
        if (targetMessage) {
            // Scroll to the message container, aligning it to the top
            targetMessage.scrollIntoView({ behavior: 'smooth', block: 'start' });

            // Highlight the target message
            targetMessage.style.border = '2px solid #2f2f2f';
            targetMessage.style.boxShadow = 'rgb(123 166 175) 0px 0px 10px';

            // Focus on the message for better accessibility
            targetMessage.setAttribute('tabindex', '-1'); // Make it focusable
            targetMessage.focus({ preventScroll: true });
        }
    }

    // Function to update the active <li> state
    function updateActiveState() {
        const listItems = document.querySelectorAll('li.relative');
        const currentPath = window.location.pathname;

        listItems.forEach(li => {
            const anchor = li.querySelector('a'); // Find the <a> inside <li>
            if (anchor && anchor.getAttribute('href') === currentPath) {
                // If href matches the current path, style it as active
                li.classList.add('active');
                li.style.background = 'black';
                li.style.borderRadius = '20px';
            } else {
                // Remove active styles from other <li> elements
                li.classList.remove('active');
                li.style.background = '';
                li.style.borderRadius = '';
            }
        });
    }

    // Function to style assistant messages
    function styleAssistantMessages() {
        const messages = document.querySelectorAll('[data-message-author-role="assistant"]');

        messages.forEach(message => {
            if (!message.classList.contains('custom-assistant-message-container')) {
                // Add custom class and apply styles
                message.classList.add('custom-assistant-message-container');
                message.style.background = '#414c3b';
                message.style.padding = '1em';
                message.style.borderRadius = '10px';
                message.style.color = '#ffffff'; // Ensure text is readable
                message.setAttribute('tabindex', '-1'); // Make it focusable
            }
        });
    }

    // Function to highlight user messages (optional enhancement)
    function styleUserMessages() {
        const userMessages = document.querySelectorAll('[data-message-author-role="user"]');

        userMessages.forEach(message => {
            if (!message.classList.contains('custom-user-message-container')) {
                // Add custom class and apply styles
                message.classList.add('custom-user-message-container');
                message.style.background = '#2a2a2a';
                message.style.padding = '1em';
                message.style.borderRadius = '10px';
                message.style.color = '#ffffff'; // Ensure text is readable
                message.setAttribute('tabindex', '-1'); // Make it focusable
            }
        });
    }

    // Debounce function to limit the rate of function execution
    function debounce(func, wait) {
        let timeout;
        return function (...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    }

    // Observe URL and DOM changes with debounced callback
    const observer = new MutationObserver(debounce(() => {
        updateActiveState();
        styleAssistantMessages();
        styleUserMessages();
        updateMessageElements();
    }, 300));

    // Start observing changes to the document body
    observer.observe(document.body, { childList: true, subtree: true });

    // Initial setup
    updateActiveState();
    styleAssistantMessages();
    styleUserMessages();
    updateMessageElements();
    createNavigationButtons();


    // Clean up when the script is removed
    window.addEventListener('unload', () => observer.disconnect());

    // Additional CSS Styling
    const otherCSS = `.h-header-height{background-color: var(--sidebar-surface-primary);}`;
    const styleTag = document.createElement('style');
    styleTag.textContent = otherCSS;
    document.head.appendChild(styleTag);
})();