LMSYS Token Maximizer

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

当前为 2024-12-25 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         LMSYS Token Maximizer
// @namespace    https://github.com/seu-usuario/lmsys-token-maximizer
// @version      2.2.2
// @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 directChatButton = document.querySelector('button[aria-controls][class*="svelte"][role="tab"]:not([aria-disabled="true"]):not([aria-selected="false"])');
                if (directChatButton && directChatButton.textContent.includes('Direct Chat')) {
                    const directChatId = directChatButton.getAttribute('aria-controls');
                    const chatContainer = document.getElementById(directChatId);

                    if (chatContainer) {
                        this.insertControls(chatContainer);
                    }
                }
            }, delay));
        }

        // Configura o observer para mudanças na página
        setupMutationObserver() {
            const observer = new MutationObserver(() => {
                // Procura especificamente pelo container do Direct Chat
                const directChatButton = document.querySelector('button[aria-controls][class*="svelte"][role="tab"]:not([aria-disabled="true"]):not([aria-selected="false"])');
                if (directChatButton && directChatButton.textContent.includes('Direct Chat')) {
                    const directChatId = directChatButton.getAttribute('aria-controls');
                    const chatContainer = document.getElementById(directChatId);

                    if (chatContainer && !document.getElementById(CONFIG.panelId)) {
                        setTimeout(() => this.insertControls(chatContainer), 100);
                    }
                }
            });

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

        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"], input[aria-label*="max tokens"], input[data-testid*="token"], input[placeholder*="token"]');
            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%';
                    }
                    // Dispara eventos nativos e eventos personalizados
                    ['input', 'change', 'blur'].forEach(eventType => {
                        input.dispatchEvent(new Event(eventType, { bubbles: true }));
                    });
                    // Tenta atualizar o React state se existir
                    if (input._valueTracker) {
                        input._valueTracker.setValue('');
                    }
                }
            });
        }

        // Modifica a temperatura
        modifyTemperature(value) {
            const inputs = document.querySelectorAll('input[aria-label*="Temperature"], input[aria-label*="temperatura"], input[data-testid*="temp"], input[placeholder*="temp"]');
            inputs.forEach(input => {
                input.value = value;
                if (input.type === 'range') {
                    input.style.backgroundSize = `${value * 100}% 100%`;
                }
                // Dispara eventos nativos e eventos personalizados
                ['input', 'change', 'blur'].forEach(eventType => {
                    input.dispatchEvent(new Event(eventType, { bubbles: true }));
                });
                // Tenta atualizar o React state se existir
                if (input._valueTracker) {
                    input._valueTracker.setValue('');
                }
            });
        }

        // 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 especificamente no botão Direct Chat
            const directChatButton = document.querySelector('button[aria-controls][class*="svelte"][role="tab"]:not([aria-disabled="true"]):not([aria-selected="false"])');
            if (directChatButton && directChatButton.textContent.includes('Direct Chat')) {
                directChatButton.addEventListener('click', () => {
                    setTimeout(() => {
                        // Encontra o texto "Token Controls" ou o container
                        const tokenControlsText = Array.from(document.querySelectorAll('span')).find(
                            span => span.textContent === 'Token Controls'
                        );

                        const controlsContainer = document.getElementById(CONFIG.panelId);

                        if (!tokenControlsText && !controlsContainer) return;

                        const targetElement = tokenControlsText || controlsContainer;
                        const textRect = targetElement.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();
    }
})();