您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Ajoute deux boutons dans la barre de saisie pour défiler vers le bas du chat et activer/désactiver l'auto-scroll.
当前为
// ==UserScript== // @name Mammouth.ai Scroll to Bottom Button // @name:en Mammouth.ai Scroll to Bottom Button // @name:es Mammouth.ai Scroll to Bottom Button // @namespace http://violentmonkey.github.io/ // @version 2.0.3 // @description Ajoute deux boutons dans la barre de saisie pour défiler vers le bas du chat et activer/désactiver l'auto-scroll. // @description:en Add two buttons in the input bar to scroll the chat down and toggle auto-scroll. // @description:es Añade dos botones en la barra de entrada para ir al final del chat y activar/desactivar el auto-scroll. // @author Assistant IA // @match https://mammouth.ai/app/* // @grant none // @license MIT // ==/UserScript== (function () { "use strict"; // Localization dictionary for UI strings. 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"]; // Auto-scroll is enabled by default. let autoScrollActive = true; let scrollableElement = null; let scrollCheck = null; let lastScrollHeight = 0; let userManuallyScrolled = false; let lastUserInteractionTime = 0; // Inject styles for the buttons. // The buttons are sized and positioned to match the message send button. const style = document.createElement("style"); style.innerHTML = ` .mammouth-button { position: absolute; top: 8px; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; cursor: pointer; opacity: 0; pointer-events: none; transition: opacity 0.2s; } .mammouth-button.visible { opacity: 1; pointer-events: auto; } /* Positioning: the send button is at right:8px, so the buttons are placed just to its left */ #mammouth-auto-scroll-button { right: 48px; } #mammouth-scroll-button { right: 88px; } .mammouth-button svg { width: 20px; height: 20px; color: #a16207; /* similar to light brown */ transition: color 0.2s; } .mammouth-button:hover svg { color: #92400e; /* similar to brown */ } /* Active indicator (green) for the auto-scroll button */ #mammouth-auto-scroll-button.active svg { color: #2eae66; } `; document.head.appendChild(style); /** * Creates the scroll and auto-scroll buttons and inserts them into the input container. */ function createButtons() { // Select the container that holds the text area and the send message button. const inputContainer = document.querySelector("div.w-full.relative"); if (!inputContainer) return; // Create the "scroll to bottom" button if it does not already exist. if (!document.getElementById("mammouth-scroll-button")) { const scrollButton = document.createElement("button"); scrollButton.id = "mammouth-scroll-button"; scrollButton.className = "mammouth-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", () => { if (scrollableElement) { scrollToBottom(); updateButtonsVisibility(); } }); inputContainer.appendChild(scrollButton); } // Create the "auto-scroll toggle" button if it does not already exist. if (!document.getElementById("mammouth-auto-scroll-button")) { const autoScrollButton = document.createElement("button"); autoScrollButton.id = "mammouth-auto-scroll-button"; autoScrollButton.className = "mammouth-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); if (autoScrollActive) { autoScrollButton.classList.add("active"); } inputContainer.appendChild(autoScrollButton); } } /** * Returns true if the chat element is near the bottom. * @returns {boolean} */ function isNearBottom() { if (!scrollableElement) return true; const scrollTop = scrollableElement.scrollTop; const scrollHeight = scrollableElement.scrollHeight; const clientHeight = scrollableElement.clientHeight; return scrollHeight - scrollTop - clientHeight < 50; } /** * Scrolls the chat element to the bottom. */ function scrollToBottom() { if (scrollableElement) { scrollableElement.scrollTop = scrollableElement.scrollHeight; userManuallyScrolled = false; } } /** * Toggles auto-scroll on and off. */ function toggleAutoScroll() { autoScrollActive = !autoScrollActive; const button = document.getElementById("mammouth-auto-scroll-button"); if (button) { if (autoScrollActive) { button.classList.add("active"); scrollToBottom(); // Scroll immediately when enabling } else { button.classList.remove("active"); } } updateButtonsVisibility(); } /** * Checks for new messages and scrolls to the bottom if appropriate. */ function checkForNewContent() { if (!scrollableElement || !autoScrollActive) return; const currentScrollHeight = scrollableElement.scrollHeight; if (currentScrollHeight > lastScrollHeight && !userManuallyScrolled) { scrollToBottom(); } lastScrollHeight = currentScrollHeight; } /** * Updates the visibility of the buttons. */ function updateButtonsVisibility() { const scrollButton = document.getElementById("mammouth-scroll-button"); const autoScrollButton = document.getElementById("mammouth-auto-scroll-button"); if (!scrollButton || !autoScrollButton || !scrollableElement) return; if (isNearBottom() || autoScrollActive) { scrollButton.classList.remove("visible"); } else { scrollButton.classList.add("visible"); } autoScrollButton.classList.add("visible"); } /** * Handles manual scrolling by the user. */ function handleUserScroll() { if (!isNearBottom()) { userManuallyScrolled = true; if (autoScrollActive) { toggleAutoScroll(); } } else { userManuallyScrolled = false; } lastUserInteractionTime = Date.now(); updateButtonsVisibility(); } /** * Initializes the buttons and event listeners. */ function initScrollButtons() { // Select the scrolling element where messages are displayed. const chatElement = document.querySelector( "div.overflow-auto.overflow-x-hidden.w-full.flex.flex-col.gap-5.scrollable" ); if (chatElement) { scrollableElement = chatElement; lastScrollHeight = chatElement.scrollHeight; // Insert the buttons into the input container. createButtons(); if (scrollCheck) clearInterval(scrollCheck); scrollableElement.addEventListener("scroll", handleUserScroll); scrollCheck = setInterval(() => { checkForNewContent(); updateButtonsVisibility(); }, 300); updateButtonsVisibility(); if (autoScrollActive) { scrollToBottom(); } } else { scrollableElement = null; const scrollButton = document.getElementById("mammouth-scroll-button"); const autoScrollButton = document.getElementById("mammouth-auto-scroll-button"); if (scrollButton) scrollButton.classList.remove("visible"); if (autoScrollButton) autoScrollButton.classList.remove("visible"); } } // Monitor DOM changes to update our buttons in dynamic or AJAX-loaded interfaces. const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === "childList" || mutation.type === "subtree") { initScrollButtons(); break; } } }); observer.observe(document.body, { childList: true, subtree: true }); // Periodically check for button initialization (useful for AJAX navigation) setInterval(initScrollButtons, 2000); if ( document.readyState === "complete" || document.readyState === "interactive" ) { initScrollButtons(); } else { window.addEventListener("DOMContentLoaded", initScrollButtons); } })();