您需要先安装一个扩展,例如 篡改猴、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 3.2 // @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, waitTimeSeconds: null }; const MODEL_MAP = { "Grok 4": "grok-4", "Grok 3": "grok-3", "Grok 4 Heavy": "grok-4-heavy", "Grok 4 With Effort Decider": "grok-4-auto", }; 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 RATE_LIMIT_CONTAINER_ID = "grok-rate-limit"; const cachedRateLimits = {}; let countdownTimer = null; const commonFinderConfigs = { thinkButton: { selector: "button", ariaLabel: "Think", svgPartialD: "M19 9C19 12.866", }, deepSearchButton: { selector: "button", ariaLabelRegex: /Deep(er)?Search/i, }, attachButton: { selector: "button", ariaLabel: "Attach", classContains: ["group/attach-button"], svgPartialD: "M10 9V15", } }; // Function to find element based on config (OR logic for conditions) function findElement(config, root = document) { const elements = root.querySelectorAll(config.selector); for (const el of elements) { let satisfied = 0; if (config.ariaLabel) { if (el.getAttribute('aria-label') === config.ariaLabel) satisfied++; } if (config.ariaLabelRegex) { const aria = el.getAttribute('aria-label'); if (aria && config.ariaLabelRegex.test(aria)) satisfied++; } if (config.svgPartialD) { const path = el.querySelector('path'); if (path && path.getAttribute('d')?.includes(config.svgPartialD)) satisfied++; } if (config.classContains) { if (config.classContains.some(cls => el.classList.contains(cls))) satisfied++; } if (satisfied > 0) { return el; } } return null; } // Function to wait for element by config function waitForElementByConfig(config, timeout = ELEMENT_WAIT_TIMEOUT_MS, root = document) { return new Promise((resolve) => { let element = findElement(config, root); if (element) { resolve(element); return; } const observer = new MutationObserver(() => { element = findElement(config, root); if (element) { observer.disconnect(); resolve(element); } }); observer.observe(root, { childList: true, subtree: true, attributes: true }); setTimeout(() => { observer.disconnect(); resolve(null); }, timeout); }); } // Function to format timer for display (H:MM:SS or MM:SS) function formatTimer(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; if (hours > 0) { return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } else { return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } } // 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 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 determine effort level based on model function getEffortLevel(modelName) { if (modelName === 'grok-4' || modelName === 'grok-4-auto') { return 'high'; } else if (modelName === 'grok-3') { return 'low'; } else { return 'high'; // Default to high for other models } } // 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 = findElement(commonFinderConfigs.attachButton, bottomBar); 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('stroke', 'currentColor'); svg.setAttribute('stroke-width', '2'); svg.setAttribute('stroke-linecap', 'round'); svg.setAttribute('stroke-linejoin', 'round'); svg.setAttribute('class', 'lucide lucide-gauge stroke-[2] text-fg-secondary transition-colors duration-100'); svg.setAttribute('aria-hidden', 'true'); const textSpan = document.createElement('span'); rateLimitContainer.appendChild(svg); rateLimitContainer.appendChild(textSpan); attachButton.insertAdjacentElement('afterend', rateLimitContainer); } const textSpan = rateLimitContainer.querySelector('span'); const svg = rateLimitContainer.querySelector('svg'); if (response.error) { if (lastRateLimit.remainingQueries !== null) { textSpan.textContent = `${lastRateLimit.remainingQueries}`; rateLimitContainer.title = `${lastRateLimit.remainingQueries} queries remaining`; } else { textSpan.textContent = 'Unavailable'; rateLimitContainer.title = 'Unavailable'; } textSpan.style.color = ''; // Set to gauge SVG for error case if (svg) { while (svg.firstChild) { svg.removeChild(svg.firstChild); } const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path1.setAttribute('d', 'm12 14 4-4'); const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path2.setAttribute('d', 'M3.34 19a10 10 0 1 1 17.32 0'); svg.appendChild(path1); svg.appendChild(path2); svg.setAttribute('class', 'lucide lucide-gauge stroke-[2] text-fg-secondary transition-colors duration-100'); } } else { const { remainingQueries, waitTimeSeconds } = response; lastRateLimit.remainingQueries = remainingQueries; lastRateLimit.waitTimeSeconds = waitTimeSeconds; if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; } let currentCountdown = waitTimeSeconds; if (remainingQueries > 0) { textSpan.textContent = `${remainingQueries}`; textSpan.style.color = ''; rateLimitContainer.title = `${remainingQueries} queries remaining`; // Set to gauge SVG if (svg) { while (svg.firstChild) { svg.removeChild(svg.firstChild); } const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path1.setAttribute('d', 'm12 14 4-4'); const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path2.setAttribute('d', 'M3.34 19a10 10 0 1 1 17.32 0'); svg.appendChild(path1); svg.appendChild(path2); svg.setAttribute('class', 'lucide lucide-gauge stroke-[2] text-fg-secondary transition-colors duration-100'); } } else if (waitTimeSeconds > 0) { textSpan.textContent = formatTimer(currentCountdown); textSpan.style.color = '#ff6347'; // reddish tint rateLimitContainer.title = `Time until available`; // Set to clock SVG if (svg) { while (svg.firstChild) { svg.removeChild(svg.firstChild); } 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); svg.setAttribute('class', 'stroke-[2] text-fg-secondary group-hover/rate-limit:text-fg-primary transition-colors duration-100'); } countdownTimer = setInterval(() => { currentCountdown--; if (currentCountdown <= 0) { clearInterval(countdownTimer); countdownTimer = null; fetchAndUpdateRateLimit(queryBar, true); } else { textSpan.textContent = formatTimer(currentCountdown); } }, 1000); } else { textSpan.textContent = `${remainingQueries}`; textSpan.style.color = '#ff6347'; rateLimitContainer.title = 'Limit reached. Awaiting reset.'; // Set to gauge SVG for this case if (svg) { while (svg.firstChild) { svg.removeChild(svg.firstChild); } const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path1.setAttribute('d', 'm12 14 4-4'); const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path2.setAttribute('d', 'M3.34 19a10 10 0 1 1 17.32 0'); svg.appendChild(path1); svg.appendChild(path2); svg.setAttribute('class', 'lucide lucide-gauge stroke-[2] text-fg-secondary transition-colors duration-100'); } } } } // 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 process the rate limit data based on effort level function processRateLimitData(data, effortLevel) { if (data.error) { return data; } let rateLimits; if (effortLevel === 'high' && data.highEffortRateLimits) { rateLimits = data.highEffortRateLimits; } else if (effortLevel === 'low' && data.lowEffortRateLimits) { rateLimits = data.lowEffortRateLimits; } else if (data.remainingQueries !== undefined) { // Fallback to old format if present return { remainingQueries: data.remainingQueries, waitTimeSeconds: data.waitTimeSeconds || 0 }; } else { return { error: true }; } return { remainingQueries: rateLimits.remainingQueries, waitTimeSeconds: data.waitTimeSeconds || 0 // Assume waitTime is top-level or add if per-effort }; } // 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()); } const effortLevel = getEffortLevel(modelName); let requestKind = DEFAULT_KIND; if (modelName === 'grok-3') { const thinkButton = await waitForElementByConfig(commonFinderConfigs.thinkButton, ELEMENT_WAIT_TIMEOUT_MS, queryBar); const searchButton = await waitForElementByConfig(commonFinderConfigs.deepSearchButton, ELEMENT_WAIT_TIMEOUT_MS, queryBar); if (thinkButton && thinkButton.getAttribute('aria-pressed') === 'true') { requestKind = 'REASONING'; } else if (searchButton && searchButton.getAttribute('aria-pressed') === 'true') { const searchAria = searchButton.getAttribute('aria-label') || ''; if (/deeper/i.test(searchAria)) { requestKind = 'DEEPERSEARCH'; } else if (/deep/i.test(searchAria)) { requestKind = 'DEEPSEARCH'; } } } const data = await fetchRateLimit(modelName, requestKind, force); const processedData = processRateLimitData(data, effortLevel); updateRateLimitDisplay(queryBar, processedData); } // 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); 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); } lastQueryBar = queryBar; } 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 waitForElementByConfig(commonFinderConfigs.thinkButton, 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 waitForElementByConfig(commonFinderConfigs.deepSearchButton, 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或关注我们的公众号极客氢云获取最新地址