您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays Grok rate limit on screen
// ==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(); })();