Removes max-width, stretches the chat, increases the scrollbar width, and makes the code copy buttons sticky.
// ==UserScript==
// @name Qwen Chat: Wide Layout + Wider Scrollbar + Sticky Code Buttons
// @name:ru Qwen Chat: Широкий макет + толстая прокрутка + липкие кнопки кода
// @namespace http://tampermonkey.net/
// @version 1.1
// @author a114_you
// @description Removes max-width, stretches the chat, increases the scrollbar width, and makes the code copy buttons sticky.
// @description:ru Убирает max-width, растягивает чат, увеличивает ширину полосы прокрутки и делает кнопки копирования кода липкими
// @match https://chat.qwen.ai/*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- НАСТРОЙКИ ---
const CFG = {
top: 40, // Отступ сверху
right: 15, // Отступ справа
size: '14px', // Размер значков
bg: '#2b2b31' // Цвет фона
};
const css = `
/* Широкий макет */
html, body { width: 100vw!important; max-width: 100vw!important; overflow-x: hidden!important; }
.mx-auto, #chat-message-container, #chat-message-container > div, .chat-messages {
width: 100%!important; max-width: none!important; margin: 0!important;
}
/* Липкие кнопки */
.sticky-buttons-clone {
position: fixed!important; z-index: 99999; display: flex; gap: 10px;
background: ${CFG.bg}!important; border-radius: 6px; padding: 6px 8px;
opacity: 0; transition: opacity .1s; pointer-events: auto;
border: none!important; box-shadow: none!important;
}
.sticky-buttons-clone.visible { opacity: 1; }
.sticky-buttons-clone div { cursor: pointer; display: flex; align-items: center; }
/* Значки */
.sticky-buttons-clone svg {
color: rgba(255,255,255,.8)!important; fill: rgba(255,255,255,.8)!important;
width: ${CFG.size}!important; height: ${CFG.size}!important;
}
.sticky-buttons-clone div:hover svg { color: #fff!important; fill: #fff!important; }
.hidden-by-sticky { opacity: 0!important; pointer-events: none!important; }
/* Скроллбар */
.chat-messages::-webkit-scrollbar { width: 16px!important; }
.chat-messages::-webkit-scrollbar-thumb {
background: rgba(130,130,130,.7)!important; border-radius: 8px;
border: 4px solid transparent; background-clip: content-box;
}
`;
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
const activeClones = new Map();
const knownBlocks = new Set(); // Список всех блоков кода в текущем чате
const update = () => {
// Проверка всех известных блоков
knownBlocks.forEach(block => {
// Если блока больше нет в документе (сменили чат) - удаляем
if (!document.contains(block)) {
const data = activeClones.get(block);
if (data) data.clone.remove();
activeClones.delete(block);
knownBlocks.delete(block);
return;
}
const header = block.querySelector('.qwen-markdown-code-header');
const actions = block.querySelector('.qwen-markdown-code-header-actions');
if (!header || !actions) return;
const bRect = block.getBoundingClientRect();
const hRect = header.getBoundingClientRect();
// Условие: заголовок ушел вверх, но низ блока еще виден
const shouldShow = hRect.bottom < (CFG.top + 2) && bRect.bottom > 50;
let data = activeClones.get(block);
if (shouldShow) {
if (!data) {
const clone = actions.cloneNode(true);
clone.className = 'sticky-buttons-clone';
const origBtns = actions.querySelectorAll('.qwen-markdown-code-header-action-item');
clone.querySelectorAll('.qwen-markdown-code-header-action-item').forEach((btn, i) => {
btn.onclick = (e) => { e.stopPropagation(); origBtns[i]?.click(); };
});
document.body.appendChild(clone);
requestAnimationFrame(() => clone.classList.add('visible'));
data = { clone, actions };
activeClones.set(block, data);
}
actions.classList.add('hidden-by-sticky');
const h = data.clone.offsetHeight || 28;
// Плавное прилипание к низу, когда блок уходит
const top = Math.min(CFG.top, bRect.bottom - h - 10);
data.clone.style.top = top + 'px';
data.clone.style.right = (window.innerWidth - bRect.right + CFG.right) + 'px';
} else if (data) {
data.clone.classList.remove('visible');
data.actions.classList.remove('hidden-by-sticky');
setTimeout(() => {
if (!data.clone.classList.contains('visible')) {
data.clone.remove();
activeClones.delete(block);
}
}, 100);
}
});
};
// Наблюдатель за появлением новых блоков (Discovery)
const domObserver = new MutationObserver((mutations) => {
mutations.forEach(m => m.addedNodes.forEach(node => {
if (node.nodeType === 1) {
const blocks = node.classList?.contains('qwen-markdown-code') ? [node] : node.querySelectorAll('.qwen-markdown-code');
blocks.forEach(b => {
knownBlocks.add(b);
update(); // Проверяем сразу
});
}
}));
});
// Скролл
let frame;
const onEvent = () => {
if (!frame) {
frame = requestAnimationFrame(() => {
update();
frame = null;
});
}
};
const init = () => {
window.addEventListener('scroll', onEvent, { passive: true, capture: true });
window.addEventListener('resize', onEvent, { passive: true });
// Инициализируем уже имеющиеся на странице блоки
document.querySelectorAll('.qwen-markdown-code').forEach(b => knownBlocks.add(b));
domObserver.observe(document.body, { childList: true, subtree: true });
update();
};
// Запуск
if (document.readyState === 'complete') init();
else window.addEventListener('load', init);
})();