Google Search Enhanced via Gemini AI

Google Search with AI-Generated Annotation via Gemini

当前为 2024-08-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Google Search Enhanced via Gemini AI
  3. // @description Google Search with AI-Generated Annotation via Gemini
  4. // @version 0.6
  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-1.5-flash-latest: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 = await fetch(apiUrl, {
  27. method: 'POST',
  28. headers: { 'Content-Type': 'application/json' },
  29. body: JSON.stringify({
  30. contents: [{
  31. parts: [{
  32. text: `I'm searching about ${document.querySelector('textarea').value}.
  33. Follow the steps below to execute step by step for each URL.
  34. 1 If the URL cannot be accessed, do not output the results
  35. 2 Summarize in 200 characters or so like an academic
  36. 3 Output only the results
  37. ${title} with URL: ${url}`
  38. }],
  39. }]
  40. }),
  41. });
  42.  
  43. if (!response.ok) throw new Error('Network response was not ok');
  44.  
  45. const reader = response.body.getReader();
  46. let result = '', done = false, decoder = new TextDecoder();
  47. while (!done) {
  48. const { value, done: doneReading } = await reader.read();
  49. done = doneReading;
  50. if (value) result += decoder.decode(value, { stream: true });
  51. }
  52. result += decoder.decode();
  53.  
  54. const data = JSON.parse(result);
  55. let summary = (data.candidates[0]?.content?.parts[0]?.text || '').replace(/\*\*/g, '').replace(/##/g, '');
  56. console.log(`summary: ${summary}`);
  57.  
  58. let targetElement = article.parentElement.parentElement.parentElement.parentElement
  59. .nextSibling?.querySelectorAll('div>span')[1] ||
  60. article.parentElement.parentElement.parentElement.parentElement
  61. .nextSibling?.querySelectorAll('div>span')[0];
  62. if (!targetElement) return;
  63. targetElement.parentElement.setAttribute('style', '-webkit-line-clamp: 30');
  64. article.classList.add('gemini-annotated');
  65.  
  66. let displayText = '✦ ';
  67. for (const char of summary) {
  68. document.querySelector('#gemini-ticker').style.opacity = '1';
  69. while (document.querySelector('#rso').classList.contains('hover')) {
  70. await delay(100);
  71. }
  72. displayText += char + '●';
  73. targetElement.textContent = displayText;
  74. await delay(2);
  75. displayText = displayText.slice(0, -1);
  76. document.querySelector('#gemini-ticker').style.opacity = '0';
  77. }
  78. targetElement.textContent = displayText;
  79. } catch (error) {
  80. document.querySelector('#gemini-ticker').style.opacity = '0';
  81. await delay(5000);
  82. console.error('Error:', error);
  83. }
  84. };
  85. const throttledProcessArticle = async (article, title, url, interval) => {
  86. await delay(interval);
  87. return processArticle(article, title, url);
  88. };
  89.  
  90. // ########## Ticker ##########
  91. const insertTickerElement = () => {
  92. const ticker = document.createElement('div');
  93. ticker.id = 'gemini-ticker';
  94. ticker.style.position = 'fixed';
  95. ticker.style.right = '20px';
  96. ticker.style.bottom = '10px';
  97. ticker.style.fontSize = '1.5em';
  98. ticker.style.color = '#77777777';
  99. ticker.style.transition = 'opacity .3s';
  100. ticker.style.zIndex = '100';
  101. ticker.innerHTML = '✦';
  102. document.querySelector('body').appendChild(ticker);
  103. };
  104.  
  105. // ########## Main ##########
  106. await delay(1000);
  107. insertTickerElement();
  108. for (let j = 0; j < 30 ; j++) {
  109. console.log(`######## attempt: ${j+1} ########`)
  110. const articles = Array.from(document.querySelectorAll('#rso>div'))
  111. .map(result=>result.querySelector('span>a:not(.gemini-annotated)'));
  112. if (articles.length == 0) break;
  113.  
  114. document.querySelector('#rso').addEventListener('mouseover', ()=>{
  115. document.querySelector('#rso').classList.add('hover')
  116. });
  117. document.querySelector('#rso').addEventListener('mouseout', ()=>{
  118. document.querySelector('#rso').classList.remove('hover')
  119. });
  120.  
  121. const promises = articles.map((targetLink, i) => {
  122. if (!targetLink) return Promise.resolve();
  123. const href = targetLink.getAttribute('href');
  124. const title = targetLink.querySelector('h3').textContent;
  125. console.log(`title: ${title}`);
  126. console.log(`url: ${href}`);
  127. if (!href) return Promise.resolve();
  128.  
  129. return throttledProcessArticle(targetLink, title, href, i * 1000);
  130. });
  131.  
  132. await Promise.all(promises);
  133.  
  134. document.querySelector('#gemini-ticker').style.opacity = '0';
  135. }
  136. document.querySelector('#gemini-ticker').style.opacity = '0';
  137. })();