Greasy Fork镜像 支持简体中文。

AI助手選擇器 / AI Assistant Selector

一個 Tampermonkey 腳本,提供浮動介面整合多款 AI 助手,支援右鍵呼叫、清空歷史訊息、可調整尺寸,並記錄 AI 選擇狀態與位置,修復 ChatGPT 回應監聽與 UI 更

  1. // ==UserScript==
  2. // @name AI助手選擇器 / AI Assistant Selector
  3. // @name:en AI Assistant Selector
  4. // @namespace http://tampermonkey.net/
  5. // @version 3.0
  6. // @description 一個 Tampermonkey 腳本,提供浮動介面整合多款 AI 助手,支援右鍵呼叫、清空歷史訊息、可調整尺寸,並記錄 AI 選擇狀態與位置,修復 ChatGPT 回應監聽與 UI 更
  7. // @description:en A Tampermonkey script that provides a floating interface to integrate multiple AI assistants, supporting right-click invocation, clearing history messages, resizable UI, and recording AI selection state and position, with fixes for ChatGPT response monitoring and UI updates
  8. // @author Your name
  9. // @match *://*/*
  10. // @match *://grok.com/*
  11. // @match *://chatgpt.com/*
  12. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  13. // @grant GM_addStyle
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @grant GM_xmlhttpRequest
  17. // @grant GM_openInTab
  18. // @license MIT
  19. // ==/UserScript==
  20.  
  21. (function() {
  22. 'use strict';
  23.  
  24. // 樣式定義
  25. const styles = `
  26. .ai-selector-container {
  27. position: fixed;
  28. background: #2c2c2c;
  29. padding: 15px;
  30. border-radius: 10px;
  31. z-index: 9999;
  32. min-width: 200px;
  33. min-height: 200px;
  34. top: ${GM_getValue('containerTop', '50px')};
  35. left: ${GM_getValue('containerLeft', '50px')};
  36. box-shadow: 0 4px 6px rgba(0,0,0,0.1);
  37. color: white;
  38. font-family: Arial, sans-serif;
  39. resize: both;
  40. overflow: auto;
  41. }
  42. .ai-selector-bubble {
  43. position: fixed;
  44. width: 40px;
  45. height: 40px;
  46. background: #666;
  47. border-radius: 50%;
  48. display: flex;
  49. align-items: center;
  50. justify-content: center;
  51. color: white;
  52. font-size: 16px;
  53. font-weight: bold;
  54. cursor: move;
  55. z-index: 10000;
  56. box-shadow: 0 2px 4px rgba(0,0,0,0.2);
  57. }
  58. .ai-selector-header {
  59. display: flex;
  60. justify-content: space-between;
  61. align-items: center;
  62. margin-bottom: 10px;
  63. cursor: move;
  64. background: #3a3a3a;
  65. padding: 5px 10px;
  66. border-radius: 5px;
  67. }
  68. .ai-selector-title {
  69. font-size: 16px;
  70. font-weight: bold;
  71. }
  72. .ai-selector-minimize {
  73. cursor: pointer;
  74. padding: 5px;
  75. }
  76. .ai-selector-content {
  77. display: none;
  78. flex-direction: column;
  79. gap: 10px;
  80. }
  81. .ai-option {
  82. display: flex;
  83. align-items: center;
  84. padding: 8px;
  85. border-radius: 5px;
  86. cursor: pointer;
  87. transition: background 0.3s;
  88. }
  89. .ai-option:hover {
  90. background: rgba(255,255,255,0.1);
  91. }
  92. .ai-option.selected {
  93. background: #4285f4;
  94. }
  95. .ai-name {
  96. margin-left: 10px;
  97. }
  98. .question-input {
  99. width: 100%;
  100. padding: 8px;
  101. border-radius: 5px;
  102. border: 1px solid #444;
  103. background: #1c1c1c;
  104. color: white;
  105. margin-top: 10px;
  106. box-sizing: border-box;
  107. }
  108. .send-button {
  109. width: 100%;
  110. padding: 8px;
  111. border-radius: 5px;
  112. border: none;
  113. background: #4285f4;
  114. color: white;
  115. cursor: pointer;
  116. margin-top: 10px;
  117. }
  118. .send-button:hover {
  119. background: #5294ff;
  120. }
  121. .send-button:disabled {
  122. background: #666;
  123. cursor: not-allowed;
  124. }
  125. .clear-button {
  126. width: 100%;
  127. padding: 8px;
  128. border-radius: 5px;
  129. border: none;
  130. background: #e74c3c;
  131. color: white;
  132. cursor: pointer;
  133. margin-bottom: 10px;
  134. }
  135. .clear-button:hover {
  136. background: #ff6655;
  137. }
  138. .grok-response-container, .chatgpt-response-container {
  139. position: fixed;
  140. background: #2c2c2c;
  141. padding: 15px;
  142. border-radius: 10px;
  143. z-index: 9998;
  144. min-width: 200px;
  145. min-height: 200px;
  146. top: ${GM_getValue('grokResponseTop', '100px')};
  147. left: ${GM_getValue('grokResponseLeft', '100px')};
  148. box-shadow: 0 4px 6px rgba(0,0,0,0.1);
  149. color: white;
  150. font-family: Arial, sans-serif;
  151. resize: both;
  152. overflow-y: auto;
  153. display: flex;
  154. flex-direction: column;
  155. gap: 10px;
  156. }
  157. .grok-response-header, .chatgpt-response-header {
  158. display: flex;
  159. justify-content: space-between;
  160. align-items: center;
  161. background: #3a3a3a;
  162. padding: 5px 10px;
  163. border-radius: 5px;
  164. cursor: move;
  165. flex-shrink: 0;
  166. margin-bottom: 0;
  167. }
  168. .grok-response-title, .chatgpt-response-title {
  169. font-size: 16px;
  170. font-weight: bold;
  171. }
  172. .grok-response-close, .chatgpt-response-close {
  173. cursor: pointer;
  174. padding: 5px;
  175. }
  176. .grok-response, .chatgpt-response {
  177. padding: 10px;
  178. background: #1c1c1c;
  179. border-radius: 5px;
  180. border: 1px solid #444;
  181. color: white;
  182. white-space: pre-wrap;
  183. flex-grow: 1;
  184. }
  185. .grok-response p, .chatgpt-response p {
  186. margin: 5px 0;
  187. padding: 5px;
  188. background: #333;
  189. border-radius: 3px;
  190. }
  191. `;
  192.  
  193. // AI助手配置
  194. const AIs = [
  195. { id: 'gemini', name: 'Gemini', url: 'https://gemini.google.com/app', inputSelector: 'rich-textarea.text-input-field_textarea', color: '#8e44ad' },
  196. { id: 'grok', name: 'Grok', url: 'https://grok.com/', color: '#e74c3c' },
  197. { id: 'chatgpt', name: 'ChatGPT', url: 'https://chatgpt.com/', color: '#27ae60' },
  198. { id: 'perplexity', name: 'Perplexity', url: 'https://www.perplexity.ai/', color: '#3498db' }
  199. ];
  200.  
  201. // 添加樣式
  202. GM_addStyle(styles);
  203.  
  204. // 拖動功能
  205. function makeDraggable(element, handle, saveKeyPrefix) {
  206. let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  207. let isDragging = false;
  208. let moved = false;
  209.  
  210. const dragHandle = handle || element;
  211.  
  212. dragHandle.addEventListener('mousedown', dragStart);
  213.  
  214. function dragStart(e) {
  215. const rect = element.getBoundingClientRect();
  216. const isBottomRight = e.clientX > rect.right - 20 && e.clientY > rect.bottom - 20;
  217. if (isBottomRight && element.style.resize === 'both') {
  218. return;
  219. }
  220.  
  221. e.preventDefault();
  222. pos3 = e.clientX;
  223. pos4 = e.clientY;
  224. isDragging = true;
  225. moved = false;
  226.  
  227. document.addEventListener('mousemove', dragMove);
  228. document.addEventListener('mouseup', dragEnd);
  229. }
  230.  
  231. function dragMove(e) {
  232. if (!isDragging) return;
  233.  
  234. e.preventDefault();
  235. pos1 = pos3 - e.clientX;
  236. pos2 = pos4 - e.clientY;
  237. pos3 = e.clientX;
  238. pos4 = e.clientY;
  239.  
  240. let newTop = element.offsetTop - pos2;
  241. let newLeft = element.offsetLeft - pos1;
  242.  
  243. newTop = Math.max(0, Math.min(newTop, window.innerHeight - element.offsetHeight));
  244. newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - element.offsetWidth));
  245.  
  246. element.style.top = `${newTop}px`;
  247. element.style.left = `${newLeft}px`;
  248.  
  249. moved = true;
  250. }
  251.  
  252. function dragEnd(e) {
  253. if (!isDragging) return;
  254.  
  255. document.removeEventListener('mousemove', dragMove);
  256. document.removeEventListener('mouseup', dragEnd);
  257.  
  258. isDragging = false;
  259.  
  260. if (saveKeyPrefix && moved) {
  261. GM_setValue(`${saveKeyPrefix}Top`, element.style.top || `${element.offsetTop}px`);
  262. GM_setValue(`${saveKeyPrefix}Left`, element.style.left || `${element.offsetLeft}px`);
  263. console.log(`Saved ${saveKeyPrefix}Top: ${element.style.top}, ${saveKeyPrefix}Left: ${element.style.left}`);
  264. }
  265. }
  266. }
  267.  
  268. // 創建UI
  269. function createUI() {
  270. const bubble = document.createElement('div');
  271. bubble.className = 'ai-selector-bubble';
  272. bubble.textContent = 'AI';
  273. const savedBubbleTop = GM_getValue('positionTop', '20px');
  274. const savedBubbleLeft = GM_getValue('positionLeft', 'calc(100% - 60px)');
  275. bubble.style.top = savedBubbleTop;
  276. bubble.style.left = savedBubbleLeft;
  277.  
  278. const container = document.createElement('div');
  279. container.className = 'ai-selector-container';
  280. container.style.display = 'none';
  281.  
  282. // 從 GM_getValue 獲取保存的尺寸和位置
  283. const savedWidth = GM_getValue('containerWidth', 300); // 純數值,無單位
  284. const savedHeight = GM_getValue('containerHeight', 300); // 純數值,無單位
  285. const savedTop = GM_getValue('containerTop', '50px');
  286. const savedLeft = GM_getValue('containerLeft', '50px');
  287.  
  288. // 應用保存的值,確保最小值並添加單位
  289. container.style.width = `${Math.max(savedWidth, 200)}px`;
  290. container.style.height = `${Math.max(savedHeight, 200)}px`;
  291. container.style.top = savedTop;
  292. container.style.left = savedLeft;
  293.  
  294. console.log(`Loaded container - width: ${container.style.width}, height: ${container.style.height}, top: ${container.style.top}, left: ${container.style.left}`);
  295.  
  296. const header = document.createElement('div');
  297. header.className = 'ai-selector-header';
  298. const title = document.createElement('div');
  299. title.className = 'ai-selector-title';
  300. title.textContent = 'AI助手選擇器 / AI Assistant Selector';
  301. const minimize = document.createElement('div');
  302. minimize.className = 'ai-selector-minimize';
  303. minimize.textContent = '×';
  304. header.appendChild(title);
  305. header.appendChild(minimize);
  306.  
  307. const content = document.createElement('div');
  308. content.className = 'ai-selector-content';
  309.  
  310. const selectedAIs = GM_getValue('selectedAIs', AIs.map(ai => ai.id));
  311. AIs.forEach(ai => {
  312. const option = document.createElement('div');
  313. option.className = 'ai-option';
  314. option.dataset.aiId = ai.id;
  315. option.style.border = `2px solid ${ai.color}`;
  316. if (selectedAIs.includes(ai.id)) {
  317. option.classList.add('selected');
  318. }
  319. const name = document.createElement('span');
  320. name.className = 'ai-name';
  321. name.textContent = ai.name;
  322. option.appendChild(name);
  323. content.appendChild(option);
  324. });
  325.  
  326. const questionInput = document.createElement('textarea');
  327. questionInput.className = 'question-input';
  328. questionInput.placeholder = '輸入您的問題 / Enter your question';
  329.  
  330. const sendButton = document.createElement('button');
  331. sendButton.className = 'send-button';
  332. sendButton.textContent = '發送到選中的AI / Send to Selected AI';
  333.  
  334. content.appendChild(questionInput);
  335. content.appendChild(sendButton);
  336.  
  337. container.appendChild(header);
  338. container.appendChild(content);
  339.  
  340. // 創建Grok回應UI
  341. const grokResponseContainer = document.createElement('div');
  342. grokResponseContainer.className = 'grok-response-container';
  343. grokResponseContainer.style.display = 'none';
  344.  
  345. let grokWidth = GM_getValue('grokResponseWidth', 300); // 純數值,無單位
  346. let grokHeight = GM_getValue('grokResponseHeight', 200); // 純數值,無單位
  347. const grokTop = GM_getValue('grokResponseTop', '100px');
  348. const grokLeft = GM_getValue('grokResponseLeft', '100px');
  349.  
  350. grokWidth = Math.max(grokWidth, 200);
  351. grokHeight = Math.max(grokHeight, 200);
  352.  
  353. grokResponseContainer.style.top = grokTop;
  354. grokResponseContainer.style.left = grokLeft;
  355. grokResponseContainer.style.width = `${grokWidth}px`;
  356. grokResponseContainer.style.height = `${grokHeight}px`;
  357.  
  358. const grokHeader = document.createElement('div');
  359. grokHeader.className = 'grok-response-header';
  360. const grokTitle = document.createElement('div');
  361. grokTitle.className = 'grok-response-title';
  362. grokTitle.textContent = 'Grok 回應 / Grok Response';
  363. const grokClose = document.createElement('div');
  364. grokClose.className = 'grok-response-close';
  365. grokClose.textContent = '×';
  366. grokHeader.appendChild(grokTitle);
  367. grokHeader.appendChild(grokClose);
  368.  
  369. const grokResponseDiv = document.createElement('div');
  370. grokResponseDiv.className = 'grok-response';
  371. const grokInitialP = document.createElement('p');
  372. grokInitialP.textContent = '等待 Grok 回應... / Waiting for Grok response...';
  373. grokResponseDiv.appendChild(grokInitialP);
  374.  
  375. const grokClearButton = document.createElement('button');
  376. grokClearButton.className = 'clear-button';
  377. grokClearButton.textContent = '清空歷史訊息 / Clear History';
  378.  
  379. grokResponseContainer.appendChild(grokHeader);
  380. grokResponseContainer.appendChild(grokClearButton);
  381. grokResponseContainer.appendChild(grokResponseDiv);
  382.  
  383. // 創建ChatGPT回應UI
  384. const chatgptResponseContainer = document.createElement('div');
  385. chatgptResponseContainer.className = 'chatgpt-response-container';
  386. chatgptResponseContainer.style.display = 'none';
  387.  
  388. let chatgptWidth = GM_getValue('chatgptResponseWidth', 300); // 純數值,無單位
  389. let chatgptHeight = GM_getValue('chatgptResponseHeight', 200); // 純數值,無單位
  390. const chatgptTop = GM_getValue('chatgptResponseTop', '150px');
  391. const chatgptLeft = GM_getValue('chatgptResponseLeft', '150px');
  392.  
  393. chatgptWidth = Math.max(chatgptWidth, 200);
  394. chatgptHeight = Math.max(chatgptHeight, 200);
  395.  
  396. chatgptResponseContainer.style.top = chatgptTop;
  397. chatgptResponseContainer.style.left = chatgptLeft;
  398. chatgptResponseContainer.style.width = `${chatgptWidth}px`;
  399. chatgptResponseContainer.style.height = `${chatgptHeight}px`;
  400.  
  401. const chatgptHeader = document.createElement('div');
  402. chatgptHeader.className = 'chatgpt-response-header';
  403. const chatgptTitle = document.createElement('div');
  404. chatgptTitle.className = 'chatgpt-response-title';
  405. chatgptTitle.textContent = 'ChatGPT 回應 / ChatGPT Response';
  406. const chatgptClose = document.createElement('div');
  407. chatgptClose.className = 'chatgpt-response-close';
  408. chatgptClose.textContent = '×';
  409. chatgptHeader.appendChild(chatgptTitle);
  410. chatgptHeader.appendChild(chatgptClose);
  411.  
  412. const chatgptResponseDiv = document.createElement('div');
  413. chatgptResponseDiv.className = 'chatgpt-response';
  414. const chatgptInitialP = document.createElement('p');
  415. chatgptInitialP.textContent = '等待 ChatGPT 回應... / Waiting for ChatGPT response...';
  416. chatgptResponseDiv.appendChild(chatgptInitialP);
  417.  
  418. const chatgptClearButton = document.createElement('button');
  419. chatgptClearButton.className = 'clear-button';
  420. chatgptClearButton.textContent = '清空歷史訊息 / Clear History';
  421.  
  422. chatgptResponseContainer.appendChild(chatgptHeader);
  423. chatgptResponseContainer.appendChild(chatgptClearButton);
  424. chatgptResponseContainer.appendChild(chatgptResponseDiv);
  425.  
  426. document.body.appendChild(bubble);
  427. document.body.appendChild(container);
  428. document.body.appendChild(grokResponseContainer);
  429. document.body.appendChild(chatgptResponseContainer);
  430.  
  431. return { bubble, container, grokResponseContainer, grokResponseDiv, chatgptResponseContainer, chatgptResponseDiv, header, grokHeader, chatgptHeader, grokClearButton, chatgptClearButton };
  432. }
  433.  
  434. // 初始化事件監聽
  435. function initializeEvents(bubble, container, grokResponseContainer, grokResponseDiv, chatgptResponseContainer, chatgptResponseDiv, header, grokHeader, chatgptHeader, grokClearButton, chatgptClearButton) {
  436. const aiOptions = container.querySelectorAll('.ai-option');
  437. const questionInput = container.querySelector('.question-input');
  438. const sendButton = container.querySelector('.send-button');
  439. const minimizeButton = container.querySelector('.ai-selector-minimize');
  440. const content = container.querySelector('.ai-selector-content');
  441. const grokCloseButton = grokResponseContainer.querySelector('.grok-response-close');
  442. const chatgptCloseButton = chatgptResponseContainer.querySelector('.chatgpt-response-close');
  443.  
  444. aiOptions.forEach(option => {
  445. option.addEventListener('click', () => {
  446. option.classList.toggle('selected');
  447. const selectedAIs = Array.from(aiOptions)
  448. .filter(opt => opt.classList.contains('selected'))
  449. .map(opt => opt.dataset.aiId);
  450. GM_setValue('selectedAIs', selectedAIs);
  451. });
  452. });
  453.  
  454. minimizeButton.addEventListener('click', () => {
  455. container.style.display = 'none';
  456. bubble.style.display = 'flex';
  457. });
  458.  
  459. sendButton.addEventListener('click', () => {
  460. const selectedAIs = Array.from(aiOptions).filter(option => option.classList.contains('selected'));
  461. const question = questionInput.value.trim();
  462. if (selectedAIs.length > 0 && question) {
  463. selectedAIs.forEach(aiOption => {
  464. const aiId = aiOption.dataset.aiId;
  465. const ai = AIs.find(a => a.id === aiId);
  466. if (ai) {
  467. openAIInNewTab(ai, question);
  468. if (ai.id === 'grok') {
  469. grokResponseContainer.style.display = 'block';
  470. while (grokResponseDiv.firstChild) {
  471. grokResponseDiv.removeChild(grokResponseDiv.firstChild);
  472. }
  473. const p = document.createElement('p');
  474. p.textContent = '等待 Grok 回應... / Waiting for Grok response...';
  475. grokResponseDiv.appendChild(p);
  476. }
  477. if (ai.id === 'chatgpt') {
  478. chatgptResponseContainer.style.display = 'block';
  479. while (chatgptResponseDiv.firstChild) {
  480. chatgptResponseDiv.removeChild(chatgptResponseDiv.firstChild);
  481. }
  482. const p = document.createElement('p');
  483. p.textContent = '等待 ChatGPT 回應... / Waiting for ChatGPT response...';
  484. chatgptResponseDiv.appendChild(p);
  485. }
  486. }
  487. });
  488. questionInput.value = '';
  489. }
  490. });
  491.  
  492. grokClearButton.addEventListener('click', () => {
  493. while (grokResponseDiv.firstChild) {
  494. grokResponseDiv.removeChild(grokResponseDiv.firstChild);
  495. }
  496. const p = document.createElement('p');
  497. p.textContent = '等待 Grok 回應... / Waiting for Grok response...';
  498. grokResponseDiv.appendChild(p);
  499. });
  500.  
  501. chatgptClearButton.addEventListener('click', () => {
  502. while (chatgptResponseDiv.firstChild) {
  503. chatgptResponseDiv.removeChild(chatgptResponseDiv.firstChild);
  504. }
  505. const p = document.createElement('p');
  506. p.textContent = '等待 ChatGPT 回應... / Waiting for ChatGPT response...';
  507. chatgptResponseDiv.appendChild(p);
  508. });
  509.  
  510. grokCloseButton.addEventListener('click', () => {
  511. grokResponseContainer.style.display = 'none';
  512. });
  513.  
  514. chatgptCloseButton.addEventListener('click', () => {
  515. chatgptResponseContainer.style.display = 'none';
  516. });
  517.  
  518. bubble.addEventListener('click', showContainer);
  519.  
  520. makeDraggable(container, header, 'container');
  521. makeDraggable(bubble, null, 'position');
  522. makeDraggable(grokResponseContainer, grokHeader, 'grokResponse');
  523. makeDraggable(chatgptResponseContainer, chatgptHeader, 'chatgptResponse');
  524.  
  525. document.addEventListener('contextmenu', (e) => {
  526. if (container.style.display === 'none') {
  527. e.preventDefault();
  528. showContainer();
  529. }
  530. });
  531.  
  532. window.addEventListener('resize', () => {
  533. if (container.style.display !== 'none') {
  534. const containerWidth = container.offsetWidth;
  535. const containerHeight = container.offsetHeight;
  536. let containerTop = parseInt(container.style.top || GM_getValue('containerTop', '50px'));
  537. let containerLeft = parseInt(container.style.left || GM_getValue('containerLeft', '50px'));
  538. containerTop = Math.max(0, Math.min(containerTop, window.innerHeight - containerHeight));
  539. containerLeft = Math.max(0, Math.min(containerLeft, window.innerWidth - containerWidth));
  540. container.style.top = `${containerTop}px`;
  541. container.style.left = `${containerLeft}px`;
  542. GM_setValue('containerTop', `${containerTop}px`);
  543. GM_setValue('containerLeft', `${containerLeft}px`);
  544. }
  545. if (grokResponseContainer.style.display !== 'none') {
  546. const grokWidth = grokResponseContainer.offsetWidth;
  547. const grokHeight = grokResponseContainer.offsetHeight;
  548. let grokTop = parseInt(grokResponseContainer.style.top);
  549. let grokLeft = parseInt(grokResponseContainer.style.left);
  550. grokTop = Math.max(0, Math.min(grokTop, window.innerHeight - grokHeight));
  551. grokLeft = Math.max(0, Math.min(grokLeft, window.innerWidth - grokWidth));
  552. grokResponseContainer.style.top = `${grokTop}px`;
  553. grokResponseContainer.style.left = `${grokLeft}px`;
  554. }
  555. if (chatgptResponseContainer.style.display !== 'none') {
  556. const chatgptWidth = chatgptResponseContainer.offsetWidth;
  557. const chatgptHeight = chatgptResponseContainer.offsetHeight;
  558. let chatgptTop = parseInt(chatgptResponseContainer.style.top);
  559. let chatgptLeft = parseInt(chatgptResponseContainer.style.left);
  560. chatgptTop = Math.max(0, Math.min(chatgptTop, window.innerHeight - chatgptHeight));
  561. chatgptLeft = Math.max(0, Math.min(chatgptLeft, window.innerWidth - chatgptWidth));
  562. chatgptResponseContainer.style.top = `${chatgptTop}px`;
  563. chatgptResponseContainer.style.left = `${chatgptLeft}px`;
  564. }
  565. });
  566.  
  567. const resizeObserver = new ResizeObserver(entries => {
  568. for (let entry of entries) {
  569. const target = entry.target;
  570. // 只在容器可見時儲存尺寸,避免儲存無效值
  571. if (target.style.display !== 'none') {
  572. if (target === container) {
  573. GM_setValue('containerWidth', target.offsetWidth);
  574. GM_setValue('containerHeight', target.offsetHeight);
  575. console.log(`Saved containerWidth: ${target.offsetWidth}, containerHeight: ${target.offsetHeight}, display: ${target.style.display}`);
  576. } else if (target === grokResponseContainer) {
  577. GM_setValue('grokResponseWidth', target.offsetWidth);
  578. GM_setValue('grokResponseHeight', target.offsetHeight);
  579. console.log(`Saved grokResponseWidth: ${target.offsetWidth}, grokResponseHeight: ${target.offsetHeight}, display: ${target.style.display}`);
  580. } else if (target === chatgptResponseContainer) {
  581. GM_setValue('chatgptResponseWidth', target.offsetWidth);
  582. GM_setValue('chatgptResponseHeight', target.offsetHeight);
  583. console.log(`Saved chatgptResponseWidth: ${target.offsetWidth}, chatgptResponseHeight: ${target.offsetHeight}, display: ${target.style.display}`);
  584. }
  585. } else {
  586. console.log(`Skipped saving for ${target.className} - display: ${target.style.display}, width: ${target.offsetWidth}, height: ${target.offsetHeight}`);
  587. }
  588. }
  589. });
  590.  
  591. resizeObserver.observe(container);
  592. resizeObserver.observe(grokResponseContainer);
  593. resizeObserver.observe(chatgptResponseContainer);
  594.  
  595. function showContainer() {
  596. bubble.style.display = 'none';
  597. container.style.display = 'block';
  598. content.style.display = 'flex';
  599.  
  600. // 每次顯示時,重新應用保存的尺寸和位置
  601. const savedWidth = GM_getValue('containerWidth', 300); // 純數值,無單位
  602. const savedHeight = GM_getValue('containerHeight', 300); // 純數值,無單位
  603. const savedTop = GM_getValue('containerTop', '50px');
  604. const savedLeft = GM_getValue('containerLeft', '50px');
  605.  
  606. container.style.width = `${Math.max(savedWidth, 200)}px`;
  607. container.style.height = `${Math.max(savedHeight, 200)}px`;
  608. container.style.top = savedTop;
  609. container.style.left = savedLeft;
  610.  
  611. // 確保位置不超出視窗
  612. const containerWidth = container.offsetWidth;
  613. const containerHeight = container.offsetHeight;
  614. let containerTop = parseInt(savedTop);
  615. let containerLeft = parseInt(savedLeft);
  616. containerTop = Math.max(0, Math.min(containerTop, window.innerHeight - containerHeight));
  617. containerLeft = Math.max(0, Math.min(containerLeft, window.innerWidth - containerWidth));
  618. container.style.top = `${containerTop}px`;
  619. container.style.left = `${containerLeft}px`;
  620.  
  621. console.log(`Show container - width: ${container.style.width}, height: ${container.style.height}, top: ${container.style.top}, left: ${container.style.left}`);
  622. }
  623. }
  624.  
  625. // 在新標籤頁中打開AI
  626. function openAIInNewTab(ai, question) {
  627. const url = `${ai.url}${ai.id === 'gemini' ? '?q=' : '?q='}${encodeURIComponent(question)}`;
  628. GM_openInTab(url, {
  629. active: false,
  630. insert: true,
  631. setParent: true
  632. });
  633. }
  634.  
  635. // 處理 Gemini 頁面
  636. function handleGeminiPage() {
  637. if (window.location.hostname === 'gemini.google.com' && window.location.search.includes('q=')) {
  638. const query = new URLSearchParams(window.location.search).get('q');
  639. if (query) {
  640. function setTextAndSendAfterDelay(string) {
  641. const richTextarea = document.querySelector('rich-textarea.text-input-field_textarea');
  642. if (!richTextarea) return false;
  643. const firstDiv = richTextarea.querySelector('div');
  644. if (!firstDiv) return false;
  645. firstDiv.innerText = string;
  646. const event = new Event('input', { bubbles: true });
  647. firstDiv.dispatchEvent(event);
  648. setTimeout(() => {
  649. const sendButton = document.querySelector('.send-button');
  650. if (sendButton) sendButton.click();
  651. }, 1000);
  652. return true;
  653. }
  654.  
  655. const waitForElement = (selector, maxAttempts = 30) => {
  656. return new Promise((resolve) => {
  657. let attempts = 0;
  658. const interval = setInterval(() => {
  659. const element = document.querySelector(selector);
  660. attempts++;
  661. if (element || attempts >= maxAttempts) {
  662. clearInterval(interval);
  663. resolve(element);
  664. }
  665. }, 500);
  666. });
  667. };
  668.  
  669. const trySetQuestion = async () => {
  670. await waitForElement('rich-textarea.text-input-field_textarea');
  671. setTextAndSendAfterDelay(decodeURIComponent(query));
  672. };
  673.  
  674. trySetQuestion();
  675. window.history.replaceState({}, document.title, '/app');
  676. }
  677. }
  678. }
  679.  
  680. // 處理 Grok 頁面
  681. function handleGrokPage() {
  682. if (window.location.hostname === 'grok.com') {
  683. let lastContent = '';
  684.  
  685. setInterval(() => {
  686. const messageBubbles = document.querySelectorAll('div.message-bubble:not(.processed)');
  687. let currentContent = '';
  688.  
  689. messageBubbles.forEach(div => {
  690. const content = div.innerHTML.trim();
  691. if (content) {
  692. const nextSibling = div.nextElementSibling;
  693. let buttonCount = 0;
  694. if (nextSibling) {
  695. buttonCount = nextSibling.querySelectorAll('button').length;
  696. }
  697. if (buttonCount > 2) {
  698. currentContent += content + '\n';
  699. div.classList.add('processed');
  700. }
  701. }
  702. });
  703.  
  704. if (currentContent && currentContent !== lastContent) {
  705. lastContent = currentContent.trim();
  706. console.log('儲存 Grok 回應:', lastContent);
  707. GM_setValue('grokResponse', lastContent);
  708. }
  709. }, 500);
  710. }
  711. }
  712.  
  713. // 處理 ChatGPT 頁面
  714. function handleChatGPTPage() {
  715. if (window.location.hostname === 'chatgpt.com') {
  716. let lastContent = '';
  717.  
  718. setInterval(() => {
  719. const assistantMessages = document.querySelectorAll('div[data-message-author-role="assistant"]:not(.processed)');
  720. let currentContent = '';
  721.  
  722. assistantMessages.forEach(div => {
  723. const innerDiv = div.querySelector('div.markdown.prose.w-full.break-words');
  724. const content = div.innerHTML.trim();
  725. if (content && innerDiv && !innerDiv.classList.contains('result-streaming') && !innerDiv.classList.contains('result-thinking')) {
  726. currentContent += content + '\n';
  727. div.classList.add('processed');
  728. }
  729. });
  730.  
  731. if (currentContent && currentContent !== lastContent) {
  732. lastContent = currentContent.trim();
  733. console.log('儲存 ChatGPT 回應:', lastContent);
  734. GM_setValue('chatgptResponse', lastContent);
  735. }
  736. }, 500);
  737. }
  738. }
  739.  
  740. // 檢查並顯示 Grok 回應
  741. function checkGrokResponse(grokResponseContainer, grokResponseDiv) {
  742. if (!grokResponseDiv) return;
  743.  
  744. setInterval(() => {
  745. const response = GM_getValue('grokResponse', '');
  746. if (response && grokResponseContainer.style.display === 'block') {
  747. console.log('更新 Grok UI:', response);
  748. const newResponse = document.createElement('p');
  749. newResponse.innerHTML = response;
  750. while (grokResponseDiv.firstChild) {
  751. grokResponseDiv.removeChild(grokResponseDiv.firstChild);
  752. }
  753. grokResponseDiv.appendChild(newResponse);
  754. GM_setValue('grokResponse', '');
  755. }
  756. }, 1000);
  757. }
  758.  
  759. // 檢查並顯示 ChatGPT 回應
  760. function checkChatGPTResponse(chatgptResponseContainer, chatgptResponseDiv) {
  761. if (!chatgptResponseDiv) return;
  762.  
  763. setInterval(() => {
  764. const response = GM_getValue('chatgptResponse', '');
  765. if (response && chatgptResponseContainer.style.display === 'block') {
  766. console.log('更新 ChatGPT UI:', response);
  767. const newResponse = document.createElement('p');
  768. newResponse.innerHTML = response;
  769. while (chatgptResponseDiv.firstChild) {
  770. chatgptResponseDiv.removeChild(chatgptResponseDiv.firstChild);
  771. }
  772. chatgptResponseDiv.appendChild(newResponse);
  773. GM_setValue('chatgptResponse', '');
  774. }
  775. }, 1000);
  776. }
  777.  
  778. // 啟動腳本
  779. function initialize() {
  780. const { bubble, container, grokResponseContainer, grokResponseDiv, chatgptResponseContainer, chatgptResponseDiv, header, grokHeader, chatgptHeader, grokClearButton, chatgptClearButton } = createUI();
  781. initializeEvents(bubble, container, grokResponseContainer, grokResponseDiv, chatgptResponseContainer, chatgptResponseDiv, header, grokHeader, chatgptHeader, grokClearButton, chatgptClearButton);
  782. handleGeminiPage();
  783. handleGrokPage();
  784. handleChatGPTPage();
  785. checkGrokResponse(grokResponseContainer, grokResponseDiv);
  786. checkChatGPTResponse(chatgptResponseContainer, chatgptResponseDiv);
  787. }
  788.  
  789. if (document.readyState === 'loading') {
  790. document.addEventListener('DOMContentLoaded', initialize);
  791. } else {
  792. initialize();
  793. }
  794. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址