Google Search Enhanced via Gemini AI

Google Search with AI-Generated Annotation via Gemini

  1. // ==UserScript==
  2. // @name Google Search Enhanced via Gemini AI
  3. // @description Google Search with AI-Generated Annotation via Gemini
  4. // @version 0.9
  5. // @license MIT
  6. // @namespace djshigel
  7. // @match https://www.google.com/search*
  8. // @run-at document-end
  9. // @grant GM.setValue
  10. // @grant GM.getValue
  11. // ==/UserScript==
  12.  
  13. (async () => {
  14. let GEMINI_API_KEY = await GM.getValue("GEMINI_API_KEY") ;
  15. if (!GEMINI_API_KEY || !Object.keys(GEMINI_API_KEY).length) {
  16. GEMINI_API_KEY = window.prompt('Get Generative Language Client API key from Google AI Studio\nhttps://ai.google.dev/aistudio', '');
  17. await GM.setValue("GEMINI_API_KEY", GEMINI_API_KEY);
  18. }
  19. const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-04-17:generateContent?key=${GEMINI_API_KEY}`;
  20. const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
  21.  
  22. // ########## Results ##########
  23. const processArticle = async (article, title, url) => {
  24. try {
  25. document.querySelector('#gemini-ticker').style.opacity = '1';
  26. const response = true || (new URL(location.href).searchParams.get('hl') == 'ja') ?
  27. await fetch(apiUrl, {
  28. method: 'POST',
  29. headers: { 'Content-Type': 'application/json' },
  30. body: JSON.stringify({
  31. contents: [{
  32. parts: [{
  33. text: `${document.querySelector('textarea').value}について調べています。
  34. URLに対し、次の手順に従ってステップバイステップで実行してください。
  35. 1 URLにアクセス出来なかった場合、結果を出力しない
  36. 2 200字程度に学者のように具体的に要約
  37. 3 結果のみを出力
  38. ${title}のURL: ${url}`
  39. }],
  40. }]
  41. }),
  42. }):
  43. await fetch(apiUrl, {
  44. method: 'POST',
  45. headers: { 'Content-Type': 'application/json' },
  46. body: JSON.stringify({
  47. contents: [{
  48. parts: [{
  49. text: `I'm searching about ${document.querySelector('textarea').value}.
  50. Follow the steps below to execute step by step for each URL.
  51. 1 If the URL cannot be accessed, do not output the results
  52. 2 Summarize in 400 characters or so like an academic
  53. 3 Output only the results
  54. ${title} with URL: ${url}`
  55. }],
  56. }]
  57. }),
  58. });
  59.  
  60. if (!response.ok) throw new Error('Network response was not ok');
  61.  
  62. const reader = response.body.getReader();
  63. let result = '', done = false, decoder = new TextDecoder();
  64. while (!done) {
  65. const { value, done: doneReading } = await reader.read();
  66. done = doneReading;
  67. if (value) result += decoder.decode(value, { stream: true });
  68. }
  69. result += decoder.decode();
  70.  
  71. const data = JSON.parse(result);
  72. let summary = (data.candidates[0]?.content?.parts[0]?.text || '').replace(/\*\*/g, '').replace(/##/g, '');
  73. console.log(`summary: ${summary}`);
  74.  
  75. let targetElement = article.parentElement.parentElement.parentElement.parentElement
  76. .nextSibling?.querySelectorAll('div>span')[1] ||
  77. article.parentElement.parentElement.parentElement.parentElement
  78. .nextSibling?.querySelectorAll('div>span')[0];
  79. if (!targetElement) return;
  80. targetElement.parentElement.setAttribute('style', '-webkit-line-clamp: 30');
  81. article.classList.add('gemini-annotated');
  82.  
  83. let displayText = '✦ ';
  84. const chunkSize = 20;
  85. targetElement.textContent = displayText;
  86. for (let i = 0; i < summary.length; i += chunkSize) {
  87. while (document.querySelector('#rso').classList.contains('hover')) {
  88. await delay(100);
  89. }
  90. const chunk = summary.slice(i, i + chunkSize);
  91. const chunkSpan = document.createElement('span');
  92. chunkSpan.style.opacity = '0';
  93. chunkSpan.textContent = chunk;
  94. targetElement.appendChild(chunkSpan);
  95. await delay(100);
  96. chunkSpan.style.transition = 'opacity 1s ease-in-out';
  97. chunkSpan.style.opacity = '1';
  98. }
  99.  
  100. } catch (error) {
  101. document.querySelector('#gemini-ticker').style.opacity = '0';
  102. await delay(5000);
  103. console.error('Error:', error);
  104. }
  105. };
  106. const throttledProcessArticle = async (article, title, url, interval) => {
  107. await delay(interval);
  108. return processArticle(article, title, url);
  109. };
  110.  
  111. // ########## Ticker ##########
  112. const insertTickerElement = () => {
  113. const ticker = document.createElement('div');
  114. ticker.id = 'gemini-ticker';
  115. ticker.style.position = 'fixed';
  116. ticker.style.right = '20px';
  117. ticker.style.bottom = '10px';
  118. ticker.style.fontSize = '1.5em';
  119. ticker.style.color = '#77777777';
  120. ticker.style.transition = 'opacity .3s';
  121. ticker.style.zIndex = '100';
  122. ticker.innerHTML = '✦';
  123. document.querySelector('body').appendChild(ticker);
  124. };
  125.  
  126. // ########## Main ##########
  127. await delay(1000);
  128. insertTickerElement();
  129. for (let j = 0; j < 30 ; j++) {
  130. console.log(`######## attempt: ${j+1} ########`)
  131. const articles = Array.from(document.querySelectorAll('#rso>div'))
  132. .map(result=>result.querySelector('span>a:not(.gemini-annotated)'));
  133. if (articles.length == 0) break;
  134.  
  135. document.querySelector('#rso').addEventListener('mouseover', ()=>{
  136. document.querySelector('#rso').classList.add('hover')
  137. });
  138. document.querySelector('#rso').addEventListener('mouseout', ()=>{
  139. document.querySelector('#rso').classList.remove('hover')
  140. });
  141.  
  142. const promises = articles.map((targetLink, i) => {
  143. if (!targetLink) return Promise.resolve();
  144. const href = targetLink.getAttribute('href');
  145. const title = targetLink.querySelector('h3').textContent;
  146. console.log(`title: ${title}`);
  147. console.log(`url: ${href}`);
  148. if (!href) return Promise.resolve();
  149.  
  150. return throttledProcessArticle(targetLink, title, href, i * 1000);
  151. });
  152.  
  153. await Promise.all(promises);
  154.  
  155. document.querySelector('#gemini-ticker').style.opacity = '0';
  156. }
  157. document.querySelector('#gemini-ticker').style.opacity = '0';
  158. })();

QingJ © 2025

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