Model Selector for lmarena (Text-Based + Debug)

Auto-select model and auto-submit prompt on lmarena (robust + debug)

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         Model Selector for lmarena (Text-Based + Debug)
// @namespace    http://tampermonkey.net/
// @version      2026-01-04
// @description  Auto-select model and auto-submit prompt on lmarena (robust + debug)
// @author       Animesh Dhakal
// @match        https://lmarena.ai/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  /* ===================== DEBUG HELPERS ===================== */

  const LOG  = (...a) => console.log('%c[LM-Arena]', 'color:#4CAF50;font-weight:bold', ...a);
  const WARN = (...a) => console.warn('%c[LM-Arena]', 'color:#FFC107;font-weight:bold', ...a);
  const ERR  = (...a) => console.error('%c[LM-Arena]', 'color:#F44336;font-weight:bold', ...a);

  /* ===================== UTILITIES ===================== */

  function waitForElm(selector, timeout = 15000) {
    LOG(`Waiting for: ${selector}`);
    return new Promise((resolve, reject) => {
      const found = document.querySelector(selector);
      if (found) return resolve(found);

      const obs = new MutationObserver(() => {
        const el = document.querySelector(selector);
        if (el) {
          obs.disconnect();
          resolve(el);
        }
      });

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

      setTimeout(() => {
        obs.disconnect();
        reject(new Error(`Timeout: ${selector}`));
      }, timeout);
    });
  }

  const sleep = ms => new Promise(r => setTimeout(r, ms));

  function isDisabled(btn) {
    return (
      btn.disabled ||
      btn.getAttribute('aria-disabled') === 'true' ||
      btn.classList.contains('disabled')
    );
  }

  async function waitUntilEnabled(btn, timeout = 10000) {
    const start = Date.now();
    while (isDisabled(btn)) {
      if (Date.now() - start > timeout) {
        throw new Error('Submit button never enabled');
      }
      await sleep(100);
    }
    return btn;
  }

  function setReactInputValue(input, value) {
    const proto = Object.getPrototypeOf(input);
    const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;

    if (setter) setter.call(input, value);
    else input.value = value;

    input.dispatchEvent(new Event('input', { bubbles: true }));
    input.dispatchEvent(new Event('change', { bubbles: true }));
  }

  /* ===================== MAIN LOGIC ===================== */

  async function run() {
    LOG('Script started');

    const params = new URLSearchParams(window.location.search);
    let model = params.get('model');
    const query = params.get('query');
    const chatModality = params.get('chat-modality') || 'direct';

    if (!model) {
      model = chatModality === 'direct'
        ? 'gpt'
        : 'ppl-sonar-pro-high';
      LOG('No model param, defaulting to:', model);
    }

    const modelLower = model.toLowerCase();
    LOG('Target model text:', modelLower);

    /* wait for message box (SPA-safe entry point) */
    await waitForElm('[name="message"]');

    /* open model combobox */
    const comboboxes = [...document.querySelectorAll("button[role='combobox']")];
    LOG(`Comboboxes found: ${comboboxes.length}`);

    const modelCombobox = comboboxes[1];

    if (!modelCombobox) {
      ERR('Model combobox not found');
      return;
    }

    modelCombobox.click();
    LOG('Model dropdown opened');

    /* wait for options to appear */
    await waitForElm('div[role="option"]');

    /* select model by VISIBLE TEXT (FIX) */
    const options = [...document.querySelectorAll('div[role="option"]')];
    LOG(`Model options found: ${options.length}`);

    const modelOption = options.find(opt =>
      opt.textContent.toLowerCase().includes(modelLower)
    );

    if (!modelOption) {
      ERR(
        'Model not found by text. Available models:',
        options.map(o => o.textContent.trim())
      );
      return;
    }

    modelOption.scrollIntoView({ block: 'center' });
    modelOption.click();
    LOG('Model selected:', modelOption.textContent.trim());

    if (!query) {
      WARN('No query provided — stopping after model selection');
      return;
    }

    /* fill message */
    const messageInput = await waitForElm('[name="message"]');
    setReactInputValue(messageInput, query);
    LOG('Message filled');

    /* submit */
    const submitButton = await waitForElm('button[type="submit"]');
    await waitUntilEnabled(submitButton);
    submitButton.click();

    LOG('Prompt submitted successfully');
  }

  /* ===================== SPA RE-RUN SUPPORT ===================== */

  let lastUrl = location.href;
  setInterval(() => {
    if (location.href !== lastUrl) {
      lastUrl = location.href;
      LOG('URL changed, re-running');
      run().catch(e => ERR(e));
    }
  }, 1000);

  /* ===================== BOOT ===================== */

  run().catch(e => ERR('Fatal error:', e));

})();