GPT Message Top Scroll

The “↑” button next to Copy/Edit scrolls only large messages (>=700 characters) to their top, aligned per message; supports both user and assistant.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         GPT Message Top Scroll
// @namespace    https://greasyfork.org/users/your-id-or-homepage
// @version      1.3
// @description  The “↑” button next to Copy/Edit scrolls only large messages (>=700 characters) to their top, aligned per message; supports both user and assistant.
// @author       Madhuvrata
// @match        https://chatgpt.com/*
// @grant        none
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(() => {
  'use strict';

  // CSS
  const style = document.createElement('style');
  style.textContent = `
    .tm-scroll-top-btn {
      width: 32px!important;
      height: 32px!important;
      margin: 0 4px!important;
      background: rgba(0,0,0,0.05)!important;
      border: 1px solid rgba(0,0,0,0.1)!important;
      border-radius: 6px!important;
      display: inline-flex!important;
      align-items: center!important;
      justify-content: center!important;
      cursor: pointer!important;
      opacity: .7!important;
      transition: opacity .2s, background .2s;
      pointer-events: auto!important;
    }
    .tm-scroll-top-btn:hover { opacity: 1!important; background: rgba(0,0,0,0.1)!important; }
    .tm-force-panel {
      opacity: 1!important;
      pointer-events: auto!important;
      mask-image: none!important;
      -webkit-mask-image: none!important;
    }
  `;
  document.head.appendChild(style);

  const svgArrow = `
    <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
      <path d="M10 4l-6 6h4v6h4v-6h4l-6-6z"/>
    </svg>`;

  // панель кнопок для user и assistant
  const PANEL_SELECTOR = `
    article[data-testid^="conversation-turn-"] .flex.justify-end,
    article[data-testid^="conversation-turn-"] .flex.justify-start
  `.trim().replace(/\s+/g,' ');

  function injectButtons() {
    document.querySelectorAll(PANEL_SELECTOR).forEach(panel => {
      panel.classList.add('tm-force-panel');
      const inner = panel.querySelector(':scope > div');
      if (!inner || inner.querySelector('.tm-scroll-top-btn')) return;
      inner.classList.add('tm-force-panel');

      // найдём связанный article и контейнер markdown
      const article = panel.closest('article[data-testid^="conversation-turn-"]');
      if (!article) return;
      const md = article.querySelector('.markdown') || article;

      // длина текста
      const txtLen = (md.innerText || '').length;

      // создаём кнопку
      const btn = document.createElement('button');
      btn.className = 'tm-scroll-top-btn';
      btn.innerHTML = svgArrow;
      btn.title = 'Scroll message to top';
      btn.addEventListener('click', e => {
        e.stopPropagation();
        // если короткое сообщение — игнорируем
        if (txtLen < 700) return;
        article.scrollIntoView({ behavior: 'smooth', block: 'start' });
      });

      inner.appendChild(btn);
    });
  }

  injectButtons();
  new MutationObserver(injectButtons)
    .observe(document.body, { childList: true, subtree: true });
})();