Grok Monitor

监控 Grok API 配额(标准、思考、深度、更深),默认显示总数,悬浮查看详情

  1. // ==UserScript==
  2. // @name Grok Monitor
  3. // @namespace https://github.com/Loongphy/Grok-Monitor
  4. // @version 1.0.0
  5. // @author Loongphy
  6. // @description 监控 Grok API 配额(标准、思考、深度、更深),默认显示总数,悬浮查看详情
  7. // @match https://grok.com/*
  8. // @grant GM_addStyle
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_registerMenuCommand
  12. // @license GPL-3.0
  13. // ==/UserScript==
  14.  
  15. /*
  16. * Grok Monitor - 监控 Grok API 配额使用情况的油猴脚本
  17. *
  18. * 本脚本基于 GPL-3.0 许可证
  19. *
  20. * 这是一个衍生作品,基于 BlueSkyXN 的 Grok Helper
  21. * 原始代码: https://github.com/BlueSkyXN/GPT-Models-Plus/blob/main/GrokHelper.js
  22. * 原作者: BlueSkyXN
  23. */
  24.  
  25. (function() {
  26. 'use strict';
  27.  
  28. // 缓存查询结果
  29. let cachedResults = null;
  30.  
  31. // 获取用户设置或设置默认值
  32. let isCompactMode = GM_getValue('compactMode', true); // 默认使用精简模式
  33.  
  34. // 四种模式 -> 中文名称对应表
  35. const MODE_LABELS = {
  36. DEFAULT: '标准',
  37. REASONING: '思考',
  38. DEEPSEARCH: '深度',
  39. DEEPERSEARCH: '更深'
  40. };
  41.  
  42. // 我们需要查询的四种模式
  43. const REQUEST_KINDS = Object.keys(MODE_LABELS);
  44.  
  45. // 图标 SVG (来自 Font Awesome 免费图标)
  46. const ICONS = {
  47. 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>',
  48. 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>',
  49. 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>',
  50. 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>'
  51. };
  52.  
  53. // 添加自定义样式
  54. GM_addStyle(`
  55. /* 通用样式 */
  56. .grok-monitor {
  57. position: fixed;
  58. left: 16px;
  59. top: 72px;
  60. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  61. z-index: 100;
  62. transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  63. width: fit-content;
  64. }
  65.  
  66. /* 完整模式样式 */
  67. .grok-monitor.full-mode {
  68. display: flex;
  69. flex-direction: column;
  70. align-items: flex-start;
  71. gap: 10px;
  72. border-radius: 12px;
  73. background-color: rgba(255, 255, 255, 0.95);
  74. color: #333;
  75. box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.1);
  76. backdrop-filter: blur(8px);
  77. max-width: 280px;
  78. border: 1px solid rgba(0, 0, 0, 0.06);
  79. transform-origin: top left;
  80. padding: 12px 16px;
  81. font-size: 14px;
  82. }
  83.  
  84. .grok-monitor.full-mode:hover {
  85. box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12), 0 2px 5px rgba(0, 0, 0, 0.1);
  86. }
  87.  
  88. /* 精简模式样式 */
  89. .grok-monitor.compact-mode {
  90. display: flex;
  91. flex-direction: column;
  92. border-radius: 12px;
  93. background-color: rgba(255, 255, 255, 0.95);
  94. box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.1);
  95. border: 1px solid rgba(0, 0, 0, 0.06);
  96. overflow: hidden;
  97. max-width: 280px;
  98. }
  99.  
  100. .grok-monitor.compact-mode .compact-header {
  101. display: flex;
  102. align-items: center;
  103. padding: 8px 16px;
  104. gap: 10px;
  105. font-size: 15px;
  106. color: #333;
  107. width: 100%;
  108. }
  109.  
  110. .grok-monitor-header {
  111. display: flex;
  112. align-items: center;
  113. justify-content: space-between;
  114. width: 100%;
  115. gap: 10px;
  116. }
  117.  
  118. .grok-monitor-title {
  119. display: flex;
  120. align-items: center;
  121. gap: 8px;
  122. font-weight: 600;
  123. font-size: 14px;
  124. color: #444;
  125. }
  126.  
  127. .grok-monitor-title .icon {
  128. display: flex;
  129. align-items: center;
  130. color: #2563EB;
  131. }
  132.  
  133. .grok-monitor-summary {
  134. display: flex;
  135. align-items: center;
  136. gap: 8px;
  137. white-space: nowrap;
  138. font-weight: 500;
  139. font-size: 15px;
  140. color: #444;
  141. width: 100%;
  142. }
  143.  
  144. .full-mode .grok-monitor-summary {
  145. background: rgba(0, 0, 0, 0.03);
  146. padding: 6px 10px;
  147. border-radius: 8px;
  148. justify-content: space-between;
  149. }
  150.  
  151. .compact-mode .grok-monitor-summary {
  152. padding: 0;
  153. margin: 0;
  154. justify-content: space-between;
  155. }
  156.  
  157. .grok-monitor-summary-text {
  158. display: flex;
  159. align-items: center;
  160. gap: 6px;
  161. }
  162.  
  163. .grok-monitor-indicator {
  164. width: 10px;
  165. height: 10px;
  166. border-radius: 50%;
  167. flex-shrink: 0;
  168. box-shadow: 0 0 0 rgba(0, 0, 0, 0);
  169. transition: all 0.3s ease;
  170. margin-left: 8px;
  171. }
  172.  
  173. .grok-monitor-indicator.green {
  174. background-color: #10B981;
  175. box-shadow: 0 0 8px rgba(16, 185, 129, 0.5);
  176. }
  177.  
  178. .grok-monitor-indicator.yellow {
  179. background-color: #F59E0B;
  180. box-shadow: 0 0 8px rgba(245, 158, 11, 0.5);
  181. }
  182.  
  183. .grok-monitor-indicator.red {
  184. background-color: #EF4444;
  185. box-shadow: 0 0 8px rgba(239, 68, 68, 0.5);
  186. }
  187.  
  188. .grok-monitor-details {
  189. display: none;
  190. flex-direction: column;
  191. gap: 8px;
  192. font-size: 13px;
  193. color: #555;
  194. width: 100%;
  195. }
  196.  
  197. .show-details .grok-monitor-details,
  198. .full-mode:hover .grok-monitor-details,
  199. .compact-mode:hover .grok-monitor-details {
  200. display: flex;
  201. animation: fadeIn 0.3s ease forwards;
  202. }
  203.  
  204. .compact-mode .grok-monitor-details {
  205. padding: 0 16px 12px;
  206. }
  207.  
  208. .grok-monitor-kind-row {
  209. display: flex;
  210. align-items: center;
  211. gap: 8px;
  212. white-space: nowrap;
  213. padding: 6px 10px;
  214. border-radius: 8px;
  215. background: rgba(0, 0, 0, 0.02);
  216. justify-content: space-between;
  217. transition: background-color 0.2s ease;
  218. }
  219.  
  220. .grok-monitor-kind-row:hover {
  221. background: rgba(0, 0, 0, 0.04);
  222. }
  223.  
  224. .grok-monitor-kind-name {
  225. font-weight: 600;
  226. color: #333;
  227. display: flex;
  228. align-items: center;
  229. gap: 6px;
  230. }
  231.  
  232. .grok-monitor-kind-name .icon {
  233. display: flex;
  234. color: #555;
  235. }
  236.  
  237. .grok-monitor-info {
  238. color: #666;
  239. display: flex;
  240. align-items: center;
  241. gap: 4px;
  242. }
  243.  
  244. .grok-monitor-info .time {
  245. opacity: 0.8;
  246. font-size: 12px;
  247. color: #777;
  248. display: flex;
  249. align-items: center;
  250. gap: 3px;
  251. }
  252.  
  253. .grok-monitor-info .time .icon {
  254. display: flex;
  255. color: #777;
  256. }
  257.  
  258. .refresh-button {
  259. display: flex;
  260. align-items: center;
  261. justify-content: center;
  262. background: rgba(37, 99, 235, 0.08);
  263. color: #2563EB;
  264. border: none;
  265. border-radius: 6px;
  266. width: 24px;
  267. height: 24px;
  268. cursor: pointer;
  269. transition: background-color 0.3s ease;
  270. padding: 0;
  271. margin-left: auto;
  272. }
  273.  
  274. .refresh-button:hover {
  275. background: rgba(37, 99, 235, 0.15);
  276. }
  277.  
  278. .refresh-button .icon-container {
  279. display: flex;
  280. align-items: center;
  281. justify-content: center;
  282. width: 100%;
  283. height: 100%;
  284. }
  285.  
  286. .refresh-button:hover .icon-container {
  287. animation: rotateIcon 0.8s ease forwards;
  288. }
  289.  
  290. .grok-monitor.updating .grok-monitor-indicator {
  291. animation: pulse 1s ease-in-out infinite;
  292. }
  293.  
  294. .mode-switch-btn {
  295. width: 100%;
  296. padding: 6px 0;
  297. font-size: 13px;
  298. border: none;
  299. background: rgba(0, 0, 0, 0.02);
  300. border-radius: 6px;
  301. cursor: pointer;
  302. color: #555;
  303. transition: background 0.2s ease;
  304. margin-top: 4px;
  305. }
  306.  
  307. .mode-switch-btn:hover {
  308. background: rgba(0, 0, 0, 0.05);
  309. }
  310.  
  311. @keyframes pulse {
  312. 0%, 100% {
  313. transform: scale(1);
  314. opacity: 1;
  315. }
  316. 50% {
  317. transform: scale(1.3);
  318. opacity: 0.7;
  319. }
  320. }
  321.  
  322. @keyframes fadeIn {
  323. from {
  324. opacity: 0;
  325. transform: translateY(-5px);
  326. }
  327. to {
  328. opacity: 1;
  329. transform: translateY(0);
  330. }
  331. }
  332.  
  333. @keyframes rotateIcon {
  334. from {
  335. transform: rotate(0deg);
  336. }
  337. to {
  338. transform: rotate(360deg);
  339. }
  340. }
  341.  
  342. @media (prefers-color-scheme: dark) {
  343. .grok-monitor.full-mode,
  344. .grok-monitor.compact-mode {
  345. background-color: rgba(30, 30, 30, 0.9);
  346. color: #eee;
  347. border-color: rgba(255, 255, 255, 0.1);
  348. }
  349.  
  350. .mode-switch-btn {
  351. background: rgba(255, 255, 255, 0.05);
  352. color: #aaa;
  353. }
  354.  
  355. .mode-switch-btn:hover {
  356. background: rgba(255, 255, 255, 0.1);
  357. }
  358.  
  359. .grok-monitor-title {
  360. color: #ddd;
  361. }
  362.  
  363. .grok-monitor-summary {
  364. color: #ddd;
  365. }
  366.  
  367. .full-mode .grok-monitor-summary {
  368. background: rgba(255, 255, 255, 0.05);
  369. }
  370.  
  371. .grok-monitor-details {
  372. color: #ccc;
  373. }
  374.  
  375. .grok-monitor-kind-row {
  376. background: rgba(255, 255, 255, 0.03);
  377. }
  378.  
  379. .grok-monitor-kind-row:hover {
  380. background: rgba(255, 255, 255, 0.07);
  381. }
  382.  
  383. .grok-monitor-kind-name {
  384. color: #ddd;
  385. }
  386.  
  387. .grok-monitor-kind-name .icon {
  388. color: #aaa;
  389. }
  390.  
  391. .grok-monitor-info {
  392. color: #bbb;
  393. }
  394.  
  395. .grok-monitor-info .time {
  396. color: #999;
  397. }
  398.  
  399. .grok-monitor-info .time .icon {
  400. color: #999;
  401. }
  402.  
  403. .refresh-button {
  404. background: rgba(59, 130, 246, 0.12);
  405. }
  406.  
  407. .refresh-button:hover {
  408. background: rgba(59, 130, 246, 0.2);
  409. }
  410. }
  411. `);
  412.  
  413. // 工具函数:格式化等待时间
  414. function formatWaitTime(seconds) {
  415. if (seconds <= 0) return '0分';
  416. const minutes = Math.floor(seconds / 60);
  417. return `${minutes}分`;
  418. }
  419.  
  420. // 工具函数:格式化窗口时间
  421. function formatWindowTime(seconds) {
  422. if (seconds <= 0) return '0h';
  423. const hours = Math.floor(seconds / 3600);
  424. return `${hours}h`;
  425. }
  426.  
  427. // 切换显示模式
  428. function toggleMode() {
  429. isCompactMode = !isCompactMode;
  430. GM_setValue('compactMode', isCompactMode);
  431.  
  432. // 直接更新UI,不刷新页面
  433. const monitorElement = document.querySelector('.grok-monitor');
  434. if (monitorElement) {
  435. // 清空现有内容
  436. monitorElement.innerHTML = '';
  437. monitorElement.className = `grok-monitor ${isCompactMode ? 'compact-mode' : 'full-mode'}`;
  438.  
  439. // 重新创建UI
  440. if (isCompactMode) {
  441. createCompactModeUI(monitorElement);
  442. } else {
  443. createFullModeUI(monitorElement);
  444. }
  445.  
  446. // 如果有缓存的数据,直接更新UI
  447. if (cachedResults) {
  448. updateUI(cachedResults);
  449. }
  450. }
  451. }
  452.  
  453. // 创建监控器UI
  454. function createMonitor() {
  455. const monitor = document.createElement('div');
  456. monitor.className = `grok-monitor ${isCompactMode ? 'compact-mode' : 'full-mode'}`;
  457.  
  458. if (isCompactMode) {
  459. // 精简模式
  460. createCompactModeUI(monitor);
  461. } else {
  462. // 完整模式
  463. createFullModeUI(monitor);
  464. }
  465.  
  466. document.body.appendChild(monitor);
  467. return monitor;
  468. }
  469.  
  470. // 创建完整模式UI
  471. function createFullModeUI(monitor) {
  472. // 标题栏
  473. const header = document.createElement('div');
  474. header.className = 'grok-monitor-header';
  475.  
  476. const title = document.createElement('div');
  477. title.className = 'grok-monitor-title';
  478.  
  479. const titleIcon = document.createElement('span');
  480. titleIcon.className = 'icon';
  481. titleIcon.innerHTML = ICONS.BOLT;
  482.  
  483. const titleText = document.createElement('span');
  484. titleText.textContent = 'Grok 配额监控';
  485.  
  486. title.appendChild(titleIcon);
  487. title.appendChild(titleText);
  488.  
  489. const refreshButton = document.createElement('button');
  490. refreshButton.className = 'refresh-button';
  491. refreshButton.title = '刷新数据';
  492.  
  493. // 创建图标容器,旋转将只应用于此容器
  494. const iconContainer = document.createElement('span');
  495. iconContainer.className = 'icon-container';
  496. iconContainer.innerHTML = ICONS.REFRESH;
  497.  
  498. refreshButton.appendChild(iconContainer);
  499.  
  500. refreshButton.onclick = async (e) => {
  501. e.stopPropagation();
  502. await checkRateLimits();
  503. };
  504.  
  505. header.appendChild(title);
  506. header.appendChild(refreshButton);
  507.  
  508. // 小版本(默认显示)
  509. const summaryRow = document.createElement('div');
  510. summaryRow.className = 'grok-monitor-summary';
  511.  
  512. const sumText = document.createElement('div');
  513. sumText.className = 'grok-monitor-summary-text';
  514.  
  515. const sumSpan = document.createElement('span');
  516. sumSpan.textContent = '剩余总数: ...';
  517.  
  518. sumText.appendChild(sumSpan);
  519.  
  520. const indicator = document.createElement('div');
  521. indicator.className = 'grok-monitor-indicator';
  522.  
  523. summaryRow.appendChild(sumText);
  524. summaryRow.appendChild(indicator);
  525.  
  526. // 大版本(悬浮后展开)
  527. const details = document.createElement('div');
  528. details.className = 'grok-monitor-details';
  529.  
  530. // 为每种模式创建行
  531. REQUEST_KINDS.forEach(kind => {
  532. const row = document.createElement('div');
  533. row.className = 'grok-monitor-kind-row';
  534.  
  535. const nameContainer = document.createElement('div');
  536. nameContainer.className = 'grok-monitor-kind-name';
  537.  
  538. const kindIcon = document.createElement('span');
  539. kindIcon.className = 'icon';
  540. kindIcon.innerHTML = ICONS.INFO;
  541.  
  542. const nameSpan = document.createElement('span');
  543. nameSpan.textContent = MODE_LABELS[kind];
  544.  
  545. nameContainer.appendChild(kindIcon);
  546. nameContainer.appendChild(nameSpan);
  547.  
  548. const infoContainer = document.createElement('div');
  549. infoContainer.className = 'grok-monitor-info';
  550.  
  551. const infoSpan = document.createElement('span');
  552. infoSpan.textContent = '剩余 .../...';
  553.  
  554. const timeSpan = document.createElement('span');
  555. timeSpan.className = 'time';
  556. const timeIcon = document.createElement('span');
  557. timeIcon.className = 'icon';
  558. timeIcon.innerHTML = ICONS.TIMER;
  559.  
  560. const timeText = document.createElement('span');
  561. timeText.textContent = '...h刷新';
  562.  
  563. timeSpan.appendChild(timeIcon);
  564. timeSpan.appendChild(timeText);
  565.  
  566. infoContainer.appendChild(infoSpan);
  567. infoContainer.appendChild(timeSpan);
  568.  
  569. row.appendChild(nameContainer);
  570. row.appendChild(infoContainer);
  571. details.appendChild(row);
  572. });
  573.  
  574. // 添加切换模式按钮
  575. const switchBtn = document.createElement('button');
  576. switchBtn.className = 'mode-switch-btn';
  577. switchBtn.textContent = '切换为精简模式';
  578. switchBtn.onclick = toggleMode;
  579. details.appendChild(switchBtn);
  580.  
  581. monitor.appendChild(header);
  582. monitor.appendChild(summaryRow);
  583. monitor.appendChild(details);
  584. }
  585.  
  586. // 创建精简模式UI
  587. function createCompactModeUI(monitor) {
  588. // 创建精简头部
  589. const compactHeader = document.createElement('div');
  590. compactHeader.className = 'compact-header';
  591.  
  592. // 精简模式只显示总数和指示灯
  593. const summaryRow = document.createElement('div');
  594. summaryRow.className = 'grok-monitor-summary';
  595.  
  596. const sumText = document.createElement('div');
  597. sumText.className = 'grok-monitor-summary-text';
  598.  
  599. const sumSpan = document.createElement('span');
  600. sumSpan.textContent = '剩余总数: ...';
  601.  
  602. sumText.appendChild(sumSpan);
  603.  
  604. const indicator = document.createElement('div');
  605. indicator.className = 'grok-monitor-indicator';
  606.  
  607. // 添加刷新按钮
  608. const refreshButton = document.createElement('button');
  609. refreshButton.className = 'refresh-button';
  610. refreshButton.title = '刷新数据';
  611.  
  612. const iconContainer = document.createElement('span');
  613. iconContainer.className = 'icon-container';
  614. iconContainer.innerHTML = ICONS.REFRESH;
  615.  
  616. refreshButton.appendChild(iconContainer);
  617.  
  618. refreshButton.onclick = async (e) => {
  619. e.stopPropagation();
  620. await checkRateLimits();
  621. };
  622.  
  623. summaryRow.appendChild(sumText);
  624. summaryRow.appendChild(indicator);
  625. summaryRow.appendChild(refreshButton);
  626.  
  627. compactHeader.appendChild(summaryRow);
  628.  
  629. // 创建详情部分(悬浮时展开)
  630. const details = document.createElement('div');
  631. details.className = 'grok-monitor-details';
  632.  
  633. // 为每种模式创建行
  634. REQUEST_KINDS.forEach(kind => {
  635. const row = document.createElement('div');
  636. row.className = 'grok-monitor-kind-row';
  637.  
  638. const nameContainer = document.createElement('div');
  639. nameContainer.className = 'grok-monitor-kind-name';
  640.  
  641. const kindIcon = document.createElement('span');
  642. kindIcon.className = 'icon';
  643. kindIcon.innerHTML = ICONS.INFO;
  644.  
  645. const nameSpan = document.createElement('span');
  646. nameSpan.textContent = MODE_LABELS[kind];
  647.  
  648. nameContainer.appendChild(kindIcon);
  649. nameContainer.appendChild(nameSpan);
  650.  
  651. const infoContainer = document.createElement('div');
  652. infoContainer.className = 'grok-monitor-info';
  653.  
  654. const infoSpan = document.createElement('span');
  655. infoSpan.textContent = '剩余 .../...';
  656.  
  657. const timeSpan = document.createElement('span');
  658. timeSpan.className = 'time';
  659. const timeIcon = document.createElement('span');
  660. timeIcon.className = 'icon';
  661. timeIcon.innerHTML = ICONS.TIMER;
  662.  
  663. const timeText = document.createElement('span');
  664. timeText.textContent = '...h刷新';
  665.  
  666. timeSpan.appendChild(timeIcon);
  667. timeSpan.appendChild(timeText);
  668.  
  669. infoContainer.appendChild(infoSpan);
  670. infoContainer.appendChild(timeSpan);
  671.  
  672. row.appendChild(nameContainer);
  673. row.appendChild(infoContainer);
  674. details.appendChild(row);
  675. });
  676.  
  677. // 添加切换模式按钮
  678. const switchBtn = document.createElement('button');
  679. switchBtn.className = 'mode-switch-btn';
  680. switchBtn.textContent = '切换为完整模式';
  681. switchBtn.onclick = toggleMode;
  682. details.appendChild(switchBtn);
  683.  
  684. monitor.appendChild(compactHeader);
  685. monitor.appendChild(details);
  686. }
  687.  
  688. // 获取当前域名的基础URL
  689. function getBaseUrl() {
  690. return window.location.origin;
  691. }
  692.  
  693. // 获取每种模式的限额
  694. async function fetchRateLimit(kind) {
  695. try {
  696. const baseUrl = getBaseUrl();
  697. const response = await fetch(`${baseUrl}/rest/rate-limits`, {
  698. method: 'POST',
  699. headers: {
  700. 'accept': '*/*',
  701. 'content-type': 'application/json'
  702. },
  703. body: JSON.stringify({
  704. requestKind: kind,
  705. modelName: "grok-3"
  706. }),
  707. credentials: 'include'
  708. });
  709.  
  710. if (response.ok) {
  711. return await response.json();
  712. } else {
  713. throw new Error(`Failed to fetch ${kind} rate limit`);
  714. }
  715. } catch (error) {
  716. console.error('Rate limit check failed:', error);
  717. return null;
  718. }
  719. }
  720.  
  721. // 一次获取所有模式数据
  722. async function getAllRateLimits() {
  723. const results = {};
  724. for (const kind of REQUEST_KINDS) {
  725. results[kind] = await fetchRateLimit(kind);
  726. }
  727. return results;
  728. }
  729.  
  730. // 更新UI
  731. function updateUI(results) {
  732. // 缓存结果以便稍后使用
  733. cachedResults = results;
  734.  
  735. const monitor = document.querySelector('.grok-monitor');
  736. const sumSpan = monitor.querySelector('.grok-monitor-summary-text span');
  737. const indicator = monitor.querySelector('.grok-monitor-indicator');
  738.  
  739. monitor.classList.add('updating');
  740.  
  741. // 移除之前的指示器类
  742. indicator.classList.remove('green', 'yellow', 'red');
  743.  
  744. // 计算合计剩余次数
  745. let sum = 0;
  746. REQUEST_KINDS.forEach(kind => {
  747. const data = results[kind];
  748. if (data && data.remainingQueries > 0) {
  749. sum += data.remainingQueries;
  750. }
  751. });
  752.  
  753. // 更新总数
  754. sumSpan.textContent = `剩余总数: ${sum}`;
  755.  
  756. // 指示灯颜色
  757. if (sum === 0) {
  758. indicator.classList.add('red');
  759. } else if (sum > 0 && sum < 5) {
  760. indicator.classList.add('yellow');
  761. } else {
  762. indicator.classList.add('green');
  763. }
  764.  
  765. // 更新详情行
  766. const detailRows = monitor.querySelectorAll('.grok-monitor-details .grok-monitor-kind-row');
  767. updateDetailRows(detailRows, results);
  768.  
  769. setTimeout(() => monitor.classList.remove('updating'), 1000);
  770. }
  771.  
  772. // 更新详情行
  773. function updateDetailRows(detailRows, results) {
  774. detailRows.forEach(row => {
  775. const label = row.querySelector('.grok-monitor-kind-name span:last-child')?.textContent;
  776. if (!label) return; // 跳过非模式行
  777.  
  778. const kind = Object.keys(MODE_LABELS).find(k => MODE_LABELS[k] === label);
  779. if (!kind) return; // 跳过非模式行
  780.  
  781. const data = results[kind];
  782.  
  783. const infoSpan = row.querySelector('.grok-monitor-info span:first-child');
  784. const timeText = row.querySelector('.grok-monitor-info .time span:last-child');
  785.  
  786. if (!data) {
  787. infoSpan.textContent = '获取失败';
  788. timeText.textContent = '';
  789. return;
  790. }
  791.  
  792. const { remainingQueries, totalQueries, windowSizeSeconds, waitTimeSeconds } = data;
  793. if (remainingQueries > 0) {
  794. infoSpan.textContent = `剩余 ${remainingQueries}/${totalQueries}`;
  795. timeText.textContent = formatWindowTime(windowSizeSeconds);
  796. } else {
  797. infoSpan.textContent = `等待刷新`;
  798. timeText.textContent = formatWaitTime(waitTimeSeconds);
  799. }
  800. });
  801. }
  802.  
  803. // 定时检查
  804. async function checkRateLimits() {
  805. const results = await getAllRateLimits();
  806. updateUI(results);
  807. }
  808.  
  809. // 注册油猴菜单
  810. GM_registerMenuCommand("切换显示模式", toggleMode);
  811.  
  812. // 初始化
  813. function init() {
  814. createMonitor();
  815. checkRateLimits();
  816. setInterval(checkRateLimits, 30000);
  817. }
  818.  
  819. if (document.readyState === 'loading') {
  820. document.addEventListener('DOMContentLoaded', init);
  821. } else {
  822. init();
  823. }
  824. })();