您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址