您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Bugfix & speed-up for ChatGPT: cleans the conversation chat window by trimming old messages from the Browser DOM. Keeps only the latest N turns visible, preventing lag and excessive DOM size on long sessions. Includes manual “Clean now” button and auto-clean toggle.
// ==UserScript== // @name ChatGPT AutoCleaner v5 // @version 1.5 // @description Bugfix & speed-up for ChatGPT: cleans the conversation chat window by trimming old messages from the Browser DOM. Keeps only the latest N turns visible, preventing lag and excessive DOM size on long sessions. Includes manual “Clean now” button and auto-clean toggle. // @author Aleksey Maximov <[email protected]> // @match https://chat.openai.com/* // @match https://chatgpt.com/* // @grant none // @namespace 81e29c9d-b6e3-4210-b862-c93cb160f09a // @license MIT // ==/UserScript== /* WHY THIS FIX EXISTS (read this once): Problem: - The ChatGPT web app keeps adding conversation turns to the DOM indefinitely. - On long sessions the DOM grows huge → reflows/repaints become expensive → UI lags. - React also keeps its own internal arrays with messages, drafts, telemetry and other data that never gets freed properly. This is a major source of memory bloat, but the safe way to clean it without breaking features is still unclear. What this script changes: - On a timer, it trims old DOM turns (visual cleanup only). - Adds a **Clean now** button to trigger immediate trim. - Auto-clean can be enabled/disabled, and interval/keep count adjusted. - Skips auto-clean when the tab is hidden. Result: - Only the latest N messages remain in the DOM. - Old nodes are removed → browser memory/paint workload drops. - React’s hidden memory leaks still remain, but DOM cleanup alone already makes long sessions much smoother. Further fixes are TBD. */ (function () { 'use strict'; // ---------- UI ---------- function injectUI() { if (document.getElementById("chatgpt-cleaner-panel")) return; const defaults = { leaveOnly: 5, intervalSec: 10, enabled: false }; const stored = { leaveOnly: parseInt(localStorage.getItem("chatgpt-leaveOnly")) || defaults.leaveOnly, intervalSec: parseInt(localStorage.getItem("chatgpt-intervalSec")) || defaults.intervalSec, enabled: localStorage.getItem("chatgpt-enabled") !== "false" }; const container = document.createElement("div"); container.id = "chatgpt-cleaner-wrapper"; Object.assign(container.style, { position: "fixed", bottom: "8px", right: "8px", zIndex: 9999, fontFamily: "sans-serif" }); const toggleButton = document.createElement("button"); toggleButton.textContent = "⚙"; Object.assign(toggleButton.style, { background: stored.enabled ? "#444" : "red", color: "#fff", border: "none", borderRadius: "4px", padding: "2px 6px", cursor: "pointer", fontSize: "14px" }); toggleButton.title = "Toggle cleaner panel"; const panel = document.createElement("div"); panel.id = "chatgpt-cleaner-panel"; Object.assign(panel.style, { display: "none", marginTop: "4px", background: "#222", color: "#fff", padding: "10px 12px 10px 10px", borderRadius: "6px", fontSize: "12px", boxShadow: "0 0 6px rgba(0,0,0,0.5)", border: "1px solid #555", position: "relative", opacity: "0.95" }); panel.innerHTML = ` <div id="chatgpt-close" style="position:absolute;top:0px;right:2px;font-size:16px;font-weight:bold;color:#ccc;cursor:pointer;">✖</div> <label> Keep <input id="chatgpt-keep-count" type="number" value="${stored.leaveOnly}" min="1" style="width:52px;min-width:52px;padding:2px 6px 2px 4px;font-size:12px;background:#111;color:#fff;border:1px solid #555;box-sizing:border-box;"> messages </label> <br> <label> Interval <input id="chatgpt-interval" type="number" value="${stored.intervalSec}" min="2" style="width:52px;min-width:52px;padding:2px 6px 2px 4px;font-size:12px;background:#111;color:#fff;border:1px solid #555;box-sizing:border-box;"> sec </label> <br> <label><input type="checkbox" id="chatgpt-enabled" ${stored.enabled ? "checked" : ""}> Auto-clean enabled</label> <br> <button id="chatgpt-clean-now" style=" margin-top:6px;background:#008000;color:#fff;border:none;border-radius:4px; padding:2px 8px;cursor:pointer;font-size:12px;">Clean now</button> `; toggleButton.onclick = () => { panel.style.display = "block"; toggleButton.style.display = "none"; }; container.appendChild(toggleButton); container.appendChild(panel); document.body.appendChild(container); const countInput = panel.querySelector("#chatgpt-keep-count"); const intervalInput = panel.querySelector("#chatgpt-interval"); const enabledCheckbox = panel.querySelector("#chatgpt-enabled"); const cleanNowBtn = panel.querySelector("#chatgpt-clean-now"); const closeBtn = panel.querySelector("#chatgpt-close"); let leaveOnly = stored.leaveOnly; let intervalMs = Math.max(2000, stored.intervalSec * 1000); let enabled = stored.enabled; let intervalId = null; function scheduleClean(force = false) { if (!force) { if (!enabled) return; if (document.hidden) return; } cleanOldMessages(force); } // ---------- main cleaner (no gating here; gating is in scheduleClean) ---------- function cleanOldMessages(manual = false) { try { if (manual) console.info("[AutoCleaner] Manual clean"); // 1) Trim DOM (visual only) const all = document.querySelectorAll('[data-testid^="conversation-turn-"]'); if (all.length) { const lastAttr = all[all.length - 1].getAttribute("data-testid"); const last = parseInt(lastAttr?.split("-")[2]); if (!isNaN(last)) { all.forEach(item => { const idx = parseInt(item.getAttribute("data-testid")?.split("-")[2]); if (!isNaN(idx) && idx < last - leaveOnly) item.remove(); }); } } // console.info("[AutoCleaner] Working..."); } catch (e) { console.error("[AutoCleaner] clean error:", e); } } function startCleaner() { if (intervalId) clearInterval(intervalId); intervalId = setInterval(() => scheduleClean(false), intervalMs); console.info(`[AutoCleaner] Started: interval=${intervalMs}ms, keep=${leaveOnly}`); } // ---------- handlers ---------- enabledCheckbox.onchange = () => { enabled = enabledCheckbox.checked; localStorage.setItem("chatgpt-enabled", enabled); toggleButton.style.background = enabled ? "#444" : "red"; console.debug("[AutoCleaner] enabled =", enabled); }; countInput.oninput = () => { const val = parseInt(countInput.value); if (!isNaN(val) && val > 0) { leaveOnly = val; localStorage.setItem("chatgpt-leaveOnly", val); console.debug("[AutoCleaner] keep set to", leaveOnly); } }; intervalInput.oninput = () => { const val = parseInt(intervalInput.value); if (!isNaN(val) && val > 1) { intervalMs = Math.max(2000, val * 1000); localStorage.setItem("chatgpt-intervalSec", val); startCleaner(); } }; cleanNowBtn.onclick = () => { console.info("[AutoCleaner] CLEAN NOW clicked"); scheduleClean(true); panel.style.display = "none"; toggleButton.style.display = "inline-block"; }; closeBtn.onclick = () => { panel.style.display = "none"; toggleButton.style.display = "inline-block"; }; startCleaner(); } if (document.readyState === "complete" || document.readyState === "interactive") { injectUI(); } else { window.addEventListener("DOMContentLoaded", injectUI); } const observer = new MutationObserver(() => { if (!document.getElementById("chatgpt-cleaner-wrapper")) injectUI(); }); observer.observe(document.body, { childList: true, subtree: true }); })();