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