lmsys-enhancer

Optimize your experience on the chat.lmsys.org with the `lmsys-enhancer` Tampermonkey script.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         lmsys-enhancer
// @namespace    http://tampermonkey.net/
// @version      0.7
// @description  Optimize your experience on the chat.lmsys.org with the `lmsys-enhancer` Tampermonkey script.
// @author       joshlee
// @match        https://chat.lmsys.org/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=lmsys.org
// @grant        none
// @license      MIT
// ==/UserScript==

const scriptConfig = {
  model: "gpt-4-turbo",
  loadLatestSession: true,
  firstMeetSessionHash: true,
  firstSend: true,
  currentSessionHash: "",
  dataKey: "session_hash_history",
  mainColor: "#DE6B2F",
  drawerWidth: 250,
  position: {
    main: "#component-1",
    changeModel: "#model_selector_row label",
    directChat: ".tab-nav.scroll-hide button:nth-child(3)",
    maxOutputTokenInput: "//span[text()='Max output tokens']/../../input",
    maxOutputTokenBar: "//span[text()='Max output tokens']/../../../../input",
    sendBtn: '//button[text()="Send"]',
    unnecessaryList: ["#component-93", "#component-83"],
  },
};

const originalSend = WebSocket.prototype.send;

var itemList;

function $x(xpathToExecute) {
  var result = [];
  var nodesSnapshot = document.evaluate(
    xpathToExecute,
    document,
    null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
    null
  );
  for (var i = 0; i < nodesSnapshot.snapshotLength; i++) {
    result.push(nodesSnapshot.snapshotItem(i));
  }
  return result;
}

function removeUselessElements() {
  // Remove unnecessary notices and components
  document
    .querySelectorAll("#notice_markdown")
    .forEach((elem) => elem.remove());
  scriptConfig.position.unnecessaryList.forEach((v) => {
    const componentToRemove = document.querySelector(v);
    if (componentToRemove) componentToRemove.remove();
  });
}

function setMaxOutputToken(maxValue = 4096) {
  // Set the maximum token output to the desired value
  const [, , input] = $x(scriptConfig.position.maxOutputTokenInput);
  const [, , bar] = $x(scriptConfig.position.maxOutputTokenBar);

  if (!input || !bar) {
    console.error("Set max output token failure!");
    return;
  }

  input.value = String(maxValue);
  bar.max = String(maxValue);
  bar.value = String(maxValue);

  const changeEvent = new Event("change", { bubbles: true, cancelable: true });
  bar.dispatchEvent(changeEvent);
}

function changeModel(model) {
  document.querySelector(scriptConfig.position.changeModel).click();
  document.querySelector(`li[data-value=${model}]`).dispatchEvent(
    new MouseEvent("mousedown", {
      view: window,
      bubbles: true,
      cancelable: true,
    })
  );
}

function hackWsSend(data) {
  let d = JSON.parse(data);
  // get session_hash
  if (d.session_hash) {
    // first create session
    if (scriptConfig.firstMeetSessionHash) {
      scriptConfig.currentSessionHash = d.session_hash;
      scriptConfig.firstMeetSessionHash = false;
    }
    if (
      scriptConfig.loadLatestSession &&
      d.fn_index === 38 &&
      getLastestSessionToggleBtn()
    ) {
      // skip creat new session
      d.fn_index = 41;
      d.data = [null, scriptConfig.model, ""];
      originalSend.call(this, data);
      toggleLastestSession();
      return;
    }
    // first send message
    if (scriptConfig.firstSend && d.fn_index === 39) {
      saveCurrentSession();
      scriptConfig.firstSend = false;
    }

    // modify session_hash
    if (scriptConfig.currentSessionHash) {
      d.session_hash = scriptConfig.currentSessionHash;
      data = JSON.stringify(d);
    }
  }
  originalSend.call(this, data);
}

function createSessionItem(text, sessionHash) {
  var itemContainer = document.createElement("div");
  itemContainer.style.display = "flex";
  itemContainer.style.justifyContent = "space-between";
  itemContainer.style.alignItems = "center";
  itemContainer.style.padding = "10px";
  itemContainer.style.borderBottom = "1px solid #ccc";
  itemContainer.className = "session-item-container";
  itemContainer.setAttribute("data-session-hash", sessionHash);

  var itemText = document.createElement("div");
  itemText.textContent = text;
  itemContainer.appendChild(itemText);

  // Create toggle session button
  var toggleButton = document.createElement("button");
  toggleButton.textContent = "⇢";
  toggleButton.style.marginLeft = "5px";
  toggleButton.style.padding = "5px 10px";
  toggleButton.style.border = "none";
  toggleButton.style.borderRadius = "4px";
  toggleButton.style.backgroundColor = "#4CAF50";
  toggleButton.style.color = "white";
  toggleButton.style.cursor = "pointer";
  // Button hover effect
  toggleButton.addEventListener("mouseover", function () {
    this.style.backgroundColor = "#45a049";
  });
  toggleButton.addEventListener("mouseout", function () {
    this.style.backgroundColor = "#4CAF50";
  });
  toggleButton.addEventListener("click", function () {
    scriptConfig.currentSessionHash = sessionHash;
    clickSendBtn();
  });
  itemContainer.appendChild(toggleButton);

  // Create Delete button
  var deleteButton = document.createElement("button");
  deleteButton.textContent = "✖";
  deleteButton.style.marginLeft = "5px";
  deleteButton.style.padding = "5px 10px";
  deleteButton.style.border = "none";
  deleteButton.style.borderRadius = "4px";
  deleteButton.style.backgroundColor = "#f44336";
  deleteButton.style.color = "white";
  deleteButton.style.cursor = "pointer";
  // Button hover effect
  deleteButton.addEventListener("mouseover", function () {
    this.style.backgroundColor = "#e53935";
  });
  deleteButton.addEventListener("mouseout", function () {
    this.style.backgroundColor = "#f44336";
  });
  deleteButton.addEventListener("click", function () {
    removeSessionHash(sessionHash);
    itemContainer.remove();
  });

  itemContainer.appendChild(deleteButton);

  itemContainer.highlight = function () {
    itemContainer.classList.add("highlighted-session");
    toggleButton.classList.add("disabled-button");
    deleteButton.classList.add("disabled-button");
  };

  itemContainer.unhighlight = function () {
    itemContainer.classList.remove("highlighted-session");
    toggleButton.classList.remove("disabled-button");
    deleteButton.classList.remove("disabled-button");
  };

  if (sessionHash === scriptConfig.currentSessionHash) {
    itemContainer.highlight();
  }

  itemList.appendChild(itemContainer);
  updateHighlightedSessions();
}

function createHistorySessionElement() {
  // Create Float Button
  var floatButton = document.createElement("button");
  floatButton.textContent = "☰";
  floatButton.style.position = "fixed";
  floatButton.style.left = "20px";
  floatButton.style.top = "50%";
  floatButton.style.transform = "translateY(-50%)";
  floatButton.style.zIndex = "9999";
  floatButton.style.padding = "15px";
  floatButton.style.borderRadius = "100%";
  floatButton.style.backgroundColor = "#212936";
  floatButton.style.color = "white";
  floatButton.style.border = "none";
  floatButton.style.boxShadow = "0 2px 5px rgba(0,0,0,0.3)";
  floatButton.style.transition = "left 0.3s ease";
  floatButton.style.cursor = "pointer";
  floatButton.addEventListener("click", function () {
    if (drawer.style.left === `-${scriptConfig.drawerWidth}px`) {
      drawer.style.left = "0";
      floatButton.style.left = `${scriptConfig.drawerWidth}px`;
    } else {
      drawer.style.left = `-${scriptConfig.drawerWidth}px`;
      floatButton.style.left = "20px";
    }
  });
  document.body.appendChild(floatButton);

  // Create drawer container
  var drawer = document.createElement("div");
  drawer.style.position = "fixed";
  drawer.style.left = "-250px";
  drawer.style.top = "0";
  drawer.style.width = `${scriptConfig.drawerWidth}px`;
  drawer.style.height = "100%";
  drawer.style.backgroundColor = "#212936";
  drawer.style.transition = "0.3s";
  drawer.style.zIndex = "9998";
  drawer.style.padding = "15px";
  drawer.style.boxSizing = "border-box";
  drawer.style.color = "white";
  drawer.style.overflowY = "auto";
  document.body.appendChild(drawer);

  // Create Deawer Title
  var drawerTitle = document.createElement("h2");
  drawerTitle.textContent = "History Session";
  drawerTitle.style.padding = "10px";
  drawerTitle.style.marginTop = "0";
  drawerTitle.style.color = "white";
  drawerTitle.style.backgroundColor = "#2c3e50";
  drawerTitle.style.borderBottom = "1px solid #ccc";
  drawer.appendChild(drawerTitle);

  // Create session item container
  itemList = document.createElement("div");
  drawer.appendChild(itemList);

  // Click outside the drawer to turn off the drawer's function
  document.addEventListener("click", function (event) {
    // Check if the click is happening outside of the drawer or floating button
    if (!drawer.contains(event.target) && !floatButton.contains(event.target)) {
      updateHighlightedSessions();
      // Opened
      if (drawer.style.left === "0px") {
        drawer.style.left = `-${scriptConfig.drawerWidth}px`; // hidden
        floatButton.style.left = "20px"; // restore
      }
    }
  });

  // Create New Chat Button
  const newChatButton = document.createElement("button");
  newChatButton.textContent = "New Chat";
  newChatButton.style.position = "absolute";
  newChatButton.style.bottom = "20px";
  newChatButton.style.left = "50%";
  newChatButton.style.transform = "translateX(-50%)";
  newChatButton.style.padding = "10px 30px";
  newChatButton.style.border = "none";
  newChatButton.style.borderRadius = "5px";
  newChatButton.style.backgroundColor = scriptConfig.mainColor;
  newChatButton.style.color = "white";
  newChatButton.style.cursor = "pointer";
  newChatButton.addEventListener("click", function () {
    handleNewChatClick();
  });
  drawer.appendChild(newChatButton);
  loadSessionHashHistory();
}

function handleNewChatClick() {
  scriptConfig.currentSessionHash = Math.random().toString(36).substring(2);
  scriptConfig.firstMeetSessionHash = true;
  scriptConfig.firstSend = true;
  scriptConfig.loadLatestSession = false;
  clickSendBtn();
}

// Update the highlighted state of the session list
function updateHighlightedSessions() {
  const sessionItems = itemList.getElementsByClassName(
    "session-item-container"
  );
  for (const item of sessionItems) {
    item.unhighlight(); // remove all highlight state
  }

  const newItem = itemList.querySelector(
    `[data-session-hash="${scriptConfig.currentSessionHash}"]`
  );
  if (newItem) {
    newItem.highlight();
    return;
  }
}

function getLastestSessionToggleBtn() {
  const gotoBtn = document.querySelectorAll(
    ".session-item-container button"
  )[0];
  return gotoBtn;
}

function toggleLastestSession() {
  scriptConfig.firstSend = false;
  scriptConfig.firstMeetSessionHash = false;
  getLastestSessionToggleBtn().click();
}

function loadSessionHashHistory() {
  itemList.innerHTML = "";
  const list = JSON.parse(localStorage.getItem(scriptConfig.dataKey)) || [];
  for (let i = list.length - 1; i >= 0; i--) {
    let e = list[i];
    createSessionItem(e.time, e.session_hash);
  }
  updateHighlightedSessions();
}

function getCurrentFormattedTime() {
  const now = new Date();
  const year = now.getFullYear().toString(); // year
  const month = (now.getMonth() + 1).toString().padStart(2, "0"); // month
  const date = now.getDate().toString().padStart(2, "0"); // day
  const hours = now.getHours().toString().padStart(2, "0"); // hout
  const minutes = now.getMinutes().toString().padStart(2, "0");
  return `${year}-${month}-${date} ${hours}:${minutes}`;
}

function removeSessionHash(targetSessionHash) {
  const list = JSON.parse(localStorage.getItem(scriptConfig.dataKey));
  let filteredList = list.filter(
    (item) => item.session_hash !== targetSessionHash
  );
  localStorage.setItem(scriptConfig.dataKey, JSON.stringify(filteredList));
}

function clickSendBtn() {
  $x(scriptConfig.position.sendBtn)[2].click();
}

function saveCurrentSession() {
  const value = localStorage.getItem(scriptConfig.dataKey);
  let list = [];
  if (value) {
    list = JSON.parse(value);
  }
  list.push({
    time: getCurrentFormattedTime(),
    session_hash: scriptConfig.currentSessionHash,
  });
  localStorage.setItem(scriptConfig.dataKey, JSON.stringify(list));
  loadSessionHashHistory();
}

function initialize() {
  window.alert = null;
  WebSocket.prototype.send = hackWsSend;
  document.querySelector(scriptConfig.position.directChat).click();

  removeUselessElements();
  setMaxOutputToken();
  changeModel(scriptConfig.model);
}

(function main() {
  // Add a style to highlight the current session
  const style = document.createElement("style");
  style.type = "text/css";
  style.innerHTML = `
  .highlighted-session {
    background-color: #e0e0e0;
    color: #333;
    border-left: 3px solid #ffeb3b;
  }
  .disabled-button {
    pointer-events: none;
    opacity: 0.6; 
  }
  `;
  document.head.appendChild(style);
  createHistorySessionElement();
  // Create a MutationObserver instance and pass the callback function
  const observer = new MutationObserver((mutations, observer) => {
    // Find the target element using its ID (you may adjust the selector according to your needs)
    const targetElement = document.querySelector(scriptConfig.position.main);
    if (targetElement) {
      // If the target element exists, execute the optimize function and stop observing
      initialize();
      observer.disconnect(); // Stop observing
    }
  });

  // Configuration for the observer:
  const observerConfig = { childList: true, subtree: true };

  // Select the node that will be observed for mutations
  const targetNode = document.body; // You may specify a more specific parent node to reduce the scope of observation

  // Call the observer's observe method to start observing the target node
  observer.observe(targetNode, observerConfig);
})();