您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improves LMSYS/LMArena chat interface: cleaner look, removes clutter & startup alerts.
// ==UserScript== // @name Better LMArena (lmsys) Chat // @namespace https://github.com/insign/userscripts // @version 202412281434 // @description Improves LMSYS/LMArena chat interface: cleaner look, removes clutter & startup alerts. // @match https://lmarena.ai/* // @match https://chat.lmsys.org/* // @icon https://www.google.com/s2/favicons?sz=64&domain=lmarena.ai // @author Hélio <[email protected]> // @license WTFPL // ==/UserScript== (function() { 'use strict' // --- Bloqueador de Alertas --- // Sobrescreve a função window.alert para impedir que alertas pop-up // interrompam o usuário, especialmente os que aparecem ao carregar a página. const originalAlert = window.alert // Guarda referência ao alert original (opcional) window.alert = function(...args) { console.log('Blocked alert:', args) // Pode-se adicionar lógica aqui, se necessário, ou apenas bloquear. // originalAlert.apply(window, args); // Descomente para reativar os alerts originais } console.log('Better LMArena: Alert blocker active.') // --- Utilitários DOM --- // Seletores de conveniência const $ = document.querySelector.bind(document) const $$ = document.querySelectorAll.bind(document) // Funções de manipulação de elementos const hide = el => { if (el) el.style.display = 'none' } // Esconde o elemento const remove = el => { if (el) el.remove() } // Remove o elemento do DOM const click = el => { if (el) el.click() } // Simula um clique no elemento const rename = (el, text) => { if (el) el.textContent = text } // Renomeia o texto do elemento /** * Aplica uma função a elementos selecionados repetidamente em intervalos, * mas apenas se uma condição de verificação for atendida. Útil para modificar * elementos que são carregados dinamicamente ou podem mudar de estado. * Otimizado para pausar quando a aba não está visível. * * @param {string|Element|NodeList|Array<string|Element|NodeList>} selector - Seletor(es) CSS, elemento(s) ou NodeList(s). * @param {function(Element): boolean} check - Função que retorna true se a ação deve ser aplicada ao elemento. * @param {function(Element): void} fn - A função a ser executada no elemento se check retornar true. * @param {number} [interval=1000] - Intervalo de verificação em milissegundos. */ const perma = (selector, check, fn, interval = 1000) => { let intervalId = null // Armazena o ID do intervalo para poder pará-lo // Função que verifica e executa a ação nos elementos encontrados const checkAndExecute = () => { let elements = [] // Array para armazenar os elementos encontrados // Normaliza o(s) seletor(es) para um array de elementos const selectors = Array.isArray(selector) ? selector : [selector] selectors.forEach(item => { if (typeof item === 'string') { elements = elements.concat(Array.from($$(item))) // Seleciona por string CSS } else if (item instanceof Element) { elements.push(item) // Adiciona elemento diretamente } else if (item instanceof NodeList) { elements = elements.concat(Array.from(item)) // Adiciona elementos de NodeList } }) // Itera sobre os elementos encontrados e aplica a lógica elements.forEach(element => { try { // Verifica a condição e executa a função se for verdadeira if (element && check(element)) { fn(element) } } catch (error) { console.warn(`Better LMArena: Error in perma check/fn for selector "${selector}":`, error, element) stopInterval() // Para o intervalo em caso de erro para evitar spam no console } }) } // Inicia o intervalo de verificação const startInterval = () => { if (!intervalId) { // Evita múltiplos intervalos rodando checkAndExecute() // Executa imediatamente uma vez intervalId = setInterval(checkAndExecute, interval) // console.log(`Better LMArena: Perma interval started for selector "${selector}"`) } } // Para o intervalo de verificação const stopInterval = () => { if (intervalId) { clearInterval(intervalId) intervalId = null // console.log(`Better LMArena: Perma interval stopped for selector "${selector}"`) } } // Ouve mudanças na visibilidade da aba para pausar/retomar o intervalo document.addEventListener('visibilitychange', () => { if (document.hidden) { stopInterval() // Pausa quando a aba fica oculta } else { startInterval() // Retoma quando a aba fica visível } }) // Inicia o intervalo assim que a função é chamada startInterval() } /** * Espera que um ou mais elementos existam no DOM e então executa um callback. * Usa MutationObserver para eficiência, evitando polling constante. * * @param {string|Element|NodeList|Array<string|Element|NodeList|function(): Element|null>} selectors - Seletor(es) CSS, elemento(s), NodeList(s) ou função(ões) que retornam um elemento. * @param {function(Element): void} [callback=null] - Função a ser executada quando o primeiro elemento for encontrado. * @param {number} [slow=0] - Atraso opcional (ms) antes de executar o callback. * @returns {Promise<void>} - Promessa que resolve quando o elemento é encontrado e o callback executado. */ const when = (selectors = ['html'], callback = null, slow = 0) => { // Garante que selectors seja um array const selectorArray = Array.isArray(selectors) ? selectors : [selectors] return new Promise((resolve) => { // Função para executar o callback (com ou sem atraso) const executeCallback = (element) => { const execute = () => { if (callback) { try { callback(element) } catch (error) { console.error(`Better LMArena: Error in 'when' callback for selector "${selectors}":`, error, element) } } resolve() // Resolve a promessa } if (slow > 0) { setTimeout(execute, slow) } else { execute() } } // Verifica se algum dos seletores já corresponde a um elemento no DOM const checkSelectors = () => { for (const selector of selectorArray) { let element = null if (typeof selector === 'string') { element = $(selector) // Busca por seletor CSS } else if (selector instanceof Element || selector instanceof NodeList && selector.length > 0) { element = (selector instanceof NodeList) ? selector[0] : selector // Usa elemento ou primeiro de NodeList } else if (typeof selector === 'function') { try { element = selector() // Executa função para obter elemento } catch (error) { console.warn(`Better LMArena: Error executing selector function in 'when':`, error) continue // Pula para o próximo seletor em caso de erro na função } } // Se encontrou um elemento, executa o callback e retorna true if (element) { executeCallback(element) return true } } return false // Nenhum elemento encontrado ainda } // Se o elemento já existe, executa o callback e retorna if (checkSelectors()) { return } // Se não encontrou, configura um MutationObserver para observar adições ao DOM const observer = new MutationObserver((mutations) => { // Otimização: Verifica apenas se nós foram adicionados const nodesAdded = mutations.some(mutation => mutation.addedNodes.length > 0) if (nodesAdded) { // Se algum nó foi adicionado, verifica novamente os seletores if (checkSelectors()) { observer.disconnect() // Para de observar assim que encontrar } } }) // Começa a observar o body e seus descendentes observer.observe(document.body || document.documentElement, { childList: true, subtree: true }) // console.log(`Better LMArena: Waiting for selector(s):`, selectors) }) } // --- Modificações Específicas LMArena --- // Renomeia os botões das abas principais para nomes mais curtos ou descritivos // Usa 'perma' porque os elementos podem ser recriados ou ter o texto alterado pela aplicação. // A função 'check' garante que a renomeação ocorra apenas uma vez por estado. perma('#component-18-button', el => el.textContent !== 'Battle', el => rename(el, 'Battle'), 500) perma('#component-63-button', el => el.textContent !== 'Side-by-Side', el => rename(el, 'Side-by-Side'), 500) perma('#component-107-button', el => el.textContent !== 'Chat', el => rename(el, 'Chat'), 500) perma('#component-108-button', el => el.textContent !== 'Vision Chat', el => rename(el, 'Vision Chat'), 500) // Pode não existir mais perma('#component-140-button', el => el.textContent !== 'Ranking', el => rename(el, 'Ranking'), 500) perma('#component-231-button', el => el.textContent !== 'About', el => rename(el, 'About'), 500) // Remove blocos de texto/aviso e termos de serviço que ocupam espaço inicial // Usa 'when' porque esses elementos geralmente aparecem uma vez ao carregar a aba. when([ // Bloco de aviso no topo (o seletor pode mudar com atualizações do Gradio/LMSYS) () => $('gradio-app > .main > .wrap > .tabs > .tabitem > .gap > #notice_markdown'), // Blocos de Termos de Serviço (ToS) em diferentes abas () => $('#component-26 > .gap > .hide-container.block'), // ToS - Battle () => $('#component-139 > .gap > .hide-container.block'),// ToS - Chat? (Verificar ID) () => $('#component-95 > .gap > .hide-container.block'), // ToS - Side-by-Side? (Verificar ID) // Bloco de markdown no topo do Leaderboard () => $('#leaderboard_markdown > .svelte-1ed2p3z > .svelte-gq7qsu.prose'), ], remove, 50) // Pequeno delay para garantir que o elemento exista // Remove outros elementos de texto/botões menos úteis (IDs podem mudar) // Tenta remover o botão "About" e alguns outros componentes (potencialmente spacers ou text blocks). when([ '#component-151-button', // Botão "About"? Verificar se é o mesmo que #component-231 // IDs abaixo podem corresponder a blocos de texto/markdown ou spacers. Verificar no inspetor. '#component-54', '#component-87', '#component-114', '#component-11', ], remove, 100).then(() => { console.log('Better LMArena: Cleaned up initial text blocks and buttons.') // Ajusta o padding dos botões das abas após a remoção de outros elementos perma('.tab-nav button', el => el.style.padding !== 'var(--size-1) var(--size-3)', el => { el.style.padding = 'var(--size-1) var(--size-3)' }, 500) // Remove padding e borda dos containers das abas perma('.tabitem', el => el.style.padding !== '0px' || el.style.borderWidth !== '0px', el => { el.style.padding = '0' el.style.border = '0' }, 500) }) // Ajusta o layout principal da aplicação para ocupar mais espaço horizontal when('.app', el => { el.style.margin = '0 auto' // Mantém centralizado el.style.maxWidth = '100%' // Largura total el.style.padding = '0' // Remove padding externo }, 50) // Centraliza a barra de navegação das abas when('.tab-nav', el => { el.style.display = 'flex' // Usar flex para centralizar el.style.justifyContent = 'center' // Centraliza os botões horizontalmente el.style.gap = 'var(--spacing-lg)' // Adiciona um espaço entre os botões }, 50) // Ajusta a altura do chatbot para ocupar mais espaço vertical perma('#chatbot', el => el.style.height !== 'calc(80vh - 50px)', el => { // Ajuste dinâmico da altura el.style.height = 'calc(80vh - 50px)' // Ex: 80% da altura da viewport menos espaço para input/header }, 1000) // Reduz o espaçamento geral entre elementos (gap) perma('.gap', el => el.style.gap !== 'var(--spacing-sm)', el => { // Usa um espaçamento menor el.style.gap = 'var(--spacing-sm)' // Ex: 6px ou var(--spacing-sm) }, 1000) // Remove o arredondamento das bordas (estilo mais quadrado) perma(['button', 'textarea', '.gradio-textbox', '.block'], el => el.style.borderRadius !== '0px', el => { el.style.borderRadius = '0px' }, 1000) // Ajusta a caixa de input (remove bordas, padding, arredondamento) perma('#input_box', el => { let changed = false if (el.style.borderWidth !== '0px') { el.style.borderWidth = '0px'; changed = true } if (el.style.padding !== '0px') { el.style.padding = '0px'; changed = true } // Aplica ao pai também se necessário (alguns estilos podem estar no container) if (el.parentNode && el.parentNode.style.borderWidth !== '0px') { el.parentNode.style.borderWidth = '0px'; el.parentNode.style.borderRadius = '0px'; changed = true } // Aplica ao textarea filho const textarea = el.querySelector('textarea') if (textarea && textarea.style.borderRadius !== '0px') { textarea.style.borderRadius = '0px'; changed = true } return changed // Retorna true apenas se algo mudou }, el => el, 1000) // Condição de check simplificada, a lógica está na função fn // Renomeia e estiliza os botões de envio/regenerate/stop perma('.submit-button', el => el.textContent !== '⤴️', el => { el.style.minWidth = '40px' // Largura mínima el.textContent = '⤴️' // Ícone de envio el.style.padding = 'var(--size-1) var(--size-1)' // Padding menor }, 500) // Outros botões podem ter classes diferentes (ex: .generate-button, .stop-button) // Adicionar 'perma' para eles se necessário. // Remove borda e arredondamento da área de compartilhamento perma('#share-region-named', el => el.style.borderWidth !== '0px', el => { el.style.border = '0' el.style.borderRadius = '0' }, 1000) // Ajusta espaçamento em containers específicos do Svelte (se aplicável) perma('.svelte-15lo0d8', el => el.style.gap !== 'var(--spacing-md)', el => { el.style.gap = 'var(--spacing-md)' }, 1000) // Remove o link "Built with Gradio" no rodapé when('.built-with', remove, 1000) // Atraso maior pois pode carregar por último // Lógica específica: Clica automaticamente em "Direct Chat" se o pop-up "Model B" aparecer // O seletor '.svelte-nab2ao' pode ser específico de um componente modal que aparece. // É necessário verificar se esse seletor ainda é válido. when('.svelte-nab2ao', () => { console.log('Better LMArena: Detected Model B selection prompt.') // Espera um pouco para garantir que o botão esteja pronto e clica nele setTimeout(() => { const directChatButton = $('#component-123-button') // ID pode ter mudado if (directChatButton) { console.log('Better LMArena: Clicking "Direct Chat" button.') click(directChatButton) } else { console.warn('Better LMArena: "Direct Chat" button (#component-123-button) not found.') } }, 500) // Atraso para garantir que o botão esteja interativo }, 500) console.log('Better LMArena script loaded and running.') })()