ChatGPT/GROK/DEEPSEEK Scroll Navigator

Adds a smart scroll button for easy navigation in AI chat interfaces

目前為 2025-08-21 提交的版本,檢視 最新版本

// ==UserScript==
// @name         ChatGPT/GROK/DEEPSEEK Scroll Navigator
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Adds a smart scroll button for easy navigation in AI chat interfaces
// @author       Lepturus
// @match        *://chatgpt.com/*
// @match        *://chat.deepseek.com/*
// @match        *://grok.com/*
// @icon         https://chatgpt.com/favicon.ico
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Configuration for different platforms
    const platformConfig = {
        chatgpt: {
            container: '.flex.basis-auto.flex-col.grow.overflow-hidden div.relative.h-full div.overflow-y-auto',
            name: 'ChatGPT'
        },
        grok: {
            container: '.scrollbar-gutter-stable',
            name: 'Grok'
        },
        deepseek: {
            container: '.scrollable',
            name: 'DeepSeek'
        }
    };

    // Main initialization function
    function initScrollNavigator() {
        let currentPlatform = null;

        // Determine current platform
        if (/chatgpt/.test(location.hostname)) currentPlatform = 'chatgpt';
        else if (/grok/.test(location.hostname)) currentPlatform = 'grok';
        else if (/deepseek/.test(location.hostname)) currentPlatform = 'deepseek';

        if (!currentPlatform) return;

        const config = platformConfig[currentPlatform];
        let scrollButton = null;
        let chatContainer = null;
        let isInitialized = false;

        // Create scroll button
        function createScrollButton() {
            if (document.getElementById('scrollNavigatorBtn')) return;

            scrollButton = document.createElement('div');
            scrollButton.id = 'scrollNavigatorBtn';
            scrollButton.innerHTML = '⬆';
            scrollButton.title = `Scroll to bottom (${config.name})`;

            // Apply styles
            Object.assign(scrollButton.style, {
                position: 'fixed',
                bottom: '20px',
                right: '20px',
                width: '45px',
                height: '45px',
                lineHeight: '45px',
                textAlign: 'center',
                backgroundColor: 'rgba(0, 0, 0, 0.7)',
                color: 'white',
                borderRadius: '50%',
                fontSize: '24px',
                cursor: 'pointer',
                zIndex: '10000',
                opacity: '0',
                transition: 'opacity 0.3s, transform 0.2s',
                transform: 'scale(0.8)',
                pointerEvents: 'none',
                boxShadow: '0 2px 10px rgba(0,0,0,0.3)'
            });

            document.body.appendChild(scrollButton);

            // Button click handler
            scrollButton.addEventListener('click', handleScrollClick);

            // Mouse events for showing/hiding
            scrollButton.addEventListener('mouseenter', () => {
                scrollButton.style.opacity = '1';
                scrollButton.style.transform = 'scale(1)';
            });

            scrollButton.addEventListener('mouseleave', () => {
                if (!isMouseNearButton()) {
                    scrollButton.style.opacity = '0';
                    scrollButton.style.transform = 'scale(0.8)';
                }
            });

            // Show button when mouse approaches screen edge
            document.addEventListener('mousemove', (e) => {
                const rect = scrollButton.getBoundingClientRect();
                const buttonCenterX = rect.left + rect.width / 2;
                const buttonCenterY = rect.top + rect.height / 2;

                const distance = Math.sqrt(
                    Math.pow(e.clientX - buttonCenterX, 2) +
                    Math.pow(e.clientY - buttonCenterY, 2)
                );

                if (distance < 120) {
                    scrollButton.style.opacity = '1';
                    scrollButton.style.transform = 'scale(1)';
                    scrollButton.style.pointerEvents = 'auto';
                } else if (distance > 150 && !scrollButton.matches(':hover')) {
                    scrollButton.style.opacity = '0';
                    scrollButton.style.transform = 'scale(0.8)';
                    scrollButton.style.pointerEvents = 'none';
                }
            });
        }

        // Check if mouse is near button
        function isMouseNearButton() {
            const rect = scrollButton.getBoundingClientRect();
            const buttonCenterX = rect.left + rect.width / 2;
            const buttonCenterY = rect.top + rect.height / 2;

            return (event) => {
                const distance = Math.sqrt(
                    Math.pow(event.clientX - buttonCenterX, 2) +
                    Math.pow(event.clientY - buttonCenterY, 2)
                );
                return distance < 100;
            };
        }

        // Handle scroll button click
        function handleScrollClick() {
            if (!chatContainer) return;

            const isNearTop = chatContainer.scrollTop < 100;

            if (isNearTop) {
                chatContainer.scrollTo({ top: chatContainer.scrollHeight, behavior: 'smooth' });
                scrollButton.innerHTML = '⬆';
                scrollButton.title = 'Scroll to top';
            } else {
                chatContainer.scrollTo({ top: 0, behavior: 'smooth' });
                scrollButton.innerHTML = '⬇';
                scrollButton.title = 'Scroll to bottom';
            }
        }

        // Find chat container
        function findChatContainer() {
            try {
                if (currentPlatform === 'deepseek') {
                    const containers = document.querySelectorAll(config.container);
                    if (containers.length > 1) return containers[1];
                    return containers[0];
                }
                return document.querySelector(config.container);
            } catch (error) {
                console.error('Error finding chat container:', error);
                return null;
            }
        }

        // Update button state based on scroll position
        function updateButtonState() {
            if (!chatContainer || !scrollButton) return;

            const scrollThreshold = 100;
            const isNearTop = chatContainer.scrollTop < scrollThreshold;
            const isNearBottom = chatContainer.scrollHeight - chatContainer.scrollTop - chatContainer.clientHeight < scrollThreshold;

            if (isNearTop) {
                scrollButton.innerHTML = '⬇';
                scrollButton.title = 'Scroll to bottom';
            } else if (isNearBottom) {
                scrollButton.innerHTML = '⬆';
                scrollButton.title = 'Scroll to top';
            } else {
                scrollButton.innerHTML = '⬇';
                scrollButton.title = 'Scroll to bottom';
            }
        }

        // Initialize the script
        function initialize() {
            if (isInitialized) return;

            chatContainer = findChatContainer();
            if (!chatContainer) {
                setTimeout(initialize, 1000);
                return;
            }

            createScrollButton();

            // Add scroll listener to update button state
            chatContainer.addEventListener('scroll', updateButtonState);

            // Initial update
            updateButtonState();

            isInitialized = true;
            console.log(`Scroll Navigator initialized for ${config.name}`);
        }

        // Start initialization
        initialize();

        // Reinitialize if DOM changes (for SPA navigation)
        const observer = new MutationObserver(() => {
            if (!chatContainer || !document.contains(chatContainer)) {
                isInitialized = false;
                chatContainer = null;
                initialize();
            }
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    // Start when DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initScrollNavigator);
    } else {
        initScrollNavigator();
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址