您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Ajoute des boutons flottants pour défiler vers le bas du chat et activer/désactiver l'auto-scroll.
// ==UserScript== // @name Mammouth.ai Floating Scroll Buttons // @name:en Mammouth.ai Floating Scroll Buttons // @name:es Botones flotantes de desplazamiento para Mammouth.ai // @namespace http://violentmonkey.github.io/ // @version 4.0.0 // @description Ajoute des boutons flottants pour défiler vers le bas du chat et activer/désactiver l'auto-scroll. // @description:en Adds floating buttons to scroll to the bottom of the chat and toggle auto-scroll. // @description:es Añade botones flotantes para desplazarse al final del chat y activar/desactivar el auto-scroll. // @author AI Script Assistant // @match https://mammouth.ai/app/* // @match https://mammouth.ai/* // @grant none // @license MIT // @compatible firefox Violentmonkey // @compatible chrome Violentmonkey // ==/UserScript== (function() { 'use strict'; /** * Localization dictionary for UI strings. * Provides translations for button tooltips in multiple languages. * @type {Object} */ const locales = { en: { scrollButton: "Scroll to bottom", autoScrollButton: "Toggle auto-scroll", }, fr: { scrollButton: "Défiler vers le bas", autoScrollButton: "Activer/désactiver le suivi automatique", }, es: { scrollButton: "Desplazarse al final", autoScrollButton: "Activar/desactivar auto-scroll", }, }; // Detect user's browser language and fallback to English. const userLang = (navigator.language || navigator.userLanguage || "en") .toLowerCase() .slice(0, 2); const messages = locales[userLang] || locales["en"]; // State variables to track scroll behavior and elements let autoScrollActive = true; // Auto-scroll is enabled by default let scrollableElement = null; // Reference to the scrollable container let scrollInterval = null; // Interval for checking new content let lastScrollHeight = 0; // Track content height changes /** * Creates and injects CSS styles for the floating buttons. * Styles are designed to ensure buttons are visible and accessible. */ function injectStyles() { // Check if styles are already injected if (document.getElementById('mammouth-scroll-styles')) { return; } const style = document.createElement('style'); style.id = 'mammouth-scroll-styles'; style.textContent = ` /* Container for floating buttons */ #mammouth-floating-buttons { position: fixed; bottom: 20px; right: 20px; display: flex; flex-direction: column; gap: 10px; z-index: 9999; } /* Individual button styling */ .mammouth-floating-button { width: 42px; height: 42px; border-radius: 50%; background: rgba(255, 255, 255, 0.9); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: transform 0.2s, background-color 0.2s; } /* Hover effect */ .mammouth-floating-button:hover { transform: scale(1.1); } /* Icon styling */ .mammouth-floating-button svg { width: 24px; height: 24px; color: #555; } /* Active state for auto-scroll button */ #auto-scroll-button.active { background-color: #2eae66; } #auto-scroll-button.active svg { color: white; } `; document.head.appendChild(style); console.log('[Mammouth Scroll] Styles injected'); } /** * Creates the floating buttons and adds them to the document. * Buttons are positioned in the bottom-right corner of the viewport. */ function createFloatingButtons() { // Check if buttons already exist to avoid duplicates if (document.getElementById('mammouth-floating-buttons')) { return; } // Create container for floating buttons const container = document.createElement('div'); container.id = 'mammouth-floating-buttons'; // Create scroll down button const scrollButton = document.createElement('button'); scrollButton.className = 'mammouth-floating-button'; scrollButton.id = 'scroll-button'; scrollButton.title = messages.scrollButton; scrollButton.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path> </svg> `; scrollButton.addEventListener('click', scrollToBottom); // Create auto-scroll toggle button const autoScrollButton = document.createElement('button'); autoScrollButton.className = 'mammouth-floating-button'; autoScrollButton.id = 'auto-scroll-button'; autoScrollButton.title = messages.autoScrollButton; autoScrollButton.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122"></path> </svg> `; autoScrollButton.addEventListener('click', toggleAutoScroll); // Set initial active state if (autoScrollActive) { autoScrollButton.classList.add('active'); } // Add buttons to container container.appendChild(autoScrollButton); container.appendChild(scrollButton); // Add container to document body document.body.appendChild(container); console.log('[Mammouth Scroll] Floating buttons created'); } /** * Finds the scrollable element containing the chat messages. * Uses multiple strategies to identify the correct element. * @returns {Element|null} - The scrollable element or null if not found */ function findScrollableElement() { console.log('[Mammouth Scroll] Searching for scrollable element...'); // Strategy 1: Find all visible scrollable elements const allElements = document.querySelectorAll('*'); const candidates = []; for (const element of allElements) { const style = window.getComputedStyle(element); // Look for elements that are scrollable and tall enough to be a chat container if ((style.overflowY === 'auto' || style.overflowY === 'scroll') && element.scrollHeight > element.clientHeight && element.clientHeight > 200) { candidates.push({ element, height: element.clientHeight, scrollHeight: element.scrollHeight }); } } // Sort candidates by scroll height (largest first) candidates.sort((a, b) => b.scrollHeight - a.scrollHeight); if (candidates.length > 0) { console.log('[Mammouth Scroll] Found scrollable candidates:', candidates); return candidates[0].element; } // Strategy 2: Try specific selectors that might match the chat container const specificSelectors = [ 'main .overflow-y-auto', '.conversation-container', '[role="log"]', '.messages-container', '.overflow-y-auto', '.scrollable' ]; for (const selector of specificSelectors) { const element = document.querySelector(selector); if (element) { console.log(`[Mammouth Scroll] Found element with selector "${selector}":`, element); return element; } } console.log('[Mammouth Scroll] No scrollable element found'); return null; } /** * Scrolls the chat element to the bottom. */ function scrollToBottom() { if (scrollableElement) { scrollableElement.scrollTop = scrollableElement.scrollHeight; console.log('[Mammouth Scroll] Scrolled to bottom'); } else { console.log('[Mammouth Scroll] Cannot scroll: no scrollable element found'); } } /** * Toggles auto-scroll on and off. * Updates button appearance to reflect current state. */ function toggleAutoScroll() { autoScrollActive = !autoScrollActive; const button = document.getElementById('auto-scroll-button'); if (button) { if (autoScrollActive) { button.classList.add('active'); scrollToBottom(); // Scroll immediately when enabling console.log('[Mammouth Scroll] Auto-scroll enabled'); } else { button.classList.remove('active'); console.log('[Mammouth Scroll] Auto-scroll disabled'); } } } /** * Checks for new content and scrolls to the bottom if appropriate. * Called periodically to handle new messages. */ function checkScrolling() { if (!scrollableElement) { // Try to find the scrollable element if we don't have one scrollableElement = findScrollableElement(); return; } const currentScrollHeight = scrollableElement.scrollHeight; // If content height increased and auto-scroll is active, scroll to bottom if (currentScrollHeight > lastScrollHeight && autoScrollActive) { scrollToBottom(); } lastScrollHeight = currentScrollHeight; } /** * Handles manual scrolling by the user. * Disables auto-scroll if user scrolls away from the bottom. */ function handleManualScroll() { if (!scrollableElement) return; // Check if we're near the bottom of the scroll area const { scrollTop, scrollHeight, clientHeight } = scrollableElement; const isNearBottom = scrollHeight - scrollTop - clientHeight < 50; // If user scrolled away from bottom and auto-scroll was active, disable it if (!isNearBottom && autoScrollActive) { autoScrollActive = false; const button = document.getElementById('auto-scroll-button'); if (button) { button.classList.remove('active'); } console.log('[Mammouth Scroll] Auto-scroll disabled due to manual scrolling'); } } /** * Main initialization function. * Sets up the UI elements and event listeners. */ function initialize() { console.log('[Mammouth Scroll] Initializing...'); injectStyles(); createFloatingButtons(); // Find the scrollable element scrollableElement = findScrollableElement(); if (scrollableElement) { lastScrollHeight = scrollableElement.scrollHeight; // Add event listener for manual scrolling scrollableElement.addEventListener('scroll', handleManualScroll); console.log('[Mammouth Scroll] Scrollable element initialized:', scrollableElement); } // Set up interval for checking content and scroll state if (scrollInterval) clearInterval(scrollInterval); scrollInterval = setInterval(checkScrolling, 500); // Initial scroll to bottom if auto-scroll is enabled if (autoScrollActive) { setTimeout(scrollToBottom, 1000); } } /** * Sets up an observer to watch for DOM changes. * Ensures buttons are recreated if removed and handles UI changes. */ function setupObserver() { const observer = new MutationObserver((mutations) => { // Check if our buttons were removed if (!document.getElementById('mammouth-floating-buttons')) { createFloatingButtons(); } // Check if the scrollable element has changed const newScrollable = findScrollableElement(); if (newScrollable !== scrollableElement) { console.log('[Mammouth Scroll] Scrollable element changed, reinitializing...'); initialize(); } }); // Monitor the body for structural changes observer.observe(document.body, { childList: true, subtree: true, attributes: false, characterData: false }); } // Start the script when the page is ready if (document.readyState === 'complete' || document.readyState === 'interactive') { initialize(); setupObserver(); } else { window.addEventListener('DOMContentLoaded', () => { initialize(); setupObserver(); }); } // Also run after a short delay to ensure dynamic content has loaded setTimeout(() => { initialize(); setupObserver(); }, 2000); })();