您需要先安装一个扩展,例如 篡改猴、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.7 // @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/* // @grant none // @license MIT // ==/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 }; const MODEL_MAP = { "Grok 4": "grok-4", "Grok 3": "grok-3", "Grok 4 Heavy": "grok-4-heavy", }; const DEFAULT_MODEL = "grok-4"; const DEFAULT_KIND = "DEFAULT"; const POLL_INTERVAL_MS = 30000; const MODEL_SELECTOR = "span.inline-block.text-primary"; const QUERY_BAR_SELECTOR = ".query-bar"; const ELEMENT_WAIT_TIMEOUT_MS = 5000; const ATTACH_SVG_PATH = "M10 9V15C10 16.1046 10.8954 17 12 17V17C13.1046 17 14 16.1046 14 15V7C14 4.79086 12.2091 3 10 3V3C7.79086 3 6 4.79086 6 7V15C6 18.3137 8.68629 21 12 21V21C15.3137 21 18 18.3137 18 15V8"; const SUBMIT_SVG_PATH = "M5 11L12 4M12 4L19 11M12 4V21"; const LOADING_SVG_PATH = "M4 9.2v5.6c0 1.116 0 1.673.11 2.134a4 4 0 0 0 2.956 2.956c.46.11 1.018.11 2.134.11h5.6c1.116 0 1.673 0 2.134-.11a4 4 0 0 0 2.956-2.956c.11-.46.11-1.018.11-2.134V9.2c0-1.116 0-1.673-.11-2.134a4 4 0 0 0-2.956-2.955C16.474 4 15.916 4 14.8 4H9.2c-1.116 0-1.673 0-2.134.11a4 4 0 0 0-2.955 2.956C4 7.526 4 8.084 4 9.2Z"; const RATE_LIMIT_CONTAINER_ID = "grok-rate-limit"; const cachedRateLimits = {}; // Function to wait for element appearance function waitForElement(selector, timeout = ELEMENT_WAIT_TIMEOUT_MS, root = document) { return new Promise((resolve) => { let element = root.querySelector(selector); if (element) { resolve(element); return; } const observer = new MutationObserver(() => { element = root.querySelector(selector); if (element) { observer.disconnect(); resolve(element); } }); observer.observe(root, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); resolve(null); }, timeout); }); } // Function to find button by text or aria-label function findButtonByText(text, startsWith = false, root) { const buttons = root.querySelectorAll('button'); for (const btn of buttons) { const btnText = btn.textContent?.trim(); const ariaLabel = btn.getAttribute('aria-label')?.trim(); const matchesBtn = startsWith ? (btnText?.startsWith(text) || ariaLabel?.startsWith(text)) : (btnText === text || ariaLabel === text); if (matchesBtn) { return btn; } const span = btn.querySelector('span'); const spanText = span?.textContent?.trim(); const matchesSpan = startsWith ? spanText?.startsWith(text) : spanText === text; if (matchesSpan) { return btn; } } return null; } // Async find button async function findButtonByTextAsync(text, startsWith = false, timeout = ELEMENT_WAIT_TIMEOUT_MS, root = document) { let found = findButtonByText(text, startsWith, root); if (found) { return found; } return new Promise((resolve) => { const observer = new MutationObserver(() => { found = findButtonByText(text, startsWith, root); if (found) { observer.disconnect(); resolve(found); } }); observer.observe(root, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); resolve(null); }, timeout); }); } // Function to remove any existing rate limit display function removeExistingRateLimit() { const existing = document.getElementById(RATE_LIMIT_CONTAINER_ID); if (existing) { existing.remove(); } } // Function to normalize model names function normalizeModelName(modelName) { const trimmed = modelName.trim(); if (!trimmed) { return DEFAULT_MODEL; } return MODEL_MAP[trimmed] || trimmed.toLowerCase().replace(/\s+/g, "-"); } // Function to update or inject the rate limit display function updateRateLimitDisplay(queryBar, response) { let rateLimitContainer = document.getElementById(RATE_LIMIT_CONTAINER_ID); if (!rateLimitContainer) { const bottomBar = queryBar.querySelector('.flex.gap-1\\.5.absolute.inset-x-0.bottom-0'); if (!bottomBar) { return; } const attachButton = bottomBar.querySelector('button[aria-label="Attach"]'); if (!attachButton) { return; } rateLimitContainer = document.createElement('div'); rateLimitContainer.id = RATE_LIMIT_CONTAINER_ID; 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'; rateLimitContainer.addEventListener('click', () => { fetchAndUpdateRateLimit(queryBar, true); }); 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); const textSpan = document.createElement('span'); rateLimitContainer.appendChild(textSpan); attachButton.insertAdjacentElement('afterend', rateLimitContainer); } const textSpan = rateLimitContainer.querySelector('span'); if (response.error) { if (lastRateLimit.remainingQueries !== null && lastRateLimit.totalQueries !== null) { textSpan.textContent = `${lastRateLimit.remainingQueries}/${lastRateLimit.totalQueries}`; } else { textSpan.textContent = 'Unavailable'; } } else { const { remainingQueries, totalQueries } = response; lastRateLimit.remainingQueries = remainingQueries; lastRateLimit.totalQueries = totalQueries; textSpan.textContent = `${remainingQueries}/${totalQueries}`; } } // Function to fetch rate limit async function fetchRateLimit(modelName, requestKind, force = false) { if (!force) { const cached = cachedRateLimits[modelName]?.[requestKind]; if (cached !== undefined) { return cached; } } try { const response = await fetch(window.location.origin + '/rest/rate-limits', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ requestKind, modelName, }), credentials: 'include', }); if (!response.ok) { throw new Error(`HTTP error: Status ${response.status}`); } const data = await response.json(); if (!cachedRateLimits[modelName]) { cachedRateLimits[modelName] = {}; } cachedRateLimits[modelName][requestKind] = data; return data; } catch (error) { console.error(`Failed to fetch rate limit:`, error); if (!cachedRateLimits[modelName]) { cachedRateLimits[modelName] = {}; } cachedRateLimits[modelName][requestKind] = undefined; return { error: true }; } } // Function to fetch and update rate limit async function fetchAndUpdateRateLimit(queryBar, force = false) { if (!queryBar || !document.body.contains(queryBar)) { return; } const modelSpan = await waitForElement(MODEL_SELECTOR, ELEMENT_WAIT_TIMEOUT_MS, queryBar); let modelName = DEFAULT_MODEL; if (modelSpan) { modelName = normalizeModelName(modelSpan.textContent.trim()); } let requestKind = DEFAULT_KIND; if (modelName === 'grok-3') { const thinkButton = await findButtonByTextAsync('Think', false, ELEMENT_WAIT_TIMEOUT_MS, queryBar); const searchButton = await findButtonByTextAsync('Deep', true, ELEMENT_WAIT_TIMEOUT_MS, queryBar); if (thinkButton && thinkButton.getAttribute('aria-pressed') === 'true') { requestKind = 'REASONING'; } else if (searchButton && searchButton.getAttribute('aria-pressed') === 'true') { const modeSpan = searchButton.querySelector('span'); let modeText = modeSpan ? modeSpan.textContent.trim() : searchButton.getAttribute('aria-label'); if (modeText === 'DeepSearch') { requestKind = 'DEEPSEARCH'; } else if (modeText === 'DeeperSearch') { requestKind = 'DEEPERSEARCH'; } } } const data = await fetchRateLimit(modelName, requestKind, force); updateRateLimitDisplay(queryBar, data); } // Function to observe the DOM for the query bar function observeDOM() { let lastQueryBar = null; let lastModelObserver = null; let lastThinkObserver = null; let lastSearchObserver = null; let lastInputElement = null; let pollInterval = null; const handleVisibilityChange = () => { if (document.visibilityState === 'visible' && lastQueryBar) { fetchAndUpdateRateLimit(lastQueryBar, true); pollInterval = setInterval(() => fetchAndUpdateRateLimit(lastQueryBar, true), POLL_INTERVAL_MS); } else { if (pollInterval) { clearInterval(pollInterval); pollInterval = null; } } }; document.addEventListener('visibilitychange', handleVisibilityChange); const initialQueryBar = document.querySelector(QUERY_BAR_SELECTOR); if (initialQueryBar) { removeExistingRateLimit(); fetchAndUpdateRateLimit(initialQueryBar); lastQueryBar = initialQueryBar; setupModelObserver(initialQueryBar); setupGrok3Observers(initialQueryBar); setupSubmissionListeners(initialQueryBar); if (document.visibilityState === 'visible') { pollInterval = setInterval(() => fetchAndUpdateRateLimit(lastQueryBar, true), POLL_INTERVAL_MS); } } const observer = new MutationObserver(() => { const queryBar = document.querySelector(QUERY_BAR_SELECTOR); if (queryBar && queryBar !== lastQueryBar) { removeExistingRateLimit(); fetchAndUpdateRateLimit(queryBar); lastQueryBar = queryBar; if (lastModelObserver) { lastModelObserver.disconnect(); } if (lastThinkObserver) { lastThinkObserver.disconnect(); } if (lastSearchObserver) { lastSearchObserver.disconnect(); } setupModelObserver(queryBar); setupGrok3Observers(queryBar); setupSubmissionListeners(queryBar); if (document.visibilityState === 'visible') { if (pollInterval) clearInterval(pollInterval); pollInterval = setInterval(() => fetchAndUpdateRateLimit(lastQueryBar, true), POLL_INTERVAL_MS); } } else if (!queryBar && lastQueryBar) { removeExistingRateLimit(); if (lastModelObserver) { lastModelObserver.disconnect(); } if (lastThinkObserver) { lastThinkObserver.disconnect(); } if (lastSearchObserver) { lastSearchObserver.disconnect(); } lastQueryBar = null; lastModelObserver = null; lastThinkObserver = null; lastSearchObserver = null; lastInputElement = null; if (pollInterval) { clearInterval(pollInterval); pollInterval = null; } } }); observer.observe(document.body, { childList: true, subtree: true, }); function setupModelObserver(queryBar) { const modelSpan = queryBar.querySelector(MODEL_SELECTOR); if (modelSpan) { lastModelObserver = new MutationObserver(() => { fetchAndUpdateRateLimit(queryBar); setupGrok3Observers(queryBar); }); lastModelObserver.observe(modelSpan, { characterData: true, childList: true, subtree: true }); } } async function setupGrok3Observers(queryBar) { const modelSpan = queryBar.querySelector(MODEL_SELECTOR); const currentModel = normalizeModelName(modelSpan ? modelSpan.textContent.trim() : DEFAULT_MODEL); if (currentModel === 'grok-3') { const thinkButton = await findButtonByTextAsync('Think', false, ELEMENT_WAIT_TIMEOUT_MS, queryBar); if (thinkButton) { if (lastThinkObserver) lastThinkObserver.disconnect(); lastThinkObserver = new MutationObserver(() => { fetchAndUpdateRateLimit(queryBar); }); lastThinkObserver.observe(thinkButton, { attributes: true, attributeFilter: ['aria-pressed', 'class'] }); } const searchButton = await findButtonByTextAsync('Deep', true, ELEMENT_WAIT_TIMEOUT_MS, queryBar); if (searchButton) { if (lastSearchObserver) lastSearchObserver.disconnect(); 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; } } } function setupSubmissionListeners(queryBar) { const inputElement = queryBar.querySelector('textarea'); if (inputElement && inputElement !== lastInputElement) { lastInputElement = inputElement; inputElement.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { console.log('Enter pressed for submit'); setTimeout(() => fetchAndUpdateRateLimit(queryBar, true), 5000); } }); } } } // Start observing the DOM for changes observeDOM(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址