- // ==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();
- }
- })();