ChatGPT LagFix Lite

Reduces ChatGPT UI lag by disabling costly visuals, collapsing old turns, lazy-loading media, and trimming sidebar rendering.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         ChatGPT LagFix Lite
// @namespace    local.chatgpt.lagfix
// @version      1.0.0
// @license      MIT
// @description  Reduces ChatGPT UI lag by disabling costly visuals, collapsing old turns, lazy-loading media, and trimming sidebar rendering.
// @author       rexxx
// @match        https://chatgpt.com/*
// @match        https://chat.openai.com/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(() => {
  "use strict";

  const STORAGE_KEY = "chatgpt_lagfix_lite_settings_v1";

  const DEFAULTS = {
    speedMode: true,
    collapseOldTurns: true,
    keepLatestTurns: 12,
    trimSidebar: true,
    keepSidebarItems: 30,
    hideSidebar: false,
    lazyMedia: true,
    deChrome: true,
    showPanel: true
  };

  let settings = loadSettings();
  let scanQueued = false;
  let observerStarted = false;

  function loadSettings() {
    try {
      return { ...DEFAULTS, ...JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}") };
    } catch {
      return { ...DEFAULTS };
    }
  }

  function saveSettings() {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
  }

  function idle(fn) {
    if ("requestIdleCallback" in window) {
      requestIdleCallback(fn, { timeout: 800 });
    } else {
      setTimeout(fn, 80);
    }
  }

  function queueScan() {
    if (scanQueued) return;
    scanQueued = true;

    setTimeout(() => {
      scanQueued = false;
      idle(scan);
    }, 120);
  }

  function injectStyle() {
    if (document.getElementById("cglf-style")) return;

    const style = document.createElement("style");
    style.id = "cglf-style";
    style.textContent = `
      html.cglf-speed *,
      html.cglf-speed *::before,
      html.cglf-speed *::after {
        animation-duration: 1ms !important;
        animation-delay: 0ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 1ms !important;
        transition-delay: 0ms !important;
        scroll-behavior: auto !important;
      }

      html.cglf-speed [class*="animate-"],
      html.cglf-speed [style*="animation"] {
        animation: none !important;
      }

      html.cglf-speed [class*="backdrop"],
      html.cglf-speed [style*="backdrop-filter"],
      html.cglf-speed [style*="-webkit-backdrop-filter"] {
        backdrop-filter: none !important;
        -webkit-backdrop-filter: none !important;
      }

      html.cglf-speed [class*="shadow"],
      html.cglf-speed [style*="box-shadow"] {
        box-shadow: none !important;
      }

      html.cglf-speed main article,
      html.cglf-speed main [data-message-author-role] {
        content-visibility: auto;
        contain-intrinsic-size: 1px 180px;
      }

      html.cglf-collapse [data-cglf-old-turn="1"]:not([data-cglf-expanded="1"]) > :not(.cglf-placeholder) {
        display: none !important;
      }

      html.cglf-collapse [data-cglf-old-turn="1"] {
        contain: layout paint style !important;
        margin-block: 4px !important;
      }

      .cglf-placeholder {
        width: 100%;
        display: block;
        text-align: left;
        cursor: pointer;
        border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
        border-radius: 10px;
        padding: 7px 10px;
        margin: 4px 0;
        font: 12px/1.35 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
        opacity: 0.72;
        background: color-mix(in srgb, Canvas 85%, currentColor 5%);
        color: inherit;
      }

      .cglf-placeholder:hover {
        opacity: 1;
      }

      html.cglf-trim-sidebar [data-cglf-sidebar-old="1"] {
        display: none !important;
      }

      html.cglf-hide-sidebar aside,
      html.cglf-hide-sidebar nav[aria-label*="Chat" i],
      html.cglf-hide-sidebar [data-testid*="sidebar" i] {
        display: none !important;
      }

      html.cglf-dechrome [aria-label*="Share" i],
      html.cglf-dechrome [data-testid*="share" i],
      html.cglf-dechrome [aria-label*="Copy link" i],
      html.cglf-dechrome [data-testid*="announcement" i],
      html.cglf-dechrome [class*="announcement" i],
      html.cglf-dechrome [class*="upsell" i],
      html.cglf-dechrome [data-testid*="upsell" i] {
        display: none !important;
      }

      #cglf-panel {
        position: fixed;
        left: 10px;
        bottom: 10px;
        z-index: 2147483647;
        font: 12px/1.35 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
        color: CanvasText;
      }

      #cglf-panel.cglf-hidden {
        display: none !important;
      }

      #cglf-panel button {
        font: inherit;
      }

      #cglf-panel-toggle {
        border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
        border-radius: 999px;
        padding: 7px 10px;
        cursor: pointer;
        background: Canvas;
        color: CanvasText;
        opacity: 0.82;
      }

      #cglf-panel-toggle:hover {
        opacity: 1;
      }

      #cglf-panel-body {
        display: none;
        min-width: 210px;
        margin-top: 6px;
        padding: 8px;
        border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
        border-radius: 12px;
        background: Canvas;
        box-shadow: 0 8px 30px rgba(0,0,0,.18);
      }

      #cglf-panel.cglf-open #cglf-panel-body {
        display: block;
      }

      #cglf-panel-body button {
        width: 100%;
        display: block;
        margin: 5px 0;
        padding: 6px 8px;
        border-radius: 8px;
        border: 1px solid color-mix(in srgb, CanvasText 15%, transparent);
        background: color-mix(in srgb, Canvas 90%, CanvasText 5%);
        color: CanvasText;
        cursor: pointer;
        text-align: left;
      }

      #cglf-panel-body button:hover {
        background: color-mix(in srgb, Canvas 82%, CanvasText 9%);
      }

      #cglf-panel-body .cglf-note {
        opacity: .66;
        margin: 6px 2px 2px;
      }
    `;

    const root = document.head || document.documentElement;
    if (root) root.appendChild(style);
  }

  function applyRootClasses() {
    const root = document.documentElement;
    if (!root) return;

    root.classList.toggle("cglf-speed", !!settings.speedMode);
    root.classList.toggle("cglf-collapse", !!settings.collapseOldTurns);
    root.classList.toggle("cglf-trim-sidebar", !!settings.trimSidebar);
    root.classList.toggle("cglf-hide-sidebar", !!settings.hideSidebar);
    root.classList.toggle("cglf-dechrome", !!settings.deChrome);
  }

  function getConversationTurns() {
    const articleTurns = [
      ...document.querySelectorAll(
        'main article[data-testid*="conversation-turn" i], main article'
      )
    ].filter(el => el.querySelector('[data-message-author-role]'));

    if (articleTurns.length) return uniqueElements(articleTurns);

    const roleNodes = [
      ...document.querySelectorAll('main [data-message-author-role]')
    ];

    return uniqueElements(
      roleNodes.map(node =>
        node.closest('article, [data-testid*="conversation-turn" i], .group, .relative') || node
      )
    );
  }

  function uniqueElements(elements) {
    const seen = new Set();
    const out = [];

    for (const el of elements) {
      if (!el || seen.has(el)) continue;
      seen.add(el);
      out.push(el);
    }

    return out;
  }

  function summarizeTurn(turn) {
    const roleNode = turn.querySelector("[data-message-author-role]");
    const role = roleNode?.getAttribute("data-message-author-role") || "turn";

    let text = roleNode?.textContent || turn.textContent || "";
    text = text
      .replace(/\s+/g, " ")
      .replace(/^LagFix: hidden older .*?click to expand\.?\s*/i, "")
      .trim();

    if (text.length > 120) text = text.slice(0, 120).trim() + "…";
    if (!text) text = "content hidden";

    return { role, text };
  }

  function ensurePlaceholder(turn, index, total) {
    let placeholder = turn.querySelector(":scope > .cglf-placeholder");

    if (!placeholder) {
      placeholder = document.createElement("button");
      placeholder.type = "button";
      placeholder.className = "cglf-placeholder";
      placeholder.addEventListener("click", event => {
        event.preventDefault();
        event.stopPropagation();
        turn.dataset.cglfExpanded =
          turn.dataset.cglfExpanded === "1" ? "0" : "1";
      });

      turn.insertBefore(placeholder, turn.firstChild);
    }

    if (!placeholder.dataset.cglfReady) {
      const { role, text } = summarizeTurn(turn);
      placeholder.textContent =
        `LagFix: hidden older ${role} message ${index + 1}/${total} — click to expand. ${text}`;
      placeholder.dataset.cglfReady = "1";
    }
  }

  function clearPlaceholder(turn) {
    const placeholder = turn.querySelector(":scope > .cglf-placeholder");
    if (placeholder) placeholder.remove();
  }

  function collapseOldTurns() {
    const turns = getConversationTurns();
    const keep = Math.max(2, Number(settings.keepLatestTurns) || DEFAULTS.keepLatestTurns);
    const cutoff = Math.max(0, turns.length - keep);

    turns.forEach((turn, index) => {
      const old = settings.collapseOldTurns && index < cutoff;

      if (old) {
        turn.dataset.cglfOldTurn = "1";
        ensurePlaceholder(turn, index, turns.length);
      } else {
        delete turn.dataset.cglfOldTurn;
        delete turn.dataset.cglfExpanded;
        clearPlaceholder(turn);
      }
    });
  }

  function trimSidebar() {
    const links = [
      ...document.querySelectorAll(
        'aside a[href^="/c/"], nav a[href^="/c/"], aside a[href*="/c/"], nav a[href*="/c/"]'
      )
    ];

    const rows = uniqueElements(
      links.map(link =>
        link.closest('li, [role="listitem"], [data-testid*="conversation" i], div') || link
      )
    );

    const keep = Math.max(5, Number(settings.keepSidebarItems) || DEFAULTS.keepSidebarItems);

    rows.forEach((row, index) => {
      if (settings.trimSidebar && index >= keep) {
        row.dataset.cglfSidebarOld = "1";
      } else {
        delete row.dataset.cglfSidebarOld;
      }
    });
  }

  function lazyMedia() {
    if (!settings.lazyMedia) return;

    document.querySelectorAll("img:not([data-cglf-lazy])").forEach(img => {
      img.loading = "lazy";
      img.decoding = "async";
      img.dataset.cglfLazy = "1";
    });

    document.querySelectorAll("iframe:not([data-cglf-lazy])").forEach(frame => {
      frame.loading = "lazy";
      frame.dataset.cglfLazy = "1";
    });

    document.querySelectorAll("video:not([data-cglf-lazy])").forEach(video => {
      video.preload = "none";
      video.dataset.cglfLazy = "1";
    });

    document
      .querySelectorAll('[data-cglf-old-turn="1"]:not([data-cglf-expanded="1"]) video')
      .forEach(video => {
        try {
          video.pause();
        } catch {}
      });
  }

  function ensurePanel() {
    if (!settings.showPanel) return;
    if (!document.body || document.getElementById("cglf-panel")) return;

    const panel = document.createElement("div");
    panel.id = "cglf-panel";
    panel.innerHTML = `
      <button id="cglf-panel-toggle" type="button" title="ChatGPT LagFix Lite">⚡ LagFix</button>
      <div id="cglf-panel-body">
        <button type="button" data-cglf-action="speed"></button>
        <button type="button" data-cglf-action="collapse"></button>
        <button type="button" data-cglf-action="sidebar-trim"></button>
        <button type="button" data-cglf-action="sidebar-hide"></button>
        <button type="button" data-cglf-action="dechrome"></button>
        <button type="button" data-cglf-action="keep"></button>
        <button type="button" data-cglf-action="reset">Reset LagFix settings</button>
        <div class="cglf-note">Hotkeys: Alt+Shift+L speed, Alt+Shift+O old turns, Alt+Shift+S sidebar.</div>
      </div>
    `;

    document.body.appendChild(panel);

    panel.querySelector("#cglf-panel-toggle").addEventListener("click", () => {
      panel.classList.toggle("cglf-open");
    });

    panel.addEventListener("click", event => {
      const button = event.target.closest("[data-cglf-action]");
      if (!button) return;

      const action = button.dataset.cglfAction;

      if (action === "speed") settings.speedMode = !settings.speedMode;
      if (action === "collapse") settings.collapseOldTurns = !settings.collapseOldTurns;
      if (action === "sidebar-trim") settings.trimSidebar = !settings.trimSidebar;
      if (action === "sidebar-hide") settings.hideSidebar = !settings.hideSidebar;
      if (action === "dechrome") settings.deChrome = !settings.deChrome;

      if (action === "keep") {
        const next = Number(prompt("Keep how many latest messages fully rendered?", String(settings.keepLatestTurns)));
        if (Number.isFinite(next) && next >= 2 && next <= 100) {
          settings.keepLatestTurns = Math.floor(next);
        }
      }

      if (action === "reset") {
        settings = { ...DEFAULTS };
      }

      saveSettings();
      applyRootClasses();
      updatePanel();
      queueScan();
    });

    updatePanel();
  }

  function updatePanel() {
    const panel = document.getElementById("cglf-panel");
    if (!panel) return;

    const label = (name, on) => `${on ? "✓" : "○"} ${name}`;

    const set = (action, text) => {
      const btn = panel.querySelector(`[data-cglf-action="${action}"]`);
      if (btn) btn.textContent = text;
    };

    set("speed", label("Speed mode", settings.speedMode));
    set("collapse", label("Collapse old messages", settings.collapseOldTurns));
    set("sidebar-trim", label("Trim sidebar history", settings.trimSidebar));
    set("sidebar-hide", label("Hide sidebar", settings.hideSidebar));
    set("dechrome", label("Hide extra chrome", settings.deChrome));
    set("keep", `Keep latest messages: ${settings.keepLatestTurns}`);
  }

  function bindHotkeys() {
    if (window.__cglfHotkeysBound) return;
    window.__cglfHotkeysBound = true;

    document.addEventListener("keydown", event => {
      if (!event.altKey || !event.shiftKey) return;

      const code = event.code;

      if (code === "KeyL") {
        settings.speedMode = !settings.speedMode;
      } else if (code === "KeyO") {
        settings.collapseOldTurns = !settings.collapseOldTurns;
      } else if (code === "KeyS") {
        settings.hideSidebar = !settings.hideSidebar;
      } else {
        return;
      }

      event.preventDefault();
      saveSettings();
      applyRootClasses();
      updatePanel();
      queueScan();
    });
  }

  function scan() {
    injectStyle();
    applyRootClasses();
    ensurePanel();
    bindHotkeys();
    collapseOldTurns();
    trimSidebar();
    lazyMedia();
  }

  function startObserver() {
    if (observerStarted || !document.documentElement) return;
    observerStarted = true;

    const observer = new MutationObserver(queueScan);
    observer.observe(document.documentElement, {
      childList: true,
      subtree: true
    });

    queueScan();
  }

  injectStyle();
  applyRootClasses();
  startObserver();

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", () => {
      scan();
      startObserver();
    }, { once: true });
  } else {
    scan();
    startObserver();
  }
})();