LMSYS Token Maximizer

Enhanced token and temperature control for LMSYS Chat with a beautiful interface

// ==UserScript==
// @name         LMSYS Token Maximizer
// @namespace    https://github.com/seu-usuario/lmsys-token-maximizer
// @version      2.2.1
// @description  Enhanced token and temperature control for LMSYS Chat with a beautiful interface
// @author       BrunexCoder
// @match        *://lmarena.ai/*
// @match        *://arena.lmsys.org/*
// @match        *://chat.lmsys.org/*
// @match        https://lmarena.ai/
// @icon         https://chat.lmsys.org/favicon.ico
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Configurações globais
    const CONFIG = {
        defaultTokens: 4096,
        defaultTemp: 0.7,
        panelId: 'token-maximizer-controls',
        firstRunKey: 'tokenMaximizer_firstRun_v2',
        styles: {
            colors: {
                primary: '#3b82f6',
                background: 'var(--background-fill-primary)',
                secondaryBg: 'var(--background-fill-secondary)',
                border: 'var(--border-color-primary)',
                text: 'var(--body-text-color)',
                accent: 'var(--link-text-color)',
                slider: {
                    track: '#e2e8f0',
                    thumb: '#3b82f6',
                    progress: '#3b82f6'
                },
                tutorial: {
                    arrow: '#3b82f6',
                    background: '#3b82f6',
                    text: '#ffffff'
                }
            },
            fontSize: {
                small: '12px',
                medium: '13px',
                large: '14px'
            },
            tutorial: {
                arrow: '#3b82f6',
                background: '#3b82f6',
                text: '#ffffff'
            }
        }
    };

    // Classe principal do Token Maximizer
    class TokenMaximizer {
        constructor() {
            this.initialized = false;
            this.debugMode = /Android/.test(navigator.userAgent);
        }

        log(message, type = 'info') {
            if (this.debugMode) {
                const logElement = document.createElement('div');
                logElement.style.cssText = `
                    position: fixed;
                    top: ${document.querySelectorAll('.debug-log').length * 30}px;
                    left: 10px;
                    background: rgba(0,0,0,0.8);
                    color: white;
                    padding: 5px 10px;
                    border-radius: 5px;
                    z-index: 9999;
                    font-size: 12px;
                `;
                logElement.className = 'debug-log';
                logElement.textContent = `[${type}] ${message}`;
                document.body.appendChild(logElement);
                
                // Remove o log após 5 segundos
                setTimeout(() => logElement.remove(), 5000);
                
                console.log(`[TokenMaximizer] ${message}`);
            }
        }

        // Inicializa o Token Maximizer
        init() {
            if (this.initialized) return;
            this.initialized = true;

            this.log('Iniciando Token Maximizer...');
            this.log(`User Agent: ${navigator.userAgent}`);
            
            this.setupInitialLoad();
            this.setupMutationObserver();
            this.showCredits();
            
            setTimeout(() => this.showTutorialArrow(), 2000);
        }

        // Configura o carregamento inicial
        setupInitialLoad() {
            const attempts = [0, 500, 1000, 2000];
            attempts.forEach(delay => setTimeout(() => {
                // Procura especificamente pelo container do Direct Chat
                const directChatTab = document.querySelector('#component-107');
                const directChatContainer = directChatTab?.querySelector('#component-111') || 
                                          directChatTab?.querySelector('.gr-group');
                
                if (directChatContainer) {
                    this.insertControls(directChatContainer);
                }
            }, delay));
        }

        // Configura o observer para mudanças na página
        setupMutationObserver() {
            const observer = new MutationObserver(() => {
                const directChatTab = document.querySelector('#component-107');
                const directChatContainer = directChatTab?.querySelector('#component-111') || 
                                          directChatTab?.querySelector('.gr-group');
                
                if (directChatContainer && !document.getElementById(CONFIG.panelId)) {
                    setTimeout(() => this.insertControls(directChatContainer), 100);
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: ['aria-selected', 'style']
            });
        }

        insertControls(targetElement) {
            const existingControls = document.getElementById(CONFIG.panelId);
            if (existingControls) existingControls.remove();

            const controlsContainer = document.createElement('div');
            controlsContainer.id = CONFIG.panelId;
            controlsContainer.style.cssText = `
                display: flex;
                align-items: center;
                gap: 16px;
                padding: 16px;
                margin: 8px 0;
                border-radius: 12px;
                background: ${CONFIG.styles.colors.background};
                border: 1px solid ${CONFIG.styles.colors.border};
                box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
            `;

            // Título com ícone e estilo do site
            const titleContainer = document.createElement('div');
            titleContainer.style.cssText = `
                display: flex;
                align-items: center;
                gap: 8px;
                padding-right: 16px;
                border-right: 1px solid ${CONFIG.styles.colors.border};
            `;

            const titleIcon = document.createElement('span');
            titleIcon.textContent = '🔧';
            titleIcon.style.fontSize = '16px';

            const titleText = document.createElement('span');
            titleText.textContent = 'Token Controls';
            titleText.style.cssText = `
                font-size: ${CONFIG.styles.fontSize.medium};
                font-weight: 600;
                color: ${CONFIG.styles.colors.text};
            `;

            titleContainer.append(titleIcon, titleText);

            // Container para os controles
            const controlsWrapper = document.createElement('div');
            controlsWrapper.style.cssText = `
                display: flex;
                align-items: center;
                gap: 16px;
                flex-grow: 1;
            `;

            // Criar controles com novo estilo
            const tokenControl = this.createCompactControl(
                'Max Tokens',
                'range',
                {min: 1024, max: 8192, value: CONFIG.defaultTokens, step: 1024},
                this.modifyInputs.bind(this)
            );

            const tempControl = this.createCompactControl(
                'Temperature',
                'range',
                {min: 0, max: 1, value: CONFIG.defaultTemp, step: 0.1},
                this.modifyTemperature.bind(this)
            );

            controlsWrapper.append(tokenControl, tempControl);
            controlsContainer.append(titleContainer, controlsWrapper);
            targetElement.insertBefore(controlsContainer, targetElement.firstChild);
        }

        createCompactControl(label, type, options, onChange) {
            const container = document.createElement('div');
            container.style.cssText = `
                display: flex;
                align-items: center;
                gap: 12px;
                padding: 8px 12px;
                background: ${CONFIG.styles.colors.secondaryBg};
                border-radius: 8px;
                flex: 1;
            `;

            const labelElement = document.createElement('label');
            labelElement.textContent = label;
            labelElement.style.cssText = `
                font-size: ${CONFIG.styles.fontSize.small};
                color: ${CONFIG.styles.colors.text};
                font-weight: 500;
                min-width: 80px;
            `;

            const inputWrapper = document.createElement('div');
            inputWrapper.style.cssText = `
                position: relative;
                flex: 1;
                display: flex;
                align-items: center;
                gap: 8px;
            `;

            const input = document.createElement('input');
            input.type = type;
            Object.assign(input, options);
            input.style.cssText = `
                width: 100%;
                height: 4px;
                -webkit-appearance: none;
                background: ${CONFIG.styles.colors.border};
                border-radius: 4px;
                cursor: pointer;
                transition: all 0.3s ease;
                
                &::-webkit-slider-thumb {
                    -webkit-appearance: none;
                    width: 16px;
                    height: 16px;
                    border-radius: 50%;
                    background: #3b82f6;
                    border: 2px solid white;
                    cursor: pointer;
                    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                    box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
                }
                
                &::-webkit-slider-thumb:hover {
                    transform: scale(1.2);
                    box-shadow: 0 4px 8px rgba(59, 130, 246, 0.4);
                }
                
                &::-webkit-slider-thumb:active {
                    transform: scale(0.95);
                    box-shadow: 0 1px 2px rgba(59, 130, 246, 0.4);
                }

                &::-webkit-slider-runnable-track {
                    width: 100%;
                    height: 4px;
                    background: linear-gradient(to right, #3b82f6 var(--value-percent, 50%), rgba(59, 130, 246, 0.1) var(--value-percent, 50%));
                    border-radius: 4px;
                    transition: background 0.3s ease;
                }

                &:focus {
                    outline: none;
                }
                
                &:hover::-webkit-slider-runnable-track {
                    background: linear-gradient(to right, #3b82f6 var(--value-percent, 50%), rgba(59, 130, 246, 0.2) var(--value-percent, 50%));
                }
            `;

            // Adiciona efeito de brilho ao passar o mouse
            input.addEventListener('mouseover', () => {
                input.style.filter = 'brightness(1.1)';
            });

            input.addEventListener('mouseout', () => {
                input.style.filter = 'brightness(1)';
            });

            input.addEventListener('input', (e) => {
                const min = parseFloat(e.target.min);
                const max = parseFloat(e.target.max);
                const val = parseFloat(e.target.value);
                const percent = ((val - min) * 100) / (max - min);
                e.target.style.setProperty('--value-percent', `${percent}%`);
                
                // Adiciona efeito de pulso ao mover o slider
                e.target.style.transform = 'scale(1.02)';
                setTimeout(() => {
                    e.target.style.transform = 'scale(1)';
                }, 100);
            });

            const initialPercent = ((options.value - options.min) * 100) / (options.max - options.min);
            input.style.setProperty('--value-percent', `${initialPercent}%`);

            const valueLabel = document.createElement('span');
            valueLabel.style.cssText = `
                font-size: ${CONFIG.styles.fontSize.small};
                color: ${CONFIG.styles.colors.text};
                font-family: var(--font-mono);
                min-width: 45px;
                text-align: right;
                transition: all 0.3s ease;
            `;
            valueLabel.textContent = options.value;

            input.addEventListener('input', (e) => {
                const value = parseFloat(e.target.value);
                valueLabel.textContent = type === 'range' && label === 'Max Tokens' 
                    ? Math.round(value).toLocaleString()
                    : value.toFixed(1);
                
                // Anima o valor ao mudar
                valueLabel.style.transform = 'scale(1.1)';
                valueLabel.style.color = '#3b82f6';
                setTimeout(() => {
                    valueLabel.style.transform = 'scale(1)';
                    valueLabel.style.color = CONFIG.styles.colors.text;
                }, 200);
                
                onChange(value);
            });

            inputWrapper.append(input, valueLabel);
            container.append(labelElement, inputWrapper);
            return container;
        }

        // Modifica os inputs de token
        modifyInputs(targetValue) {
            const inputs = document.querySelectorAll('input[aria-label*="Max output tokens"]');
            inputs.forEach(input => {
                if (input.max < targetValue) input.max = targetValue;
                if (parseInt(input.value) !== targetValue) {
                    input.value = targetValue;
                    if (input.type === 'range') {
                        input.style.backgroundSize = '100% 100%';
                    }
                    input.dispatchEvent(new Event('input', { bubbles: true }));
                    input.dispatchEvent(new Event('change', { bubbles: true }));
                }
            });
        }

        // Modifica a temperatura
        modifyTemperature(value) {
            const inputs = document.querySelectorAll('input[aria-label*="Temperature"]');
            inputs.forEach(input => {
                input.value = value;
                if (input.type === 'range') {
                    input.style.backgroundSize = `${value * 100}% 100%`;
                }
                input.dispatchEvent(new Event('input', { bubbles: true }));
                input.dispatchEvent(new Event('change', { bubbles: true }));
            });
        }

        // Encontra e clica em botões
        findAndClickButton(action) {
            const buttonTexts = {
                regenerate: ['regenerate', 'retry', 'reset'],
                clear: ['clear', 'new chat']
            };

            const texts = buttonTexts[action] || [];
            const buttons = Array.from(document.querySelectorAll('button'));

            const button = buttons.find(btn => {
                const btnText = btn.textContent.toLowerCase();
                return texts.some(text => btnText.includes(text));
            });

            if (button) {
                button.click();
                return true;
            }
            return false;
        }

        showCredits() {
            const credits = document.createElement('div');
            credits.textContent = 'Token Maximizer developed by BrunexCoder';
            
            // Adiciona um container para o efeito de borda
            const creditsContainer = document.createElement('div');
            creditsContainer.style.cssText = `
                position: fixed;
                bottom: 20px;
                right: 20px;
                padding: 2px; /* Espaço para a borda RGB */
                border-radius: 10px;
                background: linear-gradient(90deg, #ff0000, #00ff00, #0000ff, #ff0000);
                background-size: 400% 400%;
                animation: gradientBorder 3s ease infinite;
                z-index: 9999;
                box-shadow: 0 2px 10px rgba(0,0,0,0.3);
            `;

            credits.style.cssText = `
                background: ${CONFIG.styles.colors.background};
                color: ${CONFIG.styles.colors.text};
                padding: 10px 15px;
                border-radius: 8px;
                font-size: ${CONFIG.styles.fontSize.small};
                font-weight: bold;
                text-align: center;
            `;

            // Adiciona a animação do gradiente
            const style = document.createElement('style');
            style.textContent = `
                @keyframes gradientBorder {
                    0% { background-position: 0% 50%; }
                    50% { background-position: 100% 50%; }
                    100% { background-position: 0% 50%; }
                }
            `;
            document.head.appendChild(style);

            creditsContainer.appendChild(credits);
            document.body.appendChild(creditsContainer);

            setTimeout(() => {
                creditsContainer.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
                creditsContainer.style.opacity = '0';
                creditsContainer.style.transform = 'translateY(20px)';
                setTimeout(() => creditsContainer.remove(), 500);
            }, 5000);
        }

        showTutorialArrow() {
            // Verifica se é a primeira execução desta versão
            if (localStorage.getItem(CONFIG.firstRunKey)) return;
            
            // Observa cliques no botão Direct Chat
            const directChatButton = document.querySelector('button[aria-controls="component-107"]');
            if (!directChatButton) return;

            directChatButton.addEventListener('click', () => {
                setTimeout(() => {
                    // Encontra o texto "Token Controls"
                    const tokenControlsText = Array.from(document.querySelectorAll('span')).find(
                        span => span.textContent === 'Token Controls'
                    );
                    
                    if (!tokenControlsText) return;
                    
                    const textRect = tokenControlsText.getBoundingClientRect();
                    const messageWidth = 220;
                    
                    const arrowContainer = document.createElement('div');
                    arrowContainer.style.cssText = `
                        position: fixed;
                        top: ${textRect.top - 18}px;
                        left: ${textRect.left - messageWidth + 60}px;
                        transform: translateY(-50%);
                        z-index: 9999;
                        display: flex;
                        align-items: center;
                        gap: 12px;
                        animation: bounceArrow 2s infinite;
                        pointer-events: none;
                    `;

                    // Atualiza o estilo da mensagem
                    const message = document.createElement('div');
                    message.style.cssText = `
                        background: ${CONFIG.styles.colors.tutorial.background};
                        color: ${CONFIG.styles.colors.tutorial.text};
                        padding: 12px 16px;
                        border-radius: 8px;
                        font-size: ${CONFIG.styles.fontSize.medium};
                        font-weight: 500;
                        box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
                        max-width: ${messageWidth}px;
                        line-height: 1.4;
                        text-align: right;
                        letter-spacing: 0.3px;
                        text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
                    `;
                    message.textContent = '🎉 Os controles de token foram movidos para cá! Agora você pode ajustar facilmente os tokens e temperatura.';

                    // Atualiza a seta
                    const arrow = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
                    arrow.setAttribute('width', '40');
                    arrow.setAttribute('height', '40');
                    arrow.setAttribute('viewBox', '0 0 24 24');
                    arrow.style.cssText = `
                        fill: none;
                        stroke: #ffffff;
                        stroke-width: 2.5;
                        stroke-linecap: round;
                        stroke-linejoin: round;
                        filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
                    `;

                    const arrowPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
                    arrowPath.setAttribute('d', 'M5 12h14m-7-7l7 7-7 7');
                    arrow.appendChild(arrowPath);

                    // Atualiza a animação
                    const style = document.createElement('style');
                    style.textContent = `
                        @keyframes bounceArrow {
                            0%, 100% { transform: translateX(0); }
                            50% { transform: translateX(10px); }
                        }
                        
                        
                        @keyframes fadeOut {
                            from { opacity: 1; transform: translateY(0); }
                            to { opacity: 0; transform: translateY(-10px); }
                        }
                    `;
                    document.head.appendChild(style);

                    arrowContainer.append(message, arrow);
                    document.body.appendChild(arrowContainer);

                    // Remove o tutorial após 10 segundos
                    setTimeout(() => {
                        arrowContainer.style.animation = 'fadeOut 0.5s ease forwards';
                        setTimeout(() => arrowContainer.remove(), 500);
                    }, 10000);

                    // Marca como já exibido
                    localStorage.setItem(CONFIG.firstRunKey, 'true');
                }, 500); // Delay para garantir que os elementos estejam carregados
            }, { once: true }); // Garante que o evento só seja disparado uma vez
        }

        resetTutorial() {
            localStorage.removeItem(CONFIG.firstRunKey);
            this.log('Tutorial reset realizado com sucesso');
        }
    }

    // Inicializa o Token Maximizer quando a página carregar
    if (document.readyState === 'loading') {
        window.addEventListener('load', () => new TokenMaximizer().init());
    } else {
        new TokenMaximizer().init();
    }
})();