lmsys-enhancer

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

目前為 2023-12-04 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         lmsys-enhancer
// @namespace    http://tampermonkey.net/
// @version      0.5
// @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)",
    maxOutputTokenInputs: "#component-100 input",
    sendBtn: "#component-89",
    unnecessaryList: ["#component-93", "#component-83"],
  },
};

const originalSend = WebSocket.prototype.send;

var itemList;

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 maxOutputTokenInputs = document.querySelectorAll(
    scriptConfig.position.maxOutputTokenInputs
  );
  if (maxOutputTokenInputs.length >= 2) {
    maxOutputTokenInputs[1].max = String(maxValue);
    maxOutputTokenInputs[0].value = String(maxValue);
    maxOutputTokenInputs[1].value = String(maxValue);
    const changeEvent = new Event("change", {
      bubbles: true,
      cancelable: true,
    });
    maxOutputTokenInputs[1].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;
    document.querySelector(scriptConfig.position.sendBtn).click(); // send
  });
  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;
  document.querySelector(scriptConfig.position.sendBtn).click(); // send
}

// 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 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);
})();