Grok Rate Limit Display

Displays Grok rate limit on screen based on selected model/mode

// ==UserScript==
// @name         Grok Rate Limit Display
// @namespace    http://tampermonkey.net/
// @version      2.6
// @description  Displays Grok rate limit on screen based on selected model/mode
// @author       Blankspeaker, Originally ported from CursedAtom's chrome extension
// @match        https://grok.com/*
// @match        https://grok.x.ai/*
// @match        https://x.com/*
// @match        https://twitter.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    console.log('Grok Rate Limit Script loaded');

    // Variable to store the last known rate limit values
    let lastRateLimit = { remainingQueries: null, totalQueries: null };

    // Function to remove any existing rate limit display
    function removeExistingRateLimit() {
      const existing = document.getElementById('grok-rate-limit');
      if (existing) {
        existing.remove();
      }
    }

    // Function to normalize model names (lowercase, hyphenated)
    function normalizeModelName(modelName) {
      return modelName.toLowerCase().replace(/\s+/g, '-');
    }

    // Function to update or inject the rate limit display
    function updateRateLimitDisplay(queryBar, response) {
      let rateLimitContainer = document.getElementById('grok-rate-limit');

      // If no container exists, create and insert it
      if (!rateLimitContainer) {
        const bottomBar = queryBar.querySelector('.flex.gap-1\\.5.absolute.inset-x-0.bottom-0');
        if (!bottomBar) {
          console.log('Bottom bar not found');
          return;
        }

        const attachButton = bottomBar.querySelector('button[aria-label="Attach"]');
        if (!attachButton) {
          console.log('Attach button not found');
          return;
        }

        rateLimitContainer = document.createElement('div');
        rateLimitContainer.id = 'grok-rate-limit';
        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 border-border-l2 text-fg-primary hover:bg-button-ghost-hover 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 cursor-pointer';
        rateLimitContainer.style.opacity = '0.8';
        rateLimitContainer.style.transition = 'opacity 0.1s ease-in-out';

        // Add click event to update on click
        rateLimitContainer.addEventListener('click', () => {
          fetchAndUpdateRateLimit(queryBar);
        });

        // Add a gauge SVG icon
        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.setAttribute('width', '18');
        svg.setAttribute('height', '18');
        svg.setAttribute('viewBox', '0 0 24 24');
        svg.setAttribute('fill', 'none');
        svg.setAttribute('class', 'stroke-[2] text-fg-secondary group-hover/rate-limit:text-fg-primary transition-colors duration-100');

        const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
        circle.setAttribute('cx', '12');
        circle.setAttribute('cy', '12');
        circle.setAttribute('r', '8');
        circle.setAttribute('stroke', 'currentColor');
        circle.setAttribute('stroke-width', '2');

        const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        path.setAttribute('d', 'M12 12L12 6');
        path.setAttribute('stroke', 'currentColor');
        path.setAttribute('stroke-width', '2');
        path.setAttribute('stroke-linecap', 'round');

        svg.appendChild(circle);
        svg.appendChild(path);
        rateLimitContainer.appendChild(svg);

        // Add a span for the text
        const textSpan = document.createElement('span');
        rateLimitContainer.appendChild(textSpan);

        attachButton.insertAdjacentElement('afterend', rateLimitContainer);
      }

      // Update text based on response
      const textSpan = rateLimitContainer.querySelector('span');
      if (response.error) {
        // If there's an error, use the last known rate limit values if available
        if (lastRateLimit.remainingQueries !== null && lastRateLimit.totalQueries !== null) {
          textSpan.textContent = `${lastRateLimit.remainingQueries}/${lastRateLimit.totalQueries}`;
        } else {
          // If no previous data exists, show a neutral message without "Error"
          textSpan.textContent = 'Unavailable';
        }
      } else {
        const { remainingQueries, totalQueries } = response;
        // Store the latest rate limit values
        lastRateLimit.remainingQueries = remainingQueries;
        lastRateLimit.totalQueries = totalQueries;
        textSpan.textContent = `${remainingQueries}/${totalQueries}`;
      }
    }

    // Function to fetch and update rate limit
    function fetchAndUpdateRateLimit(queryBar) {
      if (!queryBar || !document.body.contains(queryBar)) {
        console.log('Query bar not valid, skipping update');
        return;
      }
      // Detect current model from the UI
      let modelName = 'grok-4'; // Updated default to grok-4 as of 2025
      const modelSpan = queryBar.querySelector('span.inline-block.text-primary');
      if (modelSpan) {
        const modelText = modelSpan.textContent.trim();
        modelName = normalizeModelName(modelText);
      }

      // Determine requestKind based on model
      let requestKind = 'DEFAULT';
      if (modelName === 'grok-3') {
        const thinkButton = queryBar.querySelector('button[aria-label="Think"]');
        if (thinkButton && thinkButton.getAttribute('aria-pressed') === 'true') {
          requestKind = 'REASONING';
        } else {
          let searchButton = queryBar.querySelector('button[aria-label^="Deep"]');
          if (searchButton && searchButton.getAttribute('aria-pressed') === 'true') {
            const modeSpan = searchButton.querySelector('span');
            if (modeSpan) {
              const modeText = modeSpan.textContent.trim();
              if (modeText === 'DeepSearch') {
                requestKind = 'DEEPSEARCH';
              } else if (modeText === 'DeeperSearch') {
                requestKind = 'DEEPERSEARCH';
              }
            }
          }
        }
      }

      fetch(window.location.origin + '/rest/rate-limits', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          requestKind: requestKind,
          modelName: modelName,
        }),
        credentials: 'include',
      })
        .then((res) => {
          if (!res.ok) {
            if (res.status === 401 || res.status === 403) {
              throw new Error('Authentication required: Please sign in.');
            }
            throw new Error(`HTTP error: Status ${res.status}`);
          }
          return res.json();
        })
        .then((data) => {
          if (queryBar && document.body.contains(queryBar)) {
            updateRateLimitDisplay(queryBar, data);
          }
        })
        .catch((err) => {
          if (queryBar && document.body.contains(queryBar)) {
            updateRateLimitDisplay(queryBar, { error: err.message });
          }
        });
    }

    // Function to setup or cleanup Grok-3 specific observers
    function setupGrok3Observers(queryBar, currentModel, lastThinkObserver, lastSearchObserver) {
      if (currentModel === 'grok-3') {
        if (!lastThinkObserver) {
          const thinkButton = queryBar.querySelector('button[aria-label="Think"]');
          if (thinkButton) {
            lastThinkObserver = new MutationObserver(() => {
              fetchAndUpdateRateLimit(queryBar);
            });
            lastThinkObserver.observe(thinkButton, { attributes: true, attributeFilter: ['aria-pressed', 'class'] });
          }
        }
        if (!lastSearchObserver) {
          const searchButton = queryBar.querySelector('button[aria-label^="Deep"]');
          if (searchButton) {
            lastSearchObserver = new MutationObserver(() => {
              fetchAndUpdateRateLimit(queryBar);
            });
            lastSearchObserver.observe(searchButton, { attributes: true, attributeFilter: ['aria-pressed', 'class'], childList: true, subtree: true, characterData: true });
          }
        }
      } else {
        if (lastThinkObserver) {
          lastThinkObserver.disconnect();
          lastThinkObserver = null;
        }
        if (lastSearchObserver) {
          lastSearchObserver.disconnect();
          lastSearchObserver = null;
        }
      }
      return { lastThinkObserver, lastSearchObserver };
    }

    // Function to observe the DOM for the query bar
    function observeDOM() {
      let lastQueryBar = null;
      let lastModel = null;
      let lastModelObserver = null;
      let lastThinkObserver = null;
      let lastSearchObserver = null;
      let lastInputElement = null;
      let lastSendButton = null;

      // Initial check for query bar
      const initialQueryBar = document.querySelector('.query-bar');
      if (initialQueryBar) {
        removeExistingRateLimit();
        fetchAndUpdateRateLimit(initialQueryBar);
        lastQueryBar = initialQueryBar;

        const modelSpan = initialQueryBar.querySelector('span.inline-block.text-primary');
        if (modelSpan) {
          lastModel = normalizeModelName(modelSpan.textContent.trim());
          lastModelObserver = new MutationObserver(() => {
            const currentModel = normalizeModelName(modelSpan.textContent.trim());
            if (currentModel !== lastModel) {
              lastModel = currentModel;
              fetchAndUpdateRateLimit(lastQueryBar);
              // Setup or cleanup Grok-3 observers
              const observers = setupGrok3Observers(lastQueryBar, currentModel, lastThinkObserver, lastSearchObserver);
              lastThinkObserver = observers.lastThinkObserver;
              lastSearchObserver = observers.lastSearchObserver;
            }
          });
          lastModelObserver.observe(modelSpan, { characterData: true, childList: true, subtree: true });
        }

        // Initial setup for Grok-3 observers if applicable
        const observers = setupGrok3Observers(initialQueryBar, lastModel, lastThinkObserver, lastSearchObserver);
        lastThinkObserver = observers.lastThinkObserver;
        lastSearchObserver = observers.lastSearchObserver;

        // Set up event listeners for query submission
        const inputElement = initialQueryBar.querySelector('textarea');
        if (inputElement) {
          lastInputElement = inputElement;
          inputElement.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' && !e.shiftKey) {
              setTimeout(() => fetchAndUpdateRateLimit(lastQueryBar), 5000);
            }
          });
        }

        const sendButton = initialQueryBar.querySelector('div.h-10.relative.aspect-square');
        if (sendButton) {
          lastSendButton = sendButton;
          sendButton.addEventListener('click', () => {
            setTimeout(() => fetchAndUpdateRateLimit(lastQueryBar), 5000);
          });
        }
      }

      const observer = new MutationObserver((mutations) => {
        const queryBar = document.querySelector('.query-bar');

        // Handle query bar changes
        if (queryBar && queryBar !== lastQueryBar) {
          removeExistingRateLimit();
          fetchAndUpdateRateLimit(queryBar);
          lastQueryBar = queryBar;

          if (lastModelObserver) {
            lastModelObserver.disconnect();
            lastModelObserver = null;
          }
          if (lastThinkObserver) {
            lastThinkObserver.disconnect();
            lastThinkObserver = null;
          }
          if (lastSearchObserver) {
            lastSearchObserver.disconnect();
            lastSearchObserver = null;
          }

          const modelSpan = queryBar.querySelector('span.inline-block.text-primary');
          if (modelSpan) {
            lastModel = normalizeModelName(modelSpan.textContent.trim());
            lastModelObserver = new MutationObserver(() => {
              const currentModel = normalizeModelName(modelSpan.textContent.trim());
              if (currentModel !== lastModel) {
                lastModel = currentModel;
                fetchAndUpdateRateLimit(lastQueryBar);
                // Setup or cleanup Grok-3 observers
                const observers = setupGrok3Observers(lastQueryBar, currentModel, lastThinkObserver, lastSearchObserver);
                lastThinkObserver = observers.lastThinkObserver;
                lastSearchObserver = observers.lastSearchObserver;
              }
            });
            lastModelObserver.observe(modelSpan, { characterData: true, childList: true, subtree: true });
          }

          // Initial setup for Grok-3 observers if applicable
          const observersInit = setupGrok3Observers(queryBar, lastModel, lastThinkObserver, lastSearchObserver);
          lastThinkObserver = observersInit.lastThinkObserver;
          lastSearchObserver = observersInit.lastSearchObserver;

          // Set up event listeners for query submission
          const inputElement = queryBar.querySelector('textarea');
          if (inputElement && inputElement !== lastInputElement) {
            lastInputElement = inputElement;
            inputElement.addEventListener('keydown', (e) => {
              if (e.key === 'Enter' && !e.shiftKey) {
                setTimeout(() => fetchAndUpdateRateLimit(lastQueryBar), 5000);
              }
            });
          }

          const sendButton = queryBar.querySelector('div.h-10.relative.aspect-square');
          if (sendButton && sendButton !== lastSendButton) {
            lastSendButton = sendButton;
            sendButton.addEventListener('click', () => {
              setTimeout(() => fetchAndUpdateRateLimit(lastQueryBar), 5000);
            });
          }
        } else if (!queryBar && lastQueryBar) {
          removeExistingRateLimit();
          if (lastModelObserver) {
            lastModelObserver.disconnect();
            lastModelObserver = null;
          }
          if (lastThinkObserver) {
            lastThinkObserver.disconnect();
            lastThinkObserver = null;
          }
          if (lastSearchObserver) {
            lastSearchObserver.disconnect();
            lastSearchObserver = null;
          }
          lastQueryBar = null;
          lastModel = null;
          lastInputElement = null;
          lastSendButton = null;
        }
      });

      observer.observe(document.body, {
        childList: true,
        subtree: true,
      });

      // Polling every 5 minutes for rate limit updates when tab is focused
      setInterval(() => {
        if (lastQueryBar && document.hasFocus()) {
          fetchAndUpdateRateLimit(lastQueryBar);
        }
      }, 300 * 1000);
    }

    // Start observing the DOM for changes
    observeDOM();

})();