// ==UserScript==
// @name Grok Rate Limit Display
// @namespace http://tampermonkey.net/
// @version 2.5
// @description Displays Grok rate limit on screen
// @author CursedAtom , ported by Blankspeaker & Grok
// @match https://grok.com/*
// @match https://grok.x.ai/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 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';
rateLimitContainer.style.opacity = '0.8';
rateLimitContainer.style.transition = 'opacity 0.1s ease-in-out';
// 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) {
// 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);
}
fetch(window.location.origin + '/rest/rate-limits', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
requestKind: 'DEFAULT',
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) {
updateRateLimitDisplay(queryBar, data);
}
})
.catch((err) => {
if (queryBar) {
updateRateLimitDisplay(queryBar, { error: err.message });
}
});
}
// Function to observe the DOM for the query bar
function observeDOM() {
let lastQueryBar = null;
let lastModel = null;
let lastModelObserver = 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);
}
});
lastModelObserver.observe(modelSpan, { characterData: true, childList: true, subtree: true });
}
}
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;
}
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);
}
});
lastModelObserver.observe(modelSpan, { characterData: true, childList: true, subtree: true });
}
} else if (!queryBar && lastQueryBar) {
removeExistingRateLimit();
if (lastModelObserver) {
lastModelObserver.disconnect();
lastModelObserver = null;
}
lastQueryBar = null;
lastModel = 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();
})();