您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
监控 Grok API 配额(标准、思考、深度、更深),默认显示总数,悬浮查看详情
// ==UserScript== // @name Grok Monitor // @namespace https://github.com/Loongphy/Grok-Monitor // @version 1.0.0 // @author Loongphy // @description 监控 Grok API 配额(标准、思考、深度、更深),默认显示总数,悬浮查看详情 // @match https://grok.com/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @license GPL-3.0 // ==/UserScript== /* * Grok Monitor - 监控 Grok API 配额使用情况的油猴脚本 * * 本脚本基于 GPL-3.0 许可证 * * 这是一个衍生作品,基于 BlueSkyXN 的 Grok Helper * 原始代码: https://github.com/BlueSkyXN/GPT-Models-Plus/blob/main/GrokHelper.js * 原作者: BlueSkyXN */ (function() { 'use strict'; // 缓存查询结果 let cachedResults = null; // 获取用户设置或设置默认值 let isCompactMode = GM_getValue('compactMode', true); // 默认使用精简模式 // 四种模式 -> 中文名称对应表 const MODE_LABELS = { DEFAULT: '标准', REASONING: '思考', DEEPSEARCH: '深度', DEEPERSEARCH: '更深' }; // 我们需要查询的四种模式 const REQUEST_KINDS = Object.keys(MODE_LABELS); // 图标 SVG (来自 Font Awesome 免费图标) const ICONS = { BOLT: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16"><path fill="currentColor" d="M349.4 44.6c5.9-13.7 1.5-29.7-10.6-38.5s-28.6-8-39.9 1.8l-256 224c-10 8.8-13.6 22.9-8.9 35.3S50.7 288 64 288H175.5L98.6 467.4c-5.9 13.7-1.5 29.7 10.6 38.5s28.6 8 39.9-1.8l256-224c10-8.8 13.6-22.9 8.9-35.3S397.3 224 384 224H272.5L349.4 44.6z"/></svg>', TIMER: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16"><path fill="currentColor" d="M464 256A208 208 0 1 1 48 256a208 208 0 1 1 416 0zM0 256a256 256 0 1 0 512 0A256 256 0 1 0 0 256zM232 120V256c0 8 4 15.5 10.7 20l96 64c11 7.4 25.9 4.4 33.3-6.7s4.4-25.9-6.7-33.3L280 243.2V120c0-13.3-10.7-24-24-24s-24 10.7-24 24z"/></svg>', INFO: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="14" height="14"><path fill="currentColor" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>', REFRESH: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="12" height="12"><path fill="currentColor" d="M463.5 224H472c13.3 0 24-10.7 24-24V72c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8H463.5z"/></svg>' }; // 添加自定义样式 GM_addStyle(` /* 通用样式 */ .grok-monitor { position: fixed; left: 16px; top: 72px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; z-index: 100; transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); width: fit-content; } /* 完整模式样式 */ .grok-monitor.full-mode { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; border-radius: 12px; background-color: rgba(255, 255, 255, 0.95); color: #333; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.1); backdrop-filter: blur(8px); max-width: 280px; border: 1px solid rgba(0, 0, 0, 0.06); transform-origin: top left; padding: 12px 16px; font-size: 14px; } .grok-monitor.full-mode:hover { box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12), 0 2px 5px rgba(0, 0, 0, 0.1); } /* 精简模式样式 */ .grok-monitor.compact-mode { display: flex; flex-direction: column; border-radius: 12px; background-color: rgba(255, 255, 255, 0.95); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.1); border: 1px solid rgba(0, 0, 0, 0.06); overflow: hidden; max-width: 280px; } .grok-monitor.compact-mode .compact-header { display: flex; align-items: center; padding: 8px 16px; gap: 10px; font-size: 15px; color: #333; width: 100%; } .grok-monitor-header { display: flex; align-items: center; justify-content: space-between; width: 100%; gap: 10px; } .grok-monitor-title { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 14px; color: #444; } .grok-monitor-title .icon { display: flex; align-items: center; color: #2563EB; } .grok-monitor-summary { display: flex; align-items: center; gap: 8px; white-space: nowrap; font-weight: 500; font-size: 15px; color: #444; width: 100%; } .full-mode .grok-monitor-summary { background: rgba(0, 0, 0, 0.03); padding: 6px 10px; border-radius: 8px; justify-content: space-between; } .compact-mode .grok-monitor-summary { padding: 0; margin: 0; justify-content: space-between; } .grok-monitor-summary-text { display: flex; align-items: center; gap: 6px; } .grok-monitor-indicator { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; box-shadow: 0 0 0 rgba(0, 0, 0, 0); transition: all 0.3s ease; margin-left: 8px; } .grok-monitor-indicator.green { background-color: #10B981; box-shadow: 0 0 8px rgba(16, 185, 129, 0.5); } .grok-monitor-indicator.yellow { background-color: #F59E0B; box-shadow: 0 0 8px rgba(245, 158, 11, 0.5); } .grok-monitor-indicator.red { background-color: #EF4444; box-shadow: 0 0 8px rgba(239, 68, 68, 0.5); } .grok-monitor-details { display: none; flex-direction: column; gap: 8px; font-size: 13px; color: #555; width: 100%; } .show-details .grok-monitor-details, .full-mode:hover .grok-monitor-details, .compact-mode:hover .grok-monitor-details { display: flex; animation: fadeIn 0.3s ease forwards; } .compact-mode .grok-monitor-details { padding: 0 16px 12px; } .grok-monitor-kind-row { display: flex; align-items: center; gap: 8px; white-space: nowrap; padding: 6px 10px; border-radius: 8px; background: rgba(0, 0, 0, 0.02); justify-content: space-between; transition: background-color 0.2s ease; } .grok-monitor-kind-row:hover { background: rgba(0, 0, 0, 0.04); } .grok-monitor-kind-name { font-weight: 600; color: #333; display: flex; align-items: center; gap: 6px; } .grok-monitor-kind-name .icon { display: flex; color: #555; } .grok-monitor-info { color: #666; display: flex; align-items: center; gap: 4px; } .grok-monitor-info .time { opacity: 0.8; font-size: 12px; color: #777; display: flex; align-items: center; gap: 3px; } .grok-monitor-info .time .icon { display: flex; color: #777; } .refresh-button { display: flex; align-items: center; justify-content: center; background: rgba(37, 99, 235, 0.08); color: #2563EB; border: none; border-radius: 6px; width: 24px; height: 24px; cursor: pointer; transition: background-color 0.3s ease; padding: 0; margin-left: auto; } .refresh-button:hover { background: rgba(37, 99, 235, 0.15); } .refresh-button .icon-container { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; } .refresh-button:hover .icon-container { animation: rotateIcon 0.8s ease forwards; } .grok-monitor.updating .grok-monitor-indicator { animation: pulse 1s ease-in-out infinite; } .mode-switch-btn { width: 100%; padding: 6px 0; font-size: 13px; border: none; background: rgba(0, 0, 0, 0.02); border-radius: 6px; cursor: pointer; color: #555; transition: background 0.2s ease; margin-top: 4px; } .mode-switch-btn:hover { background: rgba(0, 0, 0, 0.05); } @keyframes pulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.3); opacity: 0.7; } } @keyframes fadeIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: translateY(0); } } @keyframes rotateIcon { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @media (prefers-color-scheme: dark) { .grok-monitor.full-mode, .grok-monitor.compact-mode { background-color: rgba(30, 30, 30, 0.9); color: #eee; border-color: rgba(255, 255, 255, 0.1); } .mode-switch-btn { background: rgba(255, 255, 255, 0.05); color: #aaa; } .mode-switch-btn:hover { background: rgba(255, 255, 255, 0.1); } .grok-monitor-title { color: #ddd; } .grok-monitor-summary { color: #ddd; } .full-mode .grok-monitor-summary { background: rgba(255, 255, 255, 0.05); } .grok-monitor-details { color: #ccc; } .grok-monitor-kind-row { background: rgba(255, 255, 255, 0.03); } .grok-monitor-kind-row:hover { background: rgba(255, 255, 255, 0.07); } .grok-monitor-kind-name { color: #ddd; } .grok-monitor-kind-name .icon { color: #aaa; } .grok-monitor-info { color: #bbb; } .grok-monitor-info .time { color: #999; } .grok-monitor-info .time .icon { color: #999; } .refresh-button { background: rgba(59, 130, 246, 0.12); } .refresh-button:hover { background: rgba(59, 130, 246, 0.2); } } `); // 工具函数:格式化等待时间 function formatWaitTime(seconds) { if (seconds <= 0) return '0分'; const minutes = Math.floor(seconds / 60); return `${minutes}分`; } // 工具函数:格式化窗口时间 function formatWindowTime(seconds) { if (seconds <= 0) return '0h'; const hours = Math.floor(seconds / 3600); return `${hours}h`; } // 切换显示模式 function toggleMode() { isCompactMode = !isCompactMode; GM_setValue('compactMode', isCompactMode); // 直接更新UI,不刷新页面 const monitorElement = document.querySelector('.grok-monitor'); if (monitorElement) { // 清空现有内容 monitorElement.innerHTML = ''; monitorElement.className = `grok-monitor ${isCompactMode ? 'compact-mode' : 'full-mode'}`; // 重新创建UI if (isCompactMode) { createCompactModeUI(monitorElement); } else { createFullModeUI(monitorElement); } // 如果有缓存的数据,直接更新UI if (cachedResults) { updateUI(cachedResults); } } } // 创建监控器UI function createMonitor() { const monitor = document.createElement('div'); monitor.className = `grok-monitor ${isCompactMode ? 'compact-mode' : 'full-mode'}`; if (isCompactMode) { // 精简模式 createCompactModeUI(monitor); } else { // 完整模式 createFullModeUI(monitor); } document.body.appendChild(monitor); return monitor; } // 创建完整模式UI function createFullModeUI(monitor) { // 标题栏 const header = document.createElement('div'); header.className = 'grok-monitor-header'; const title = document.createElement('div'); title.className = 'grok-monitor-title'; const titleIcon = document.createElement('span'); titleIcon.className = 'icon'; titleIcon.innerHTML = ICONS.BOLT; const titleText = document.createElement('span'); titleText.textContent = 'Grok 配额监控'; title.appendChild(titleIcon); title.appendChild(titleText); const refreshButton = document.createElement('button'); refreshButton.className = 'refresh-button'; refreshButton.title = '刷新数据'; // 创建图标容器,旋转将只应用于此容器 const iconContainer = document.createElement('span'); iconContainer.className = 'icon-container'; iconContainer.innerHTML = ICONS.REFRESH; refreshButton.appendChild(iconContainer); refreshButton.onclick = async (e) => { e.stopPropagation(); await checkRateLimits(); }; header.appendChild(title); header.appendChild(refreshButton); // 小版本(默认显示) const summaryRow = document.createElement('div'); summaryRow.className = 'grok-monitor-summary'; const sumText = document.createElement('div'); sumText.className = 'grok-monitor-summary-text'; const sumSpan = document.createElement('span'); sumSpan.textContent = '剩余总数: ...'; sumText.appendChild(sumSpan); const indicator = document.createElement('div'); indicator.className = 'grok-monitor-indicator'; summaryRow.appendChild(sumText); summaryRow.appendChild(indicator); // 大版本(悬浮后展开) const details = document.createElement('div'); details.className = 'grok-monitor-details'; // 为每种模式创建行 REQUEST_KINDS.forEach(kind => { const row = document.createElement('div'); row.className = 'grok-monitor-kind-row'; const nameContainer = document.createElement('div'); nameContainer.className = 'grok-monitor-kind-name'; const kindIcon = document.createElement('span'); kindIcon.className = 'icon'; kindIcon.innerHTML = ICONS.INFO; const nameSpan = document.createElement('span'); nameSpan.textContent = MODE_LABELS[kind]; nameContainer.appendChild(kindIcon); nameContainer.appendChild(nameSpan); const infoContainer = document.createElement('div'); infoContainer.className = 'grok-monitor-info'; const infoSpan = document.createElement('span'); infoSpan.textContent = '剩余 .../...'; const timeSpan = document.createElement('span'); timeSpan.className = 'time'; const timeIcon = document.createElement('span'); timeIcon.className = 'icon'; timeIcon.innerHTML = ICONS.TIMER; const timeText = document.createElement('span'); timeText.textContent = '...h刷新'; timeSpan.appendChild(timeIcon); timeSpan.appendChild(timeText); infoContainer.appendChild(infoSpan); infoContainer.appendChild(timeSpan); row.appendChild(nameContainer); row.appendChild(infoContainer); details.appendChild(row); }); // 添加切换模式按钮 const switchBtn = document.createElement('button'); switchBtn.className = 'mode-switch-btn'; switchBtn.textContent = '切换为精简模式'; switchBtn.onclick = toggleMode; details.appendChild(switchBtn); monitor.appendChild(header); monitor.appendChild(summaryRow); monitor.appendChild(details); } // 创建精简模式UI function createCompactModeUI(monitor) { // 创建精简头部 const compactHeader = document.createElement('div'); compactHeader.className = 'compact-header'; // 精简模式只显示总数和指示灯 const summaryRow = document.createElement('div'); summaryRow.className = 'grok-monitor-summary'; const sumText = document.createElement('div'); sumText.className = 'grok-monitor-summary-text'; const sumSpan = document.createElement('span'); sumSpan.textContent = '剩余总数: ...'; sumText.appendChild(sumSpan); const indicator = document.createElement('div'); indicator.className = 'grok-monitor-indicator'; // 添加刷新按钮 const refreshButton = document.createElement('button'); refreshButton.className = 'refresh-button'; refreshButton.title = '刷新数据'; const iconContainer = document.createElement('span'); iconContainer.className = 'icon-container'; iconContainer.innerHTML = ICONS.REFRESH; refreshButton.appendChild(iconContainer); refreshButton.onclick = async (e) => { e.stopPropagation(); await checkRateLimits(); }; summaryRow.appendChild(sumText); summaryRow.appendChild(indicator); summaryRow.appendChild(refreshButton); compactHeader.appendChild(summaryRow); // 创建详情部分(悬浮时展开) const details = document.createElement('div'); details.className = 'grok-monitor-details'; // 为每种模式创建行 REQUEST_KINDS.forEach(kind => { const row = document.createElement('div'); row.className = 'grok-monitor-kind-row'; const nameContainer = document.createElement('div'); nameContainer.className = 'grok-monitor-kind-name'; const kindIcon = document.createElement('span'); kindIcon.className = 'icon'; kindIcon.innerHTML = ICONS.INFO; const nameSpan = document.createElement('span'); nameSpan.textContent = MODE_LABELS[kind]; nameContainer.appendChild(kindIcon); nameContainer.appendChild(nameSpan); const infoContainer = document.createElement('div'); infoContainer.className = 'grok-monitor-info'; const infoSpan = document.createElement('span'); infoSpan.textContent = '剩余 .../...'; const timeSpan = document.createElement('span'); timeSpan.className = 'time'; const timeIcon = document.createElement('span'); timeIcon.className = 'icon'; timeIcon.innerHTML = ICONS.TIMER; const timeText = document.createElement('span'); timeText.textContent = '...h刷新'; timeSpan.appendChild(timeIcon); timeSpan.appendChild(timeText); infoContainer.appendChild(infoSpan); infoContainer.appendChild(timeSpan); row.appendChild(nameContainer); row.appendChild(infoContainer); details.appendChild(row); }); // 添加切换模式按钮 const switchBtn = document.createElement('button'); switchBtn.className = 'mode-switch-btn'; switchBtn.textContent = '切换为完整模式'; switchBtn.onclick = toggleMode; details.appendChild(switchBtn); monitor.appendChild(compactHeader); monitor.appendChild(details); } // 获取当前域名的基础URL function getBaseUrl() { return window.location.origin; } // 获取每种模式的限额 async function fetchRateLimit(kind) { try { const baseUrl = getBaseUrl(); const response = await fetch(`${baseUrl}/rest/rate-limits`, { method: 'POST', headers: { 'accept': '*/*', 'content-type': 'application/json' }, body: JSON.stringify({ requestKind: kind, modelName: "grok-3" }), credentials: 'include' }); if (response.ok) { return await response.json(); } else { throw new Error(`Failed to fetch ${kind} rate limit`); } } catch (error) { console.error('Rate limit check failed:', error); return null; } } // 一次获取所有模式数据 async function getAllRateLimits() { const results = {}; for (const kind of REQUEST_KINDS) { results[kind] = await fetchRateLimit(kind); } return results; } // 更新UI function updateUI(results) { // 缓存结果以便稍后使用 cachedResults = results; const monitor = document.querySelector('.grok-monitor'); const sumSpan = monitor.querySelector('.grok-monitor-summary-text span'); const indicator = monitor.querySelector('.grok-monitor-indicator'); monitor.classList.add('updating'); // 移除之前的指示器类 indicator.classList.remove('green', 'yellow', 'red'); // 计算合计剩余次数 let sum = 0; REQUEST_KINDS.forEach(kind => { const data = results[kind]; if (data && data.remainingQueries > 0) { sum += data.remainingQueries; } }); // 更新总数 sumSpan.textContent = `剩余总数: ${sum}`; // 指示灯颜色 if (sum === 0) { indicator.classList.add('red'); } else if (sum > 0 && sum < 5) { indicator.classList.add('yellow'); } else { indicator.classList.add('green'); } // 更新详情行 const detailRows = monitor.querySelectorAll('.grok-monitor-details .grok-monitor-kind-row'); updateDetailRows(detailRows, results); setTimeout(() => monitor.classList.remove('updating'), 1000); } // 更新详情行 function updateDetailRows(detailRows, results) { detailRows.forEach(row => { const label = row.querySelector('.grok-monitor-kind-name span:last-child')?.textContent; if (!label) return; // 跳过非模式行 const kind = Object.keys(MODE_LABELS).find(k => MODE_LABELS[k] === label); if (!kind) return; // 跳过非模式行 const data = results[kind]; const infoSpan = row.querySelector('.grok-monitor-info span:first-child'); const timeText = row.querySelector('.grok-monitor-info .time span:last-child'); if (!data) { infoSpan.textContent = '获取失败'; timeText.textContent = ''; return; } const { remainingQueries, totalQueries, windowSizeSeconds, waitTimeSeconds } = data; if (remainingQueries > 0) { infoSpan.textContent = `剩余 ${remainingQueries}/${totalQueries}`; timeText.textContent = formatWindowTime(windowSizeSeconds); } else { infoSpan.textContent = `等待刷新`; timeText.textContent = formatWaitTime(waitTimeSeconds); } }); } // 定时检查 async function checkRateLimits() { const results = await getAllRateLimits(); updateUI(results); } // 注册油猴菜单 GM_registerMenuCommand("切换显示模式", toggleMode); // 初始化 function init() { createMonitor(); checkRateLimits(); setInterval(checkRateLimits, 30000); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();