Better LMarena (lmsys) Chat

make chat lmarena (lmsys) chat better and clean

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name Better LMarena (lmsys) Chat
// @namespace https://github.com/insign/better-lmsys-chat
// @version 202412221856
// @description make chat lmarena (lmsys) chat better and clean
// @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'

  const $      = document.querySelector.bind(document)
  const $$     = document.querySelectorAll.bind(document)
  const hide   = el => el.style.display = 'none'
  const remove = el => el.remove()
  const click  = el => el.click()

  const rename = (el, text) => el.textContent = text

  /**
   * Executes a function on selected elements repeatedly until the condition is met.
   *
   * @param {string|Element|NodeList|Array<string|Element|NodeList>} selector - The CSS selector, element, NodeList, or an array containing a combination of these types to select the elements.
   * @param {function(Element): boolean} check - A function that checks if the condition is met for an element. It should return `true` if the condition is met and `false` otherwise.
   * @param {function(Element): void} fn - The function to be executed on each element that meets the condition.
   * @param {number} [interval=50] - The interval in milliseconds between each check of the elements.
   *
   * @example
   * // Example usage with CSS selector
   * perma('.example-class', (el) => el.textContent !== 'Example', (el) => {
   *   el.textContent = 'Example';
   * });
   *
   * @example
   * // Example usage with element
   * const element = document.querySelector('#example-id');
   * perma(element, (el) => el.classList.contains('active'), (el) => {
   *   el.classList.add('active');
   * });
   *
   * @example
   * // Example usage with NodeList
   * const elements = document.querySelectorAll('.example-class');
   * perma(elements, (el) => el.dataset.status !== 'ready', (el) => {
   *   el.dataset.status = 'ready';
   * });
   *
   * @example
   * // Example usage with an array of selectors
   * perma(['.example-class', '#example-id'], (el) => el.style.display !== 'none', (el) => {
   *   el.style.display = 'none';
   * });
   */
  const perma = (selector, check, fn, interval = 1000) => {
    let intervalId = null

    const checkAndExecute = () => {
      let elements = []

      if (Array.isArray(selector)) {
        selector.forEach((item) => {
          if (typeof item === 'string') {
            elements = elements.concat(Array.from($$(item)))
          }
          else if (item instanceof Element) {
            elements.push(item)
          }
          else if (item instanceof NodeList) {
            elements = elements.concat(Array.from(item))
          }
        })
      }
      else if (typeof selector === 'string') {
        elements = $$(selector)
      }
      else if (selector instanceof Element) {
        elements = [ selector ]
      }
      else if (selector instanceof NodeList) {
        elements = Array.from(selector)
      }

      elements.forEach((element) => {
        if (check(element)) {
          fn(element)
        }
      })
    }

    const startInterval = () => {
      if (!intervalId) {
        intervalId = setInterval(checkAndExecute, interval)
      }
    }

    const stopInterval = () => {
      if (intervalId) {
        clearInterval(intervalId)
        intervalId = null
      }
    }

    document.addEventListener('visibilitychange', function() {
      if (document.hidden) {
        stopInterval()
      }
      else {
        startInterval()
      }
    })

    startInterval() // Start initially
  }
  /**
   * Waits for specific elements to be present in the DOM and executes a callback function when they are found.
   *
   * @param {string|Element|NodeList|Array<string|Element|NodeList|function(): Element|null>} [selectors=['html']] - The CSS selector(s), element(s), NodeList(s), or array function(s) that return an element or null. Can be a single selector, element, NodeList, array function, or an array containing a combination of these types. Defaults to ['html'] if not provided.
   * @param {function(Element): void} [callback=null] - The callback function to be executed when the specified elements are found. It receives the first found element as an argument. If not provided, the function will resolve without executing a callback.
   * @param {number} [slow=0] - The delay in milliseconds before executing the callback function. If set to 0 (default), the callback will be executed immediately after the elements are found.
   *
   * @returns {Promise<void>} A promise that resolves when the specified elements are found and the callback function (if provided) has been executed.
   *
   * @example
   * // Example usage with a single CSS selector
   * when('.example-class', (element) => {
   *   console.log('Element found:', element);
   * });
   *
   * @example
   * // Example usage with multiple CSS selectors
   * when(['.example-class', '#example-id'], (element) => {
   *   console.log('Element found:', element);
   * });
   *
   * @example
   * // Example usage with an array function
   * when(() => document.querySelector('.example-class'), (element) => {
   *   console.log('Element found:', element);
   * });
   *
   * @example
   * // Example usage with a delay before executing the callback
   * when('.example-class', (element) => {
   *   console.log('Element found:', element);
   * }, 1000);
   *
   * @example
   * // Example usage without a callback function
   * when('.example-class').then(() => {
   *   console.log('Element found');
   * });
   */
  const when  = (selectors = [ 'html' ], callback = null, slow = 0) => {
    if (!Array.isArray(selectors)) {
      selectors = [ selectors ]
    }

    return new Promise((resolve) => {
      const executeCallback = (element) => {
        if (callback) {
          if (slow > 0) {
            setTimeout(() => {
              callback(element)
              resolve()
            }, slow)
          }
          else {
            callback(element)
            resolve()
          }
        }
        else {
          resolve()
        }
      }

      const checkSelectors = () => {
        for (const selector of selectors) {
          let element = null

          if (typeof selector === 'string') {
            element = $(selector)
          }
          else if (selector instanceof Element) {
            element = selector
          }
          else if (selector instanceof NodeList) {
            element = selector[0]
          }
          else if (typeof selector === 'function') {
            element = selector()
            if (element === null) {
              continue
            }
          }

          if (element) {
            executeCallback(element)
            return true
          }
        }
        return false
      }

      if (checkSelectors()) {
        return
      }

      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          Array.from(mutation.addedNodes).forEach((node) => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              if (checkSelectors()) {
                observer.disconnect()
              }
            }
          })
        })
      })

      observer.observe(document.body, { childList: true, subtree: true })
    })
  }

  // when('.prose', el => !el.closest('.wrap'), remove)

  perma('#component-18-button', el => el.textContent !== 'Battle', el => rename(el, 'Battle'), 100)
  perma('#component-63-button', el => el.textContent !== 'SbS', el => rename(el, 'Side-by-Side'), 100)
  perma('#component-107-button', el => el.textContent !== 'Chat', el => rename(el, 'Chat'), 100)
  perma('#component-108-button', el => el.textContent !== 'Vision Chat', el => rename(el, 'Vision Chat'), 100)
  perma('#component-140-button', el => el.textContent !== 'Ranking', el => rename(el, 'Ranking'), 100)
  perma('#component-231-button', el => el.textContent !== 'About', el => rename(el, 'About'), 100)

  // ###notice_markdown > .svelte-1ed2p3z > .svelte-gq7qsu.prose > .prose.svelte-8tpqd2.md
  when([
    '#notice_markdown > .svelte-1ed2p3z > .svelte-gq7qsu.prose > .prose.svelte-8tpqd2.md', // top blocks of notice
    '#component-26 > .gap.svelte-vt1mxs > .hide-container.padded.svelte-12cmxck.block', // ToS
    '#component-139 > .gap.svelte-vt1mxs > .hide-container.padded.svelte-12cmxck.block', // ToS
    '#component-95 > .gap.svelte-vt1mxs > .hide-container.padded.svelte-12cmxck.block', // ToS
    '#leaderboard_markdown > .svelte-1ed2p3z > .svelte-gq7qsu.prose > .prose.svelte-8tpqd2.md', // top blocks of leaderboard
  ], remove)


  when([
    '#component-151-button', '#component-54', '#component-87', '#component-114', '#component-11',
  ], remove).then(() => { // all texts and about button :(

    perma('.tab-nav button', el => el.style.padding !== 'var(--size-1) var(--size-3)', el => {
      console.info('padding', el.style.padding)
      el.style.padding = 'var(--size-1) var(--size-3)'
    }, 100)

    perma('.tabitem', el => el.style.padding !== '0px', el => {
      console.info(el.style.padding)

      el.style.padding = 0
      el.style.border  = 0
    })
  })

  when('.app', el => {
    el.style.margin   = '0 auto'
    el.style.maxWidth = '100%'
    el.style.padding  = 0
  })

  when('.tab-nav', el => {
    el.style.display   = 'flow'
    el.style.textAlign = 'center'
  })

  perma('#chatbot', el => el.style.height !== '75vh', el => {
    console.info('height', el.style.height)
    el.style.height = '75vh'
  })

  perma('.gap', el => el.style.gap !== '6px', el => {
    console.info('gap', el.style.gap)
    el.style.gap = '6px'
  })


  // no-radius
  perma([ 'button' ], el => el.style.borderRadius !== '0px', el => {
    console.info('border-radius', el.style.borderRadius)
    el.style.borderRadius = 0
  })

  perma('#input_box', el => el.style.border !== '0px', el => {
    console.info('Found input_box parent')
    el.style.border                  = 0
    el.style.padding                 = 0
    el.parentNode.style.border       = 0
    el.parentNode.style.borderRadius = 0

    // run on the child textarea
    el.querySelector('textarea').style.borderRadius = 0
  })

  // buttons send, 1123
  perma([
    '.submit-button',
  ], el => el.style.minWidth !== '35px', el => {
    console.info('buttons send', el.style.minWidth)
    el.style.minWidth = '35px'
    el.textContent    = '⤴️'
  })

  perma('#share-region-named', el => el.style.border !== '0px', el => {
    el.style.border       = 0
    el.style.borderRadius = 0
  })


  // gapper
  perma('.svelte-15lo0d8', el => el.style.gap !== 'var(--spacing-md)', el => {
    console.info('gap', el.style.gap)
    el.style.gap = 'var(--spacing-md)'
  })

  when('.built-with', remove, 1000)

  // When appears "Model B" clicks on "Direct Chat" - I needed to use another setTimeout inside
  when('.svelte-nab2ao', () => setTimeout(() => $('#component-123-button').click(), 1000))


})()