Кнопка «К началу ответа» для ChatGPT, Qwen, DeepSeek, Claude, Gemini, Grok и Perplexity: как я победил скролл и AI‑мысли

от автора

Спойлер: коды готовы — вставьте и пользуйтесь.

Логотипы ChatGPT, DeepSeek, Qwen и кнопка

Пример кнопки. Логотипы являются товарными знаками компаний.

Знаете, что меня расстраивало больше всего в чатах с нейросетями? Сидишь, читаешь длинный ответ в момент генерации. Дошёл до середины, понял, что упустил какую-то деталь в начале, крутишь скролл вверх. А бот в этот момент дописывает новый абзац, и весь текст уезжает обратно вниз.

Штатная стрелочка “наверх” тут не спасает. Она кидает к шапке сайта, а не к началу конкретного сообщения ассистента. Приходится ловить текст вручную. Сейчас стали выкатывать что-то вроде истории запросов справа от чата, похожее на закладки, но мне они не нравятся по той же причине: надо приглядываться и целиться в анимированный интерфейс.

Чтобы не дергать страницу туда-сюда, я набросал около 10 юзерскриптов для всех популярных чатов. Они вешают в углу экрана кнопку, которая телепортирует прямо к первой строчке именно последнего ответа. Заодно вычищают визуальный мусор, который разработчики добавляют в интерфейс без возможности отключения штатными методами.

Под капотом логика везде одинаковая. Скрипт ищет в DOM-дереве последний блок с ответом, рисует поверх всего кнопку с position: fixed, а по клику вызывает обычный scrollIntoView. Чтобы было понятно, куда именно мы прыгнули, блок на секунду подсвечивается. Можно придумать любые эффекты и доработки оригинального интерфейса. Я начал с кнопки, а потом решил почистить интерфейс.

Поскольку все эти чаты работают как SPA, просто повесить обработчик на загрузку страницы не получится. Приходится слушать изменения через MutationObserver, иначе при переходе в новый диалог кнопка отвалится.

Самое интересное начинается на этапе поиска нужного div. У каждой платформы свои заморочки.

В ChatGPT и Claude жизнь облегчает наличие нормальных data-* атрибутов или предсказуемых классов. Нашел [data-message-author-role="assistant"] — и можно расслабиться.

Честно говоря, я не сильно вникал в код. Как только видел, что кнопка работает, то переходил к настройке другого ИИ чата. Поэтому, если кому интересно сделать надёжнее, пишите в комментариях как улучшить код для уменьшения вероятности отвала после обновлений сайтов с чатами.

С Gemini и Grok тоже более-менее понятно: там либо веб-компоненты вроде model-response, либо обычные markdown-контейнеры.

А вот DeepSeek любит использовать хеш-классы. Сегодня ты ищешь сообщение по .ds-message, а завтра верстак обновился, и классы превратились в случайный набор букв. Приходится делать кучу фолбэков, как в Qwen, где скрипт перебирает несколько вариантов селекторов, пока не наткнется на нужный. В Perplexity вообще приходится искать контент внутри специфичных renderer-блоков.

Ниже полный рабочий userscript для ChatGPT.

// ==UserScript==// @name         ChatGPT// @namespace    http://tampermonkey.net/// @version      1.5// @description  Кнопка к началу ответа на chatgpt.com// @match        https://chatgpt.com/*// @grant        none// @run-at       document-idle// ==/UserScript==(function() {    'use strict';    // Скрываем блоки CSS    const style = document.createElement('style');    style.textContent = `        /* Reasoning */        .reasoning,        .model-reasoning,        .thought,        .chain-of-thought,        [data-reasoning="true"],        [class*="reasoning"],        [class*="thought"] {            display: none !important;        }        .thinking-indicator,        .model-thinking {            display: none !important;        }        /* Follow-up suggestions */        p:has(+ ul[data-is-last-node][data-is-only-node]) {            display: none !important;        }        ul[data-is-last-node][data-is-only-node] {            display: none !important;        }        ul:has(li span.entity-underline) {            display: none !important;        }        p:has(span:contains("следующим шагом")) {            display: none !important;        }        .follow-up,        .suggestion,        .suggested-actions {            display: none !important;        }        /* Дисклеймер */        .text-caption-regular {            display: none !important;        }        [class*="text-token-text-tertiary"] .text-caption-regular {            display: none !important;        }        /* Кнопка "Принять предложение" */        div.mx-3\\.5.mt-1.mb-2:has(button[aria-label="Принять предложение"]) {            display: none !important;        }    `;    document.head.appendChild(style);    // Динамическое скрытие (для follow-up)    function hideUnwantedElements() {        document.querySelectorAll('ul[data-is-last-node][data-is-only-node]').forEach(el => {            el.style.display = 'none';        });        document.querySelectorAll('p:has(+ ul[data-is-last-node][data-is-only-node])').forEach(el => {            el.style.display = 'none';        });        document.querySelectorAll('p').forEach(el => {            if (el.textContent.includes('следующим шагом')) {                const next = el.nextElementSibling;                if (next && next.tagName === 'UL' && next.querySelector('span.entity-underline')) {                    el.style.display = 'none';                    next.style.display = 'none';                }            }        });    }    hideUnwantedElements();    const observer = new MutationObserver(() => {        hideUnwantedElements();    });    observer.observe(document.body, { childList: true, subtree: true });    // Поиск последнего ответа ассистента    function getLastAssistantMessage() {        const messages = document.querySelectorAll('[data-message-author-role="assistant"]');        if (messages.length > 0) {            return messages[messages.length - 1];        }        const turns = document.querySelectorAll('[data-turn="assistant"]');        if (turns.length > 0) {            return turns[turns.length - 1];        }        return null;    }    // Создание кнопки    const btn = document.createElement('button');    btn.textContent = 'К началу ответа';        // ← Текст кнопки (меняйте)    btn.title = 'Прокрутить к началу последнего ответа ИИ';    btn.style.cssText = `        position: fixed;        bottom: 28px;                          /* ← Отступ снизу */        right: 28px;                           /* ← Отступ справа */        z-index: 10000;        padding: 10px 18px;        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); /* ← Цвета градиента (меняйте) */        color: white;        border: none;        border-radius: 8px;        cursor: pointer;        font-size: 13px;        font-weight: 500;        box-shadow: 0 4px 14px rgba(102, 126, 234, 0.35);        transition: all 0.2s ease;        backdrop-filter: blur(6px);    `;    btn.onmouseenter = () => {        btn.style.transform = 'translateY(-2px)';        btn.style.boxShadow = '0 6px 18px rgba(102, 126, 234, 0.5)';    };    btn.onmouseleave = () => {        btn.style.transform = 'translateY(0)';        btn.style.boxShadow = '0 4px 14px rgba(102, 126, 234, 0.35)';    };    btn.onclick = () => {        const target = getLastAssistantMessage();        if (target) {            target.scrollIntoView({ behavior: 'smooth', block: 'start' });            const origBorder = target.style.borderTop;            const origTrans = target.style.transition;            target.style.transition = 'border-top 0.2s ease';            target.style.borderTop = '3px solid #667eea';   // ← Цвет подсветки (меняйте)            setTimeout(() => {                target.style.borderTop = origBorder;                target.style.transition = origTrans;            }, 1500);        } else {            console.warn('[ChatGPT Scroll] Assistant message not found');            window.scrollTo({ top: 0, behavior: 'smooth' });        }    };    // Добавление кнопки в DOM    function initButton() {        if (document.body) {            document.body.appendChild(btn);        } else {            setTimeout(initButton, 100);        }    }    if (document.readyState === 'loading') {        document.addEventListener('DOMContentLoaded', initButton);    } else {        initButton();    }    // Поддержка SPA    let lastUrl = location.href;    new MutationObserver(() => {        const url = location.href;        if (url !== lastUrl) {            lastUrl = url;            if (!document.body.contains(btn)) {                initButton();            }        }    }).observe(document, { subtree: true, childList: true });})();

Применить скрипт легко: Установите расширение Tampermonkey для вашего браузера. Нажмите на иконку Tampermonkey и “Создать новый скрипт”. Удалите всё в шаблоне и вставьте скрипт. Сохраните (Ctrl+S) и обновите страницу нужного чата. Наслаждайтесь кнопкой!

Расположение кнопки на разных экранах может отличаться. Если она встала не туда, то просто поправьте значения bottom и right в коде за 5 секунд.

Настройка под себя: Цвет кнопки — замените #667eea и #764ba2 на свои hex-коды. Расположение — поменяйте значения bottom и right. Текст кнопки — замените “К началу ответа” на что угодно. Скрытие подписей — удалите или закомментируйте строки с селекторами.

Минус у такого подхода со скриптами один — мы парсим чужую верстку. Как только платформа решает провести редизайн (вряд ли это будет очень часто) или просто меняет нейминг классов, то скрипт сразу же ломается и хеш-классы отваливаются первыми. Data-атрибуты живут дольше, но и они не вечны.

Это не история из разряда «поставил и забыл». Скорее, «поставил, пользуешься, пока не прилетит обновление от вендора». Но меня это устраивает. Для меня секундное дело зайти в DevTools и глянуть код.

Вчера как раз DeepSeek выкатил минорный апдейт, и кнопка исчезла. Пришлось открывать консоль, искать новый класс для блока с мыслями и править CSS. Зато теперь интерфейс снова не режет глаз, и я очень рад своему небольшому изобретению. Мелочь, а приятно!

Все скрипты кнопок для разных ИИ лежат у меня на GitHub: button-AI.


Коротко обо мне: я архитектор и дизайнер интерьера, и моя страсть к удобству распространяется не только на архитектурные пространства, но и на цифровые интерфейсы. Меня зовут Артём Ерыков. Если интересно — загляните на мой сайт. Буду рад познакомиться.

ссылка на оригинал статьи https://habr.com/ru/articles/1053326/