Grok Rate Limit Display

Displays remaining queries and cooldowns on grok.com.

2025/12/05のページです。最新版はこちら

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Grok Rate Limit Display
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @description  Displays remaining queries and cooldowns on grok.com.
// @author       KHROTU, ported from Blankspeaker & CursedAtom
// @match        https://grok.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=grok.com
// @grant        none
// @license      MIT
// @homepageURL  https://greasyfork.org/en/scripts/558017-grok-rate-limit-display
// ==/UserScript==

(function() {
    'use strict';

    let lastHigh = { remaining: null, wait: null };
    let lastLow = { remaining: null, wait: null };
    let lastBoth = { high: null, low: null, wait: null };

    const MODEL_MAP = {
        "Grok 4": "grok-4",
        "Grok 3": "grok-3",
        "Grok 4 Heavy": "grok-4-heavy",
        "Grok 4 With Effort Decider": "grok-4-auto",
        "Auto": "grok-4-auto",
        "Fast": "grok-3",
        "Expert": "grok-4",
        "Heavy": "grok-4-heavy",
        "Grok 4 Fast": "grok-4-mini-thinking-tahoe",
        "Grok 4.1": "grok-4-1-non-thinking-w-tool",
        "Grok 4.1 Thinking": "grok-4-1-thinking-1108b",
    };

    const DEFAULT_MODEL = "grok-4-auto";
    const DEFAULT_KIND = "DEFAULT";
    const POLL_INTERVAL_MS = 30000;
    const MODEL_SELECTOR = "button[aria-label='Model select']";
    const QUERY_BAR_SELECTOR = ".query-bar";
    const ELEMENT_WAIT_TIMEOUT_MS = 5000;
    const RATE_LIMIT_CONTAINER_ID = "grok-rate-limit";

    const cachedRateLimits = {};

    let countdownTimer = null;
    let isCountingDown = false;
    let lastQueryBar = null;
    let lastModelObserver = null;
    let lastThinkObserver = null;
    let lastSearchObserver = null;
    let lastInputElement = null;
    let lastSubmitButton = null;
    let pollInterval = null;
    let lastModelName = null;
    let overlapCheckInterval = null;
    let isHiddenDueToOverlap = false;

    const commonFinderConfigs = {
        thinkButton: {
            selector: "button",
            ariaLabel: "Think",
            svgPartialD: "M19 9C19 12.866",
        },
        deepSearchButton: {
            selector: "button",
            ariaLabelRegex: /Deep(er)?Search/i,
        },
        attachButton: {
            selector: "button",
            ariaLabel: "Attach",
            classContains: ["group/attach-button"],
            svgPartialD: "M10 9V15",
        },
        submitButton: {
            selector: "button",
            ariaLabel: "Submit",
            svgPartialD: "M5 11L12 4M12 4L19 11M12 4V21",
        }
    };

    function isImaginePage() {
        return window.location.pathname.startsWith('/imagine');
    }

    function debounce(func, delay) {
        let timeout;
        return (...args) => {
            clearTimeout(timeout);
            timeout = setTimeout(() => func(...args), delay);
        };
    }

    function findElement(config, root = document) {
        const elements = root.querySelectorAll(config.selector);
        for (const el of elements) {
            let satisfied = 0;

            if (config.ariaLabel) {
                if (el.getAttribute('aria-label') === config.ariaLabel) satisfied++;
            }

            if (config.ariaLabelRegex) {
                const aria = el.getAttribute('aria-label');
                if (aria && config.ariaLabelRegex.test(aria)) satisfied++;
            }

            if (config.svgPartialD) {
                const path = el.querySelector('path');
                if (path && path.getAttribute('d')?.includes(config.svgPartialD)) satisfied++;
            }

            if (config.classContains) {
                if (config.classContains.some(cls => el.classList.contains(cls))) satisfied++;
            }

            if (satisfied > 0) {
                return el;
            }
        }
        return null;
    }

    function formatTimer(seconds) {
        const hours = Math.floor(seconds / 3600);
        const minutes = Math.floor((seconds % 3600) / 60);
        const secs = seconds % 60;
        if (hours > 0) {
            return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
        } else {
            return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
        }
    }

    function checkTextOverlap(queryBar) {
      const rateLimitContainer = document.getElementById(RATE_LIMIT_CONTAINER_ID);
      if (!rateLimitContainer) return;

      const contentEditable = queryBar.querySelector('div[contenteditable="true"]');
      const textArea = queryBar.querySelector('textarea[aria-label*="Ask Grok"]');
      const inputElement = contentEditable || textArea;

      if (!inputElement) return;

      const textContent = inputElement.value || inputElement.textContent || '';
      const textLength = textContent.trim().length;
      const queryBarWidth = queryBar.offsetWidth;
      const rateLimitWidth = rateLimitContainer.offsetWidth;
      const availableSpace = queryBarWidth - rateLimitWidth - 100;
      const isSmallScreen = window.innerWidth < 900 ||
                           availableSpace < 200 ||
                           window.screen?.width < 500 ||
                           document.documentElement.clientWidth < 500;
      const characterLimit = isSmallScreen ? 0 : 28;
      const shouldHide = textLength > characterLimit;

      if (shouldHide && !isHiddenDueToOverlap) {
        rateLimitContainer.style.transition = 'transform 0.2s ease-out, opacity 0.2s ease-out';
        rateLimitContainer.style.transform = 'translateX(100%)';
        rateLimitContainer.style.opacity = '0';

        setTimeout(() => {
          if (isHiddenDueToOverlap) {
            rateLimitContainer.style.display = 'none';
          }
        }, 200);

        isHiddenDueToOverlap = true;
      } else if (!shouldHide && isHiddenDueToOverlap) {
        rateLimitContainer.style.display = '';
        rateLimitContainer.style.transition = 'transform 0.2s ease-out, opacity 0.2s ease-out';
        rateLimitContainer.offsetHeight;
        rateLimitContainer.style.transform = 'translateX(0)';
        rateLimitContainer.style.opacity = '0.8';
        isHiddenDueToOverlap = false;
      }
    }

    function startOverlapChecking(queryBar) {
      if (overlapCheckInterval) {
        clearInterval(overlapCheckInterval);
      }
      overlapCheckInterval = setInterval(() => {
        if (document.body.contains(queryBar)) {
          checkTextOverlap(queryBar);
        } else {
          clearInterval(overlapCheckInterval);
          overlapCheckInterval = null;
        }
      }, 500);
    }

    function stopOverlapChecking() {
      if (overlapCheckInterval) {
        clearInterval(overlapCheckInterval);
        overlapCheckInterval = null;
      }
      isHiddenDueToOverlap = false;
    }

    function removeExistingRateLimit() {
      const existing = document.getElementById(RATE_LIMIT_CONTAINER_ID);
      if (existing) {
        existing.remove();
      }
    }

    function getCurrentModelKey(queryBar) {
        const modelButton = queryBar.querySelector(MODEL_SELECTOR);
        if (!modelButton) return DEFAULT_MODEL;

        const textElement = modelButton.querySelector('span.font-semibold');
        if (textElement) {
            const modelText = textElement.textContent.trim();
            return MODEL_MAP[modelText] || DEFAULT_MODEL;
        }

        const oldTextElement = modelButton.querySelector('span.inline-block');
        if (oldTextElement) {
            const modelText = oldTextElement.textContent.trim();
            return MODEL_MAP[modelText] || DEFAULT_MODEL;
        }

        const svg = modelButton.querySelector('svg');
        if (svg) {
            const pathsD = Array.from(svg.querySelectorAll('path'))
                .map(p => p.getAttribute('d') || '')
                .filter(d => d.length > 0)
                .join(' ');
            const hasBrainFill = svg.querySelector('path[class*="fill-yellow-100"]') !== null;
            if (pathsD.includes('M6.5 12.5L11.5 17.5')) return 'grok-4-auto';
            if (pathsD.includes('M5 14.25L14 4')) return 'grok-3';
            if (hasBrainFill || pathsD.includes('M19 9C19 12.866')) return 'grok-4';
            if (pathsD.includes('M12 3a6 6 0 0 0 9 9')) return 'grok-4-mini-thinking-tahoe';
            if (pathsD.includes('M11 18H10C7.79086 18 6 16.2091 6 14V13')) return 'grok-4-heavy';
        }
        return DEFAULT_MODEL;
    }

    function getEffortLevel(modelName) {
      if (modelName === 'grok-4-auto') return 'both';
      if (modelName === 'grok-3') return 'low';
      if (modelName === 'grok-4-1-non-thinking-w-tool') return 'low';
      if (modelName === 'grok-4-1-thinking-1108b') return 'high';
      return 'high';
    }

    function updateRateLimitDisplay(queryBar, response, effort) {
      if (isImaginePage()) {
        removeExistingRateLimit();
        return;
      }

      let rateLimitContainer = document.getElementById(RATE_LIMIT_CONTAINER_ID);
      if (!rateLimitContainer) {
        const bottomBar = queryBar.querySelector('.flex.gap-1\\.5.absolute.inset-x-0.bottom-0');
        if (!bottomBar) return;

        const modelSelectDiv = bottomBar.querySelector('div.z-20');
        const submitButton = findElement(commonFinderConfigs.submitButton, bottomBar);
        const attachButton = findElement(commonFinderConfigs.attachButton, bottomBar);

        let insertPoint = modelSelectDiv || submitButton || attachButton || bottomBar;

        rateLimitContainer = document.createElement('div');
        rateLimitContainer.id = RATE_LIMIT_CONTAINER_ID;
        rateLimitContainer.className = 'inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-60 disabled:cursor-not-allowed [&_svg]:duration-100 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:-mx-0.5 select-none text-fg-primary hover:bg-button-ghost-hover hover:border-border-l2 disabled:hover:bg-transparent h-10 px-3.5 py-2 text-sm rounded-full group/rate-limit transition-colors duration-100 relative overflow-hidden border border-transparent cursor-pointer';
        rateLimitContainer.style.opacity = '0.8';
        rateLimitContainer.style.transition = 'opacity 0.1s ease-in-out';
        rateLimitContainer.style.zIndex = '20';
        rateLimitContainer.addEventListener('click', () => fetchAndUpdateRateLimit(queryBar, true));

        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        Object.entries({
            width: '18', height: '18', viewBox: '0 0 24 24', fill: 'none',
            stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round',
            'stroke-linejoin': 'round', class: 'lucide lucide-gauge stroke-[2] text-fg-secondary transition-colors duration-100',
            'aria-hidden': 'true'
        }).forEach(([key, value]) => svg.setAttribute(key, value));

        const contentDiv = document.createElement('div');
        contentDiv.className = 'flex items-center';
        rateLimitContainer.append(svg, contentDiv);
        insertPoint === bottomBar ? bottomBar.appendChild(rateLimitContainer) : insertPoint.insertAdjacentElement('beforebegin', rateLimitContainer);
      }

      const contentDiv = rateLimitContainer.lastChild;
      const svg = rateLimitContainer.querySelector('svg');
      contentDiv.innerHTML = '';
      const isBoth = effort === 'both';

      if (response.error) {
        if (isBoth) {
          if (lastBoth.high !== null && lastBoth.low !== null) {
            appendNumberSpan(contentDiv, lastBoth.high, '');
            appendDivider(contentDiv);
            appendNumberSpan(contentDiv, lastBoth.low, '');
            rateLimitContainer.title = `High: ${lastBoth.high} | Low: ${lastBoth.low} queries remaining`;
            setGaugeSVG(svg);
          } else {
            appendNumberSpan(contentDiv, 'Unavailable', '');
            rateLimitContainer.title = 'Unavailable';
            setGaugeSVG(svg);
          }
        } else {
          const lastForEffort = (effort === 'high') ? lastHigh : lastLow;
          if (lastForEffort.remaining !== null) {
            let displayRemaining = lastForEffort.remaining;
            let tooltipText = `${lastForEffort.remaining} queries remaining`;
            if (lastForEffort.isFreeAccount && lastForEffort.cost && lastForEffort.cost > 1) {
              displayRemaining = Math.floor(lastForEffort.originalRemaining / lastForEffort.cost);
              tooltipText = `${displayRemaining} queries (${lastForEffort.originalRemaining} tokens ÷ ${lastForEffort.cost} cost)`;
            }
            appendNumberSpan(contentDiv, displayRemaining, '');
            rateLimitContainer.title = tooltipText;
            setGaugeSVG(svg);
          } else {
            appendNumberSpan(contentDiv, 'Unavailable', '');
            rateLimitContainer.title = 'Unavailable';
            setGaugeSVG(svg);
          }
        }
      } else {
        if (countdownTimer) clearInterval(countdownTimer);
        countdownTimer = null;

        const startCountdown = (duration, timerSpan) => {
          let currentCountdown = duration;
          isCountingDown = true;
          if (pollInterval) {
            clearInterval(pollInterval);
            pollInterval = null;
          }
          countdownTimer = setInterval(() => {
            currentCountdown--;
            if (currentCountdown <= 0) {
              clearInterval(countdownTimer);
              countdownTimer = null;
              isCountingDown = false;
              fetchAndUpdateRateLimit(queryBar, true);
              if (document.visibilityState === 'visible' && lastQueryBar) {
                pollInterval = setInterval(() => fetchAndUpdateRateLimit(lastQueryBar, true), POLL_INTERVAL_MS);
              }
            } else {
              timerSpan.textContent = formatTimer(currentCountdown);
            }
          }, 1000);
        };

        if (isBoth) {
          Object.assign(lastBoth, {
              high: response.highRemaining, low: response.lowRemaining, wait: response.waitTimeSeconds,
              originalHigh: response.originalHighRemaining, highCost: response.highCost, isFreeAccount: response.isFreeAccount
          });
          const { high, low, wait } = lastBoth;
          if (high > 0) {
            appendNumberSpan(contentDiv, high, '');
            appendDivider(contentDiv);
            appendNumberSpan(contentDiv, low, '');
            rateLimitContainer.title = (response.isFreeAccount && response.highCost > 1)
                ? `High: ${high} queries (${response.originalHighRemaining} tokens ÷ ${response.highCost} cost) | Low: ${low} queries remaining`
                : `High: ${high} | Low: ${low} queries remaining`;
            setGaugeSVG(svg);
          } else if (wait > 0) {
            const timerSpan = appendNumberSpan(contentDiv, formatTimer(wait), '#ff6347');
            if (low > 0) {
              appendDivider(contentDiv);
              appendNumberSpan(contentDiv, low, '');
              rateLimitContainer.title = `High: Time until reset | Low: ${low} queries remaining`;
            } else {
              rateLimitContainer.title = `Time until reset`;
            }
            setClockSVG(svg);
            startCountdown(wait, timerSpan);
          } else {
            appendNumberSpan(contentDiv, '0', '#ff6347');
            if (low > 0) {
              appendDivider(contentDiv);
              appendNumberSpan(contentDiv, low, '');
              rateLimitContainer.title = `High: Limit reached | Low: ${low} queries remaining`;
            } else {
              rateLimitContainer.title = `Limit reached. Awaiting reset.`;
            }
            setGaugeSVG(svg);
          }
        } else {
          const lastForEffort = (effort === 'high') ? lastHigh : lastLow;
          Object.assign(lastForEffort, {
              remaining: response.remainingQueries, wait: response.waitTimeSeconds, originalRemaining: response.originalRemaining,
              cost: response.cost, isFreeAccount: response.isFreeAccount
          });
          const { remaining, wait } = lastForEffort;
          if (remaining > 0) {
            appendNumberSpan(contentDiv, remaining, '');
            rateLimitContainer.title = (response.isFreeAccount && response.cost > 1)
                ? `${remaining} queries (${response.originalRemaining} tokens ÷ ${response.cost} cost)`
                : `${remaining} queries remaining`;
            setGaugeSVG(svg);
          } else if (wait > 0) {
            const timerSpan = appendNumberSpan(contentDiv, formatTimer(wait), '#ff6347');
            rateLimitContainer.title = `Time until reset`;
            setClockSVG(svg);
            startCountdown(wait, timerSpan);
          } else {
            appendNumberSpan(contentDiv, '0', '#ff6347');
            rateLimitContainer.title = 'Limit reached. Awaiting reset.';
            setGaugeSVG(svg);
          }
        }
      }
    }

    function appendNumberSpan(parent, text, color) {
      const span = document.createElement('span');
      span.textContent = text;
      if (color) span.style.color = color;
      parent.appendChild(span);
      return span;
    }

    function appendDivider(parent) {
      const divider = document.createElement('div');
      divider.className = 'h-6 w-[2px] bg-border-l2 mx-1';
      parent.appendChild(divider);
    }

    function setGaugeSVG(svg) {
      if (svg) {
        svg.innerHTML = `<path d="m12 14 4-4"></path><path d="M3.34 19a10 10 0 1 1 17.32 0"></path>`;
        svg.setAttribute('class', 'lucide lucide-gauge stroke-[2] text-fg-secondary transition-colors duration-100');
      }
    }

    function setClockSVG(svg) {
      if (svg) {
        svg.innerHTML = `<circle cx="12" cy="12" r="8" stroke="currentColor" stroke-width="2"></circle><path d="M12 12L12 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>`;
        svg.setAttribute('class', 'stroke-[2] text-fg-secondary group-hover/rate-limit:text-fg-primary transition-colors duration-100');
      }
    }

    async function fetchRateLimit(modelName, requestKind, force = false) {
      const now = Date.now();
      if (!force) {
        const cached = cachedRateLimits[modelName]?.[requestKind];
        if (cached?.timestamp && (now - cached.timestamp) < 10000) {
          return cached.data;
        }
      }
      if (cachedRateLimits._lastRequest && (now - cachedRateLimits._lastRequest) < 2000) {
        const cached = cachedRateLimits[modelName]?.[requestKind];
        return cached?.data ?? { error: true, reason: 'Rate limited' };
      }
      cachedRateLimits._lastRequest = now;

      try {
        const response = await fetch(`${window.location.origin}/rest/rate-limits`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ requestKind, modelName }),
          credentials: 'include',
        });
        if (!response.ok) throw new Error(`HTTP error: Status ${response.status}`);
        const data = await response.json();
        if (!cachedRateLimits[modelName]) cachedRateLimits[modelName] = {};
        cachedRateLimits[modelName][requestKind] = { data, timestamp: now };
        return data;
      } catch (error) {
        if (!cachedRateLimits[modelName]) cachedRateLimits[modelName] = {};
        const errorData = { error: true, reason: error.message };
        cachedRateLimits[modelName][requestKind] = { data: errorData, timestamp: now };
        return errorData;
      }
    }

    function processRateLimitData(data, effortLevel) {
      if (data.error) return data;
      const isFreeAccount = data.totalTokens <= 80;

      if (effortLevel === 'both') {
        const high = data.highEffortRateLimits?.remainingQueries;
        const low = data.lowEffortRateLimits?.remainingQueries;
        const highCost = data.highEffortRateLimits?.cost || 1;
        const waitTimeSeconds = Math.max(data.highEffortRateLimits?.waitTimeSeconds || 0, data.lowEffortRateLimits?.waitTimeSeconds || 0, data.waitTimeSeconds || 0);
        if (high === undefined || low === undefined) return { error: true, reason: 'Missing effort data' };
        return {
          highRemaining: isFreeAccount ? Math.floor(high / highCost) : high,
          lowRemaining: low,
          waitTimeSeconds: waitTimeSeconds,
          originalHighRemaining: high,
          highCost: highCost,
          isFreeAccount: isFreeAccount
        };
      } else {
        const key = effortLevel === 'high' ? 'highEffortRateLimits' : 'lowEffortRateLimits';
        let remaining = data[key]?.remainingQueries ?? data.remainingQueries;
        let cost = data[key]?.cost || 1;
        if (remaining === undefined) return { error: true, reason: `Missing ${effortLevel} effort data` };
        return {
          remainingQueries: isFreeAccount ? Math.floor(remaining / cost) : remaining,
          waitTimeSeconds: data[key]?.waitTimeSeconds || data.waitTimeSeconds || 0,
          originalRemaining: remaining,
          cost: cost,
          isFreeAccount: isFreeAccount
        };
      }
    }

    async function fetchAndUpdateRateLimit(queryBar, force = false) {
      if (isImaginePage() || !queryBar || !document.body.contains(queryBar)) return;
      const modelName = getCurrentModelKey(queryBar);
      if (modelName !== lastModelName) force = true;
      if (isCountingDown && !force) return;

      const effortLevel = getEffortLevel(modelName);
      let requestKind = DEFAULT_KIND;
      if (modelName === 'grok-3') {
        const thinkButton = findElement(commonFinderConfigs.thinkButton, queryBar);
        const searchButton = findElement(commonFinderConfigs.deepSearchButton, queryBar);
        if (thinkButton?.getAttribute('aria-pressed') === 'true') {
          requestKind = 'REASONING';
        } else if (searchButton?.getAttribute('aria-pressed') === 'true') {
          const searchAria = searchButton.getAttribute('aria-label') || '';
          if (/deeper/i.test(searchAria)) requestKind = 'DEEPERSEARCH';
          else if (/deep/i.test(searchAria)) requestKind = 'DEEPSEARCH';
        }
      }
      const data = await fetchRateLimit(modelName, requestKind, force);
      const processedData = processRateLimitData(data, effortLevel);
      updateRateLimitDisplay(queryBar, processedData, effortLevel);
      lastModelName = modelName;
    }

    function observeDOM() {
      const handleVisibilityChange = () => {
        if (document.visibilityState === 'visible' && lastQueryBar && !isImaginePage()) {
          fetchAndUpdateRateLimit(lastQueryBar, true);
          if (!isCountingDown) {
            if (pollInterval) clearInterval(pollInterval);
            pollInterval = setInterval(() => fetchAndUpdateRateLimit(lastQueryBar, true), POLL_INTERVAL_MS);
          }
        } else {
          if (pollInterval) {
            clearInterval(pollInterval);
            pollInterval = null;
          }
        }
      };
      const handleResize = debounce(() => {
        if (lastQueryBar) checkTextOverlap(lastQueryBar);
      }, 300);
      document.addEventListener('visibilitychange', handleVisibilityChange);
      window.addEventListener('resize', handleResize);

      const setupForQueryBar = (queryBar) => {
          removeExistingRateLimit();
          fetchAndUpdateRateLimit(queryBar);
          lastQueryBar = queryBar;
          setupQueryBarObserver(queryBar);
          setupGrok3Observers(queryBar);
          setupSubmissionListeners(queryBar);
          startOverlapChecking(queryBar);
          setTimeout(() => checkTextOverlap(queryBar), 100);
          if (document.visibilityState === 'visible' && !isCountingDown) {
              if (pollInterval) clearInterval(pollInterval);
              pollInterval = setInterval(() => fetchAndUpdateRateLimit(lastQueryBar, true), POLL_INTERVAL_MS);
          }
      };

      const cleanup = () => {
          removeExistingRateLimit();
          stopOverlapChecking();
          if (lastModelObserver) lastModelObserver.disconnect();
          if (lastThinkObserver) lastThinkObserver.disconnect();
          if (lastSearchObserver) lastSearchObserver.disconnect();
          if (pollInterval) clearInterval(pollInterval);
          lastQueryBar = lastModelObserver = lastThinkObserver = lastSearchObserver = lastInputElement = lastSubmitButton = pollInterval = null;
      };

      if (!isImaginePage()) {
        const initialQueryBar = document.querySelector(QUERY_BAR_SELECTOR);
        if (initialQueryBar) setupForQueryBar(initialQueryBar);
      }

      const observer = new MutationObserver(() => {
        if (isImaginePage()) {
          if (lastQueryBar) cleanup();
          return;
        }
        const queryBar = document.querySelector(QUERY_BAR_SELECTOR);
        if (queryBar && queryBar !== lastQueryBar) {
            if (lastQueryBar) cleanup();
            setupForQueryBar(queryBar);
        } else if (!queryBar && lastQueryBar) {
            cleanup();
        }
      });
      observer.observe(document.body, { childList: true, subtree: true });

      function setupQueryBarObserver(queryBar) {
        const debouncedUpdate = debounce(() => {
          fetchAndUpdateRateLimit(queryBar);
          setupGrok3Observers(queryBar);
        }, 300);
        lastModelObserver = new MutationObserver(debouncedUpdate);
        lastModelObserver.observe(queryBar, { childList: true, subtree: true, attributes: true, characterData: true });
      }

      function setupGrok3Observers(queryBar) {
        if (lastThinkObserver) lastThinkObserver.disconnect();
        if (lastSearchObserver) lastSearchObserver.disconnect();
        lastThinkObserver = lastSearchObserver = null;
        if (getCurrentModelKey(queryBar) === 'grok-3') {
          const thinkButton = findElement(commonFinderConfigs.thinkButton, queryBar);
          if (thinkButton) {
            lastThinkObserver = new MutationObserver(() => fetchAndUpdateRateLimit(queryBar));
            lastThinkObserver.observe(thinkButton, { attributes: true, attributeFilter: ['aria-pressed', 'class'] });
          }
          const searchButton = findElement(commonFinderConfigs.deepSearchButton, queryBar);
          if (searchButton) {
            lastSearchObserver = new MutationObserver(() => fetchAndUpdateRateLimit(queryBar));
            lastSearchObserver.observe(searchButton, { attributes: true, attributeFilter: ['aria-pressed', 'class'], childList: true, subtree: true, characterData: true });
          }
        }
      }

      function setupSubmissionListeners(queryBar) {
        const contentEditable = queryBar.querySelector('div[contenteditable="true"]');
        const textArea = queryBar.querySelector('textarea[aria-label*="Ask Grok"]');
        const inputElement = contentEditable || textArea;
        if (inputElement && inputElement !== lastInputElement) {
          lastInputElement = inputElement;
          const debouncedOverlapCheck = debounce(() => checkTextOverlap(queryBar), 300);
          inputElement.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' && !e.shiftKey) {
              setTimeout(() => fetchAndUpdateRateLimit(queryBar, true), 3000);
            }
          });
          inputElement.addEventListener('input', debouncedOverlapCheck);
          inputElement.addEventListener('focus', debouncedOverlapCheck);
          inputElement.addEventListener('blur', () => setTimeout(() => checkTextOverlap(queryBar), 200));
        }

        const submitButton = findElement(commonFinderConfigs.submitButton, queryBar);
        if (submitButton && submitButton !== lastSubmitButton) {
          lastSubmitButton = submitButton;
          submitButton.addEventListener('click', () => setTimeout(() => fetchAndUpdateRateLimit(queryBar, true), 3000));
        }
      }
    }
    observeDOM();
})();