Grok.com Rate Limit Display

Shows you how many queries you have left/total in your rolling 2 hour window on grok.com

Versión del día 26/04/2025. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Grok.com Rate Limit Display
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Shows you how many queries you have left/total in your rolling 2 hour window on grok.com
// @author       Blankspeaker (based on the chrome extension by CursedAtom) & Grok
// @match        https://grok.com/*
// @grant        none
// @license      GNU GPLv3
// ==/UserScript==

/*
 * DEBUG_MODE: Set to true for detailed logs (e.g., polling, span creation, click events).
 * Set to false for minimal logs (only errors). Default: false.
 */
(function() {
    'use strict';

    const DEBUG_MODE = false; // Set to true for troubleshooting, false for minimal output

    // Function to log messages based on debug mode
    function log(message) {
        if (DEBUG_MODE) {
            console.log(`[Grok Rate Limit] ${message}`);
        }
    }

    // Function to poll for an element
    async function pollForElement(selector, maxAttempts = 5, interval = 1000) {
        let attempts = 0;
        while (attempts < maxAttempts) {
            const element = document.querySelector(selector);
            if (element) {
                log(`Element found: ${selector}`);
                return element;
            }
            attempts++;
            log(`Element not found: ${selector}, attempt ${attempts}/${maxAttempts}`);
            await new Promise(resolve => setTimeout(resolve, interval));
        }
        log(`Max attempts reached, could not find element: ${selector}`);
        return null;
    }

    // Function to fetch rate limit
    async function fetchRateLimit() {
        try {
            // Use a simpler selector to match the span containing "Grok 2" or "Grok 3"
            const selector = 'span.inline-block.text-primary.text-xs';
            const modelSpan = await pollForElement(selector);
            let modelName = 'grok-3'; // Default to grok-3
            if (modelSpan) {
                const modelText = modelSpan.textContent.trim();
                if (modelText === 'Grok 2') {
                    modelName = 'grok-2';
                } else if (modelText === 'Grok 3') {
                    modelName = 'grok-3';
                }
                log(`Model detected: ${modelText}, setting modelName to ${modelName}`);
            } else {
                log('Model span not found, defaulting to modelName: grok-3');
            }

            const response = await fetch('https://grok.com/rest/rate-limits', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    requestKind: 'DEFAULT',
                    modelName: modelName,
                }),
                credentials: 'include',
            });

            if (!response.ok) {
                throw new Error(`HTTP error: ${response.status}`);
            }

            const data = await response.json();
            log('Rate limit data: ' + JSON.stringify(data));
            return data;
        } catch (error) {
            console.error('[Grok Rate Limit] Failed to fetch rate limit:', error);
            return null;
        }
    }

    // Function to update or create the rate limit display
    async function displayRateLimit() {
        try {
            // Check if rate limit span already exists
            const existingSpan = document.querySelector('span.rate-limit-span');
            if (existingSpan) {
                // Update existing span
                const rateLimitData = await fetchRateLimit();
                if (rateLimitData) {
                    const remaining = rateLimitData.remainingQueries ?? 'Unknown';
                    const total = rateLimitData.totalQueries ?? 'Unknown';
                    existingSpan.textContent = `Queries left: ${remaining} of ${total} `;
                }
                log('Rate limit span updated');
                return true; // Indicate span exists
            }

            // Try primary target: <a> with href="/chat" (for private chat page)
            let targetLink = document.querySelector('a.inline-flex.items-center.justify-center.gap-2[href="/chat"]');
            if (!targetLink) {
                // Fall back to alternative target: <a> with href="/chat#private" (for main page)
                targetLink = document.querySelector('a.inline-flex.items-center.justify-center.gap-2[href="/chat#private"][aria-label="Switch to Private Chat"]');
                if (!targetLink) {
                    // Broaden the selector to any link containing "chat" in href
                    targetLink = document.querySelector('a[href*="/chat"]');
                    if (!targetLink) {
                        log('All target chat links not found');
                        return false; // Indicate failure to find target
                    }
                }
            }

            // Fetch rate limit data
            const rateLimitData = await fetchRateLimit();
            if (!rateLimitData) {
                return false;
            }

            // Extract rate limit info
            const remaining = rateLimitData.remainingQueries ?? 'Unknown';
            const total = rateLimitData.totalQueries ?? 'Unknown';

            // Create rate limit display element
            const rateLimitSpan = document.createElement('span');
            rateLimitSpan.textContent = `Queries left: ${remaining} of ${total} `;
            rateLimitSpan.classList.add('rate-limit-span');
            rateLimitSpan.style.marginRight = '8px';
            rateLimitSpan.style.color = '#666';
            rateLimitSpan.style.fontSize = '12px';
            rateLimitSpan.style.display = 'inline-flex';
            rateLimitSpan.style.alignItems = 'center';

            // Insert to the left of the target link
            targetLink.parentNode.insertBefore(rateLimitSpan, targetLink);
            log('Rate limit span created');
            return true; // Indicate success
        } catch (error) {
            console.error('[Grok Rate Limit] Error in displayRateLimit:', error);
            return false;
        }
    }

    // Function to poll for either target element with retries
    async function pollForTarget(maxAttempts = 5, interval = 3000) {
        try {
            let attempts = 0;
            while (attempts < maxAttempts) {
                // Early exit if span already exists
                if (document.querySelector('span.rate-limit-span')) {
                    log('Rate limit span already exists, stopping polling');
                    return;
                }
                const success = await displayRateLimit();
                if (success) {
                    return; // Exit once displayed
                }
                attempts++;
                log(`Polling attempt ${attempts}/${maxAttempts}`);
                await new Promise(resolve => setTimeout(resolve, interval));
            }
            log('Max attempts reached, could not find either target chat link');
        } catch (error) {
            console.error('[Grok Rate Limit] Error in pollForTarget:', error);
        }
    }

    // Run initially with a delay to allow page to load
    try {
        setTimeout(() => {
            pollForTarget();
        }, 2000); // Start after 2 seconds
    } catch (error) {
        console.error('[Grok Rate Limit] Error setting up delayed polling:', error);
    }

    // Periodically refresh rate limit (every 120 seconds)
    try {
        setInterval(async () => {
            const existingSpan = document.querySelector('span.rate-limit-span');
            if (existingSpan) {
                const rateLimitData = await fetchRateLimit();
                if (rateLimitData) {
                    const remaining = rateLimitData.remainingQueries ?? 'Unknown';
                    const total = rateLimitData.totalQueries ?? 'Unknown';
                    existingSpan.textContent = `Queries left: ${remaining} of ${total} `;
                    log('Rate limit span refreshed');
                }
            }
        }, 30000); // Refresh every 30 seconds
    } catch (error) {
        console.error('[Grok Rate Limit] Error setting up periodic refresh:', error);
    }

    // Add click listener for the send button SVG to refresh rate limit after 1 second
    try {
        document.addEventListener('click', async (event) => {
            const svg = event.target.closest('svg[width="20"][height="20"][viewBox="0 0 24 24"][fill="none"][class="stroke-[2] relative"]');
            if (svg && svg.querySelector('path[d="M5 11L12 4M12 4L19 11M12 4V21"]')) {
                log('Send button SVG clicked, scheduling refresh');
                setTimeout(async () => {
                    const existingSpan = document.querySelector('span.rate-limit-span');
                    if (existingSpan) {
                        const rateLimitData = await fetchRateLimit();
                        if (rateLimitData) {
                            const remaining = rateLimitData.remainingQueries ?? 'Unknown';
                            const total = rateLimitData.totalQueries ?? 'Unknown';
                            existingSpan.textContent = `Queries left: ${remaining} of ${total} `;
                            log('Rate limit span refreshed after click');
                        }
                    } else {
                        // If no span exists, trigger full display logic
                        await displayRateLimit();
                    }
                }, 1000); // Refresh after 1 second
            }
        });
    } catch (error) {
        console.error('[Grok Rate Limit] Error setting up click listener:', error);
    }
})();