Google Search Enhanced via Gemini AI

Google Search with AI-Generated Annotation via Gemini

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

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