AIGPT Everywhere

Mini A.I. floating menu that can define words, answer questions, translate, and much more in a single click and with your custom prompts. Includes useful click to search on Google and copy selected text buttons, along with Rocker+Mouse Gestures and Units+Currency+Time zone Converters, all features can be easily modified or disabled.

Instalează acest script?
Script sugerat de autor

Poate îți va plăcea șiPrecise Time Converter on Google.

Instalează acest script
  1. // ==UserScript==
  2. // @name AIGPT Everywhere
  3. // @namespace OperaBrowserGestures
  4. // @description Mini A.I. floating menu that can define words, answer questions, translate, and much more in a single click and with your custom prompts. Includes useful click to search on Google and copy selected text buttons, along with Rocker+Mouse Gestures and Units+Currency+Time zone Converters, all features can be easily modified or disabled.
  5. // @version 75
  6. // @author hacker09
  7. // @include *
  8. // @exclude https://accounts.google.com/v3/signin/*
  9. // @icon https://i.imgur.com/8iw8GOm.png
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_getResourceText
  12. // @grant GM.xmlHttpRequest
  13. // @grant GM_setClipboard
  14. // @grant GM_deleteValue
  15. // @grant GM_openInTab
  16. // @grant window.close
  17. // @run-at document-end
  18. // @grant GM_setValue
  19. // @grant GM_getValue
  20. // @connect google.com
  21. // @connect generativelanguage.googleapis.com
  22. // @resource AIMenuContent https://hacker09.glitch.me/AIMenu.html
  23. // @require https://update.greatest.deepsurf.us/scripts/506699/marked.js
  24. // @require https://update.greatest.deepsurf.us/scripts/519002/Units%20Converter.js
  25. // ==/UserScript==
  26.  
  27. /* jshint esversion: 11 */
  28.  
  29. const toHTML = html => window.trustedTypes?.createPolicy('BypassTT', { createHTML: HTML => HTML })?.createHTML(html) || html; //Bypass Trusted Types API + create safe HTML for chromium browsers
  30.  
  31. if ((location.href === 'https://aistudio.google.com/app/apikey' && document.querySelector(".apikey-link") !== null) && GM_getValue("APIKey") === undefined || GM_getValue("APIKey") === null || GM_getValue("APIKey") === '') { //Set up the API Key
  32. window.onload = setTimeout(function() {
  33. document.querySelectorAll(".apikey-link")[1].click(); //Click on the API Key
  34. setTimeout(function() {
  35. GM_setValue("APIKey", document.querySelector(".apikey-text").innerText); //Store the API Key
  36. alert((GM_getValue("APIKey") !== undefined && GM_getValue("APIKey") !== null && GM_getValue("APIKey") !== '') ? 'API Key automatically added!' : 'Failed to automatically add API Key!');
  37. }, 500);
  38. }, 1000);
  39. }
  40.  
  41. if (GM_getValue("SearchHighlight") === undefined) { //Set up everything on the first run
  42. GM_setValue("SearchHighlight", true);
  43. GM_setValue("MouseGestures", true);
  44. GM_setValue("TimeConverter", true);
  45. GM_setValue("UnitsConverter", true);
  46. GM_setValue("CurrenciesConverter", true);
  47. }
  48.  
  49. function ToggleFeature(feature)
  50. {
  51. GM_setValue(feature, GM_getValue(feature) === true ? false : true);
  52. location.reload();
  53. }
  54.  
  55. //Mouse Gestures__________________________________________________________________________________________________________________________________________________________________________________
  56. GM_registerMenuCommand(`${GM_getValue("MouseGestures") ? "Disable" : "Enable"} Mouse Gestures`, function() { ToggleFeature("MouseGestures"); });
  57.  
  58. if (GM_getValue("MouseGestures") === true) //If the MouseGestures is enabled
  59. {
  60. var link;
  61.  
  62. document.querySelectorAll('a').forEach(el => {
  63. el.addEventListener('mouseover', function() {
  64. link = this.href; //Store the hovered link
  65. });
  66.  
  67. el.addEventListener('mouseout', () => {
  68. const previousLink = link; //Save the hovered link
  69. setTimeout(() => {
  70. if (previousLink === link) { //Check if the same link is still hovered
  71. link = 'about:newtab'; //Open a new tab
  72. }
  73. }, 200);
  74. });
  75. });
  76.  
  77. const funcs = { //Store the MouseGestures functions
  78.  
  79. 'DL': function() { //Detect the Down+Left movement
  80. GM_openInTab(location.href, { incognito: true, });
  81. window.top.close();
  82. },
  83.  
  84. 'L': function() { //Detect the Left movement
  85. window.history.back();
  86. },
  87.  
  88. 'R': function() { //Detect the Right movement
  89. window.history.forward();
  90. },
  91.  
  92. 'D': function(e) { //Detect the Down movement
  93. if (e.shiftKey) {
  94. open(link, '_blank', 'height=' + screen.height + ',width=' + screen.width);
  95. }
  96. else {
  97. GM_openInTab(link, { active: true, insert: true, setParent: true });
  98. }
  99. },
  100.  
  101. 'UD': function() { //Detect the Up+Down movement
  102. location.reload();
  103. },
  104.  
  105. 'DR': function(e) { //Detect the Down+Right movement
  106. top.close();
  107. e.preventDefault();
  108. e.stopPropagation();
  109. },
  110.  
  111. 'DU': function() { //Detect the Down+Up movement
  112. GM_openInTab(link, { active: false, insert: true, setParent: true });
  113. }
  114. };
  115.  
  116. //Math codes to track the mouse movement gestures
  117. var x, y, path;
  118. const TOLERANCE = 3;
  119. const SENSITIVITY = 3;
  120. const s = 1 << ((7 - SENSITIVITY) << 1);
  121. const t1 = Math.tan(0.15708 * TOLERANCE),t2 = 1 / t1;
  122.  
  123. const tracer = function(e) {
  124. var cx = e.clientX, cy = e.clientY, deltaX = cx - x, deltaY = cy - y, distance = deltaX * deltaX + deltaY * deltaY;
  125. if (distance > s) {
  126. var slope = Math.abs(deltaY / deltaX), direction = '';
  127. if (slope > t1) {
  128. direction = deltaY > 0 ? 'D' : 'U';
  129. } else if (slope <= t2) {
  130. direction = deltaX > 0 ? 'R' : 'L';
  131. }
  132. if (path.charAt(path.length - 1) !== direction) {
  133. path += direction;
  134. }
  135. x = cx;
  136. y = cy;
  137. }
  138. };
  139.  
  140. window.addEventListener('mousedown', function(e) {
  141. if (e.which === 3) {
  142. x = e.clientX;
  143. y = e.clientY;
  144. path = "";
  145. window.addEventListener('mousemove', tracer, false); //Detect the mouse position
  146. }
  147. }, false);
  148.  
  149. window.addEventListener('contextmenu', function(e) { //When the right click BTN is released
  150. window.removeEventListener('mousemove', tracer, false); //Track the mouse movements
  151. if (path !== "") {
  152. e.preventDefault();
  153. if (funcs.hasOwnProperty(path)) {
  154. funcs[path](e);
  155. }
  156. }
  157. }, false);
  158. }
  159.  
  160. //Rocker Mouse Gestures___________________________________________________________________________________________________________________________________________________________________________
  161. GM_registerMenuCommand(`${GM_getValue("RockerMouseGestures") ? "Disable" : "Enable"} Rocker Gestures`, function() { ToggleFeature("RockerMouseGestures"); });
  162.  
  163. if (GM_getValue("RockerMouseGestures") === true) //If the RockerMouseGestures is enabled
  164. {
  165. const mouseState = { 0: false, 2: false }; //0: Left, 2: Right
  166.  
  167. window.addEventListener("mouseup", function(e) {
  168. mouseState[e.button] = false; //Update the state for the released button
  169.  
  170. if (mouseState[0] && !mouseState[2]) { //Left clicked, Right released
  171. history.back();
  172. } else if (mouseState[2] && !mouseState[0]) { //Right clicked, Left released
  173. history.forward();
  174. }
  175. }, false);
  176.  
  177. window.addEventListener("mousedown", function(e) {
  178. mouseState[e.button] = true; //Update the state for the pressed button
  179. }, false);
  180. }
  181.  
  182. //Search HighLight + Time + Currencies + Units Converters + Search HighLight + AI menus___________________________________________________________________________________________________________
  183. GM_registerMenuCommand(`${GM_getValue("SearchHighlight") ? "Disable" : "Enable"} Search Highlight`, function() { ToggleFeature("SearchHighlight"); });
  184.  
  185. if (GM_getValue("SearchHighlight") === true) //If the SearchHighlight is enabled
  186. {
  187. var SelectedText;
  188. const Links = new RegExp(/\.org|\.ly|\.net|\.co|\.tv|\.me|\.biz|\.club|\.site|\.br|\.gov|\.io|\.ai|\.jp|\.edu|\.au|\.in|\.it|\.ca|\.mx|\.fr|\.tw|\.il|\.uk|\.zoom\.us|\.youtu\.be|\.com|\.us|\.de|\.cn|\.ru|\.es|\.ch|\.nl|\.se|\.no|\.dk|\.fi|\.pl|\.tr|\.xyz|\.za/i);
  189.  
  190. document.body.addEventListener('mouseup', async function() { //When the user releases the mouse click after selecting something
  191. HtmlMenu.style.display = 'block'; //Display the container div
  192. SelectedText = getSelection().toString().trim(); //Store the selected text
  193. shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = ''; //Remove the previous Units/Currency text
  194.  
  195. function ShowConvertion(UnitORCurrency, Type, Result) {
  196. shadowRoot.querySelector("#SearchBTN span")?.remove(); //Return previous HTML
  197. shadowRoot.querySelector("#SearchBTN").innerHTML = toHTML('<span class="GreyBar">│ </span>' + shadowRoot.querySelector("#SearchBTN").innerHTML);
  198.  
  199. if (UnitORCurrency === 'Currencies') {
  200. const hasSymbol = SelectedText.match(Currencies)[2].match(CurrencySymbols) !== null;
  201. const currencyFormat = Intl.NumberFormat(navigator.language, {
  202. style: 'currency',
  203. currency: GM_getValue("YourLocalCurrency")
  204. }).format(Result);
  205.  
  206. const displayText = hasSymbol ? (Type + ' 🠂 ' + currencyFormat) : currencyFormat;
  207. shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = toHTML(displayText);
  208. }
  209. else
  210. {
  211. shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = toHTML(UnitORCurrency === 'Units' ? `${Result} ${Type}` : Result); //Show the converted time results
  212. }
  213.  
  214. setTimeout(() => { //Wait for Units to show up to get the right offsetWidth
  215. const offsetWidth = shadowRoot.querySelector("#ShowCurrencyORUnits").offsetWidth; //Store the current menu size
  216. shadowRoot.querySelector("#ShowCurrencyORUnits").onmouseover = function() { //When the mouse hovers the unit/currency
  217. shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = toHTML(`Copy`);
  218. shadowRoot.querySelector("#ShowCurrencyORUnits").style.display = 'inline-flex';
  219. shadowRoot.querySelector("#ShowCurrencyORUnits").style.width = `${offsetWidth}px`; //Maintain the aspect ratio
  220. };
  221. }, 0);
  222.  
  223. const htmlcode = shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML; //Save the converted unit/currency value
  224. shadowRoot.querySelector("#ShowCurrencyORUnits").onmouseout = function() { //When the mouse leaves the unit/currency
  225. shadowRoot.querySelector("#ShowCurrencyORUnits").style.width = ''; //Return the original aspect ratio
  226. shadowRoot.querySelector("#ShowCurrencyORUnits").style.display = ''; //Return the original aspect ratio
  227. shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = toHTML(htmlcode); //Return the previous html
  228. };
  229.  
  230. shadowRoot.querySelector("#ShowCurrencyORUnits").onclick = function() { //When the unit/currency is clicked
  231. UnitORCurrency.match(/Units|Time/) ? GM_setClipboard(`${Result} ${Type}`) : GM_setClipboard(Intl.NumberFormat(navigator.language, { style: 'currency', currency: GM_getValue("YourLocalCurrency") }).format(Result));
  232. };
  233. }
  234.  
  235. function Get(url) { //Get the final converted time/currency value
  236. return new Promise(resolve => GM.xmlHttpRequest({
  237. method: "GET",
  238. url: url,
  239. onload: response => resolve(new DOMParser().parseFromString(response.responseText, 'text/html'))
  240. }));
  241. }
  242.  
  243. //Time Converter______________________________________________________________________________________________________________________________________________________________________________
  244. GM_registerMenuCommand(`${GM_getValue("TimeConverter") ? "Disable" : "Enable"} Time Converter`, function() { ToggleFeature("TimeConverter"); });
  245. const time = new RegExp(/^[ \t\xA0]*(?=.*?(\d{1,2}:\d{2}(?::\d{2})?\s?(?:[aApP]\.?[mM]\.?)?)|\d{1,2}(?::\d{2}(?::\d{2})?)?\s?(?:[aApP]\.?[mM]\.?)?)(?=.*?(PST|PDT|MST|MDT|CST|CDT|EST|EDT|AST|ADT|NST|NDT|GMT|BST|MET|CET|CEST|EET|EEST|WET|WEST|JST|KST|IST|MSK|UTC|PT))(?:\1[ \t\xA0]*\2|\2[ \t\xA0]*\1)[ \t\xA0]*$/i);
  246.  
  247. if (GM_getValue("TimeConverter") === true && SelectedText.match(time) !== null) //If the TimeConverter is enabled and if the selected text is a time
  248. {
  249. const timeResponse = await Get(`https://www.google.com/search?q=${SelectedText.match(time)[0].replace("WET", "Western European Time")} to local time`);
  250. const ConvertedTime = timeResponse.querySelector(".aCAqKc")?.innerText;
  251. const Secs = SelectedText.match(time)[0].match(/(?:\d{1,2}:\d{2}(:\d{2})\s?[ap]\.?m)/i)?.[1] || '';
  252. ConvertedTime && ShowConvertion('Time', '', ConvertedTime.replace(/(\d{1,2}):(\d{2})\s?([pP][mM])/, (_, h, m) => `${(h % 12 + 12) % 24}:${m}`).match(/[\d:]+/g)[0] + Secs); //Convert PM to 24-hour format if we got the final time conversion value
  253. }
  254.  
  255. //Currencies Converter________________________________________________________________________________________________________________________________________________________________________
  256. GM_registerMenuCommand(`${GM_getValue("CurrenciesConverter") ? "Disable" : "Enable"} Currencies Converter`, function() { ToggleFeature("CurrenciesConverter"); });
  257. const CurrencySymbols = new RegExp(/AU\$|HK\$|US\$|\$US|R\$|\$|¥|€|Rp|Kč|kr(?!w)|zł|£|฿|₩|лв|₪|円|₱|₽|руб|lei|Fr|krw|RON|TRY|₿|Br|₾|₴|₸|₺/i);
  258. const Currencies = new RegExp(/^[ \t\xA0]*\$?(?=.*?(\d+(?:.*\d+)?))(?=(?:\1[ \t\xA0]*)?(Dólares|dolares|dólares|dollars?|AU\$?D?|BGN|BRL|BCH|BTC|BYN|CAD|CHF|Fr|CNY|CZK|DKK|EUR|EGP|ETH|GBP|GEL|HKD|HUF|IDR|ILS|INR|JPY|LTC|KRW|MXN|NOK|NZD|PHP|PLN|RON|RUB|SEK|SGD|THB|TRY|USD|UAH|ZAR|KZT|YTL|\$|R\$|HK\$|US\$|\$US|¥|€|Rp|Kč|kr|krw|zł|£|฿|₩|лв|₪|円|₱|₽|руб|lei|Kč|₿|Br|₾|₴|₸|₺))(?:\1[ \t\xA0]*\2|\2[ \t\xA0]*\1)[ \t\xA0]*$/i); //https://regex101.com/r/6vTbtv/20 Davidebyzero
  259.  
  260. if (GM_getValue("CurrenciesConverter") === true && SelectedText.match(Currencies) !== null) { //If Currencies Converter is enabled and if the selected text is a currency
  261. if (GM_getValue("YourLocalCurrency") === undefined) {
  262. const UserInput = prompt('Write your local currency.\nThe script will always use your local currency to make exchange-rate conversions.\n\n*Currency input examples:\nBRL\nCAD\nUSD\netc...\n\n*Press OK');
  263. GM_setValue("YourLocalCurrency", UserInput);
  264. }
  265. const currencyMap = { 'AU$': 'AUD', '$': 'USD', 'us$': 'USD', '$us': 'USD', 'r$': 'BRL', 'hk$': 'HKD', '¥': 'JPY', '€': 'EUR', 'rp': 'IDR', 'kč': 'CZK', 'kr': 'NOK', 'zł': 'PLN', '£': 'GBP', '฿': 'THB', '₩': 'KRW', 'лв': 'BGN', '₪': 'ILS', '円': 'JPY', '₱': 'PHP', '₽': 'RUB', 'руб': 'RUB', 'lei': 'RON', 'ron': 'Romanian Leu', 'krw': 'KRW', 'fr': 'CHF', '₿': 'BTC', 'Br': 'BYN', '₾': 'GEL', '₴': 'UAH', '₸': 'KZT', '₺': 'YTL', 'try': 'Turkish Lira' };
  266. const CurrencySymbol = currencyMap[SelectedText.match(CurrencySymbols)?.[0].toUpperCase()] || SelectedText.match(Currencies)[2]; //Store the currency symbol
  267. const currencyResponse = await Get(`https://www.google.com/search?q=${SelectedText.match(Currencies)[1]} ${CurrencySymbol} in ${GM_getValue("YourLocalCurrency")}`);
  268. const FinalCurrency = parseFloat(currencyResponse.querySelector(".SwHCTb, .pclqee").innerText.split(' ')[0].replaceAll(',', '')); //Store the FinalCurrency and erase all commas
  269. ShowConvertion('Currencies', CurrencySymbol, FinalCurrency);
  270. }
  271.  
  272. //Units Converter_____________________________________________________________________________________________________________________________________________________________________________
  273. GM_registerMenuCommand(`${GM_getValue("UnitsConverter") ? "Disable" : "Enable"} Units Converter`, function() { ToggleFeature("UnitsConverter"); });
  274. const Units = new RegExp(/^[ \t\xA0]*(-?\d+(?:[., ]\d+)?)(?:[ \t\xA0]*x[ \t\xA0]*(-?\d+(?:[., ]\d+)?))?[ \t\xA0]*(in|inch|inches|"|”|″|cm|cms|centimeters?|m|mt|mts|meters?|ft|kg|lbs?|pounds?|kilograms?|ounces?|g|ozs?|fl oz|fl oz (us)|fluid ounces?|kphs?|km\/h|kilometers per hours?|mhp|mphs?|meters per hours?|(?:°\s?|º\s?|)(?:degrees?\s+)?(?:celsius|fahrenheit|[CF])|km\/hs?|ml|milliliters?|l|liters?|litres?|gal|gallons?|yards?|yd|Millimeter|millimetre|kilometers?|mi|mm|miles?|km|ft|fl|feets?|grams?|kilowatts?|kws?|brake horsepower|mechanical horsepower|hps?|bhps?|miles per gallons?|mpgs?|liters per 100 kilometers?|lt?\/100km|liquid quarts?|lqs?|qt|foot-? ?pounds?|ft-?lbs?|lb fts?|newton-? ?meters?|n·?m|\^\d+)[ \t\xA0]*(?:\(\w+\)[ \t\xA0]*)?$/i);
  275.  
  276. if (GM_getValue("UnitsConverter") === true && SelectedText.match(Units) !== null) { //If the Units Converter option is enabled and if the selected text is an unit
  277.  
  278. const selectedUnitType = SelectedText.match(Units)[3].toLowerCase();
  279. const SelectedUnitValue = SelectedText.match(Units)[1].replaceAll(',', '.');
  280. const SecondSelectedUnitValue = SelectedText.match(Units)[2]?.replaceAll(',', '.') || 0;
  281.  
  282. const convertValue = (value, unitType) => {
  283. const { factor, convert } = window.UConv[unitType] || {};
  284. return convert ? convert(value) : value * factor;
  285. };
  286.  
  287. var NewUnit = window.UConv[selectedUnitType]?.unit || selectedUnitType;
  288. var ConvertedUnit = `${convertValue(parseFloat(SelectedUnitValue), selectedUnitType).toFixed(2)}${SecondSelectedUnitValue != 0 ? ` x ${convertValue(parseFloat(SecondSelectedUnitValue), selectedUnitType).toFixed(2)}` : ''}`;
  289. ConvertedUnit = SelectedText.match(/\^(\d+\.?\d*)/) ? (NewUnit = 'power', Math.pow(parseFloat(SelectedUnitValue), parseFloat(SelectedText.match(/\^(\d+\.?\d*)/)[1]))) : ConvertedUnit;
  290. ShowConvertion('Units', NewUnit, ConvertedUnit);
  291. }
  292.  
  293. //Mini Menu___________________________________________________________________________________________________________________________________________________________________________________
  294. if (shadowRoot.querySelector("#SearchBTN").innerText === 'Open') //If the Search BTN text is 'Open'
  295. {
  296. shadowRoot.querySelector("#highlight_menu > ul").style.paddingInlineStart = '19px'; //Increase the menu size
  297. shadowRoot.querySelector("#SearchBTN").innerText = 'Search'; //Display the BTN text as Search again
  298. shadowRoot.querySelectorAll(".AI-BG-box button").forEach(button => { button.style.marginLeft = ''; }); //Remove the margin left
  299. shadowRoot.querySelector("#OpenAfter").remove(); //Remove the custom Open white hover overlay
  300. }
  301.  
  302. if (SelectedText.match(Links) !== null) //If the selected text is a link
  303. {
  304. shadowRoot.querySelector("#highlight_menu > ul").style.paddingInlineStart = '27px'; //Increase the menu size
  305. shadowRoot.querySelector("#SearchBTN").innerText = 'Open'; //Change the BTN text to Open
  306. shadowRoot.querySelectorAll(".AI-BG-box button").forEach(button => { button.style.marginLeft = '-2%'; }); //Add a margin left
  307. shadowRoot.innerHTML += toHTML(` <style id="OpenAfter"> #SearchBTN::after { width: 177% !important; transform: translate(-34%, -71%) !important; } </style> `); //Add a custom Open white hover overlay
  308. }
  309.  
  310. shadowRoot.querySelector("#SearchBTN").onmousedown = function() {
  311. GM_openInTab(SelectedText.match(Links) ? SelectedText.replace(/^(?!https?:\/\/)(.+)$/, 'https://$1') : `https://www.google.com/search?q=${SelectedText.replaceAll('&', '%26').replace(/\s+/g, ' ')}`, { active: true, setParent: true, loadInBackground: true }); //Open link or google and search for the selected text
  312. shadowRoot.querySelector("#highlight_menu").classList.remove('show'); //Hide the menu
  313. };
  314.  
  315. const menu = shadowRoot.querySelector("#highlight_menu");
  316. if (document.getSelection().toString().trim() !== '') { //If text has been selected
  317. const p = document.getSelection().getRangeAt(0).getBoundingClientRect(); //Store the selected position
  318.  
  319. menu.classList.add('show'); //Show the menu
  320. menu.offsetHeight; //Trigger reflow by forcing a style calculation
  321. menu.style.left = p.left + (p.width / 2) - (menu.offsetWidth / 2) + 'px';
  322. menu.style.top = p.top - menu.offsetHeight - 10 + 'px';
  323. menu.classList.add('highlight_menu_animate');
  324.  
  325. return; //Keep the menu open
  326. }
  327. menu.classList.remove('show'); //Hide the menu
  328. shadowRoot.querySelector("#SearchBTN span")?.remove(); //Return previous HTML
  329. }); //Finishes the mouseup event listener
  330.  
  331. //AI Menu_______________________________________________________________________________________________________________________________________________________________________________________
  332. var desiredVoice = null, isRecognizing = false;
  333. const HtmlMenu = document.createElement('div'); //Create a container div
  334. HtmlMenu.setAttribute('style', `width: 0px; height: 0px; display: none;`); //Hide the container div by default
  335. const shadowRoot = HtmlMenu.attachShadow({ mode: 'closed' });
  336. const UniqueLangs = navigator.languages.filter((l, i, arr) => !arr.slice(0, i).some(e => e.split('-')[0].toLowerCase() === l.split('-')[0].toLowerCase()) ); //Filter unique languages
  337. const Lang = UniqueLangs.join(' and into '); //Use 1 or 2 languages
  338.  
  339. shadowRoot.innerHTML = toHTML(GM_getResourceText("AIMenuContent")); //Set the AI menu HTML+CSS
  340.  
  341. function Generate(Prompt, button) { //Call the AI endpoint
  342. const context = !!shadowRoot.querySelector("#context.show") ? `"${Prompt}"\n\nMainly base yourself on the text below\n\n${document.body.innerText}` : Prompt; //Add the page context if context is enabled
  343. const isQuestion = Prompt.includes('?') ? 'Give me a very short, then a long detailed answer' : 'Help me further understand/learn a term or topic from the text/word';
  344. const isWord = Prompt.split(' ').length < 5 ? `\nAfter showing (in order) (add a heading as "${Prompt}"), a few possible "Synonyms:", "Definition:" and "Example:".` : '';
  345. const msg = button.match('translate') ? `Translate this text: "${Prompt.trim().slice(0, 215)}${Prompt.length > 215 ? '…' : ''}"` : button.match('Prompt') ? `${Prompt.trim().slice(0, 240)}${Prompt.length > 240 ? '…' : ''}` : `Help me further explore a term or topic from the text: "${Prompt.trim().slice(0, 180)}${Prompt.length > 180 ? '…' : ''}"`; //AI Box top text
  346. const AIFunction = button.match('translate') ? `Translate into ${Lang} the following text:\n\n"${Prompt}"\n${isWord}${UniqueLangs.length > 1 ? `\n\nYou must answer using only 1 language first, then use only the other language, don't mix both languages!\n\n"${Prompt}" should be translated for the 2nd language` : ''}` : button.match('Prompt') ? context : `${isQuestion}: "${Prompt}"`; //AI final prompt
  347.  
  348. function handleState(state) { //Show #AIMenu + #dictate but hide #TopPause for the load/abort states. Do the opposite for the 'start' state.
  349. ["#AIMenu", "#dictate", "#TopPause"].forEach(el => {
  350. if (el === "#TopPause") {
  351. shadowRoot.querySelector(el).classList[state !== 'start' ? 'remove' : 'add']('show');
  352. } else {
  353. shadowRoot.querySelector(el).classList[state !== 'start' ? 'add' : 'remove']('show');
  354. }
  355. });
  356. }
  357.  
  358. const Generating = setInterval(function() { //Start the interval to change the text
  359. if (shadowRoot.querySelector("#finalanswer").innerText === 'ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ▋') { //Start the if condition
  360. shadowRoot.querySelector("#finalanswer").innerText = 'ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ'; //Remove ▋
  361. } else { //Toggle between showing and hiding ▋
  362. shadowRoot.querySelector("#finalanswer").innerText = 'ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ▋'; //Add ▋
  363. }
  364. }, 200);
  365.  
  366. const request = GM.xmlHttpRequest({
  367. method: "POST",
  368. url: `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:streamGenerateContent?key=${GM_getValue("APIKey")}`,
  369. responseType: 'stream',
  370. headers: { "Content-Type": "application/json" },
  371. data: JSON.stringify({
  372. contents: [{
  373. parts: [{
  374. text: AIFunction //AI prompt
  375. }]
  376. }],
  377. systemInstruction: {
  378. parts: [{
  379. text: `List of things you aren't allowed to say/do anything like:\n1 "Based on the provided text"\n2 "The text is already in"\n3 "No translation is needed"\n4 Ask for more context\n5 "You haven't provided context"\n6 Use bullet points for Synonyms`
  380. }]
  381. },
  382. safetySettings: [
  383. { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
  384. { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
  385. { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
  386. { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" }
  387. ],
  388. }),
  389. onerror: function(err) {
  390. clearInterval(Generating); //Stop showing ▋
  391. shadowRoot.querySelector("#msg").innerHTML = 'Error';
  392. shadowRoot.querySelector("#finalanswer").innerHTML = toHTML(`<br>Please copy and paste the error below:<br><a class="feedback" href="https://greatest.deepsurf.us/scripts/419825/feedback">Click here to report this bug</a><br><br> Prompt: ${Prompt}<br> Button: ${button}<br> Error: <pre>${JSON.stringify(err, null, 2)}</pre><br><br><br>`);
  393. },
  394. onload: function(response) {
  395. handleState('load');
  396. },
  397. onabort: function(response) {
  398. handleState('abort');
  399. clearInterval(Generating); //Stop showing ▋
  400. shadowRoot.querySelector("#finalanswer").innerHTML = toHTML('<div>ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤResponse has been interrupted.<div>');
  401. },
  402. onloadstart: function(response) {
  403. handleState('start');
  404. shadowRoot.querySelector("#prompt").focus();
  405. shadowRoot.querySelector("#msg").innerHTML = msg;
  406.  
  407. shadowRoot.querySelector("#copyAnswer").onclick = function() {
  408. shadowRoot.querySelector("#copyAnswer").style.display = 'none';
  409. shadowRoot.querySelector("#AnswerCopied").style.display = 'inline-flex';
  410. GM_setClipboard(shadowRoot.querySelector("#finalanswer").innerText.replace('ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ', ''));
  411. setTimeout(() => { //Return play BTN svg
  412. shadowRoot.querySelector("#copyAnswer").style.display = 'inline-flex';
  413. shadowRoot.querySelector("#AnswerCopied").style.display = 'none';
  414. }, 1000);
  415. };
  416.  
  417. const reader = response.response.getReader();
  418. const decoder = new TextDecoder();
  419. var buffer = '', partialMarkdown = '';
  420.  
  421. function readStream() {
  422. reader.read().then(({ value }) => {
  423. buffer += decoder.decode(value, { stream: true });
  424. var startIdx = 0;
  425. while (true) {
  426. const openBrace = buffer.indexOf('{', startIdx);
  427. if (openBrace === -1) break;
  428. var balance = 1, closeBrace = openBrace + 1;
  429.  
  430. while (balance > 0 && closeBrace < buffer.length) {
  431. if (buffer[closeBrace] === '{') balance++;
  432. if (buffer[closeBrace] === '}') balance--;
  433. closeBrace++;
  434. }
  435.  
  436. if (balance !== 0) break; //Incomplete JSON object
  437.  
  438. const jsonString = buffer.substring(openBrace, closeBrace);
  439. const item = JSON.parse(jsonString);
  440. clearInterval(Generating); //Stop showing ▋
  441. partialMarkdown += item.candidates?.[0]?.content?.parts?.[0]?.text || item.error.message; //If there's no error show the AI answer, else show the error message
  442.  
  443. const tempDiv = document.createElement('div');
  444. tempDiv.innerHTML = window.marked.parse(partialMarkdown);
  445.  
  446. shadowRoot.querySelector("#finalanswer").innerHTML = '';
  447. shadowRoot.querySelector("#finalanswer").appendChild(tempDiv);
  448. startIdx = closeBrace;
  449. }
  450. buffer = buffer.substring(startIdx);
  451. readStream();
  452. });
  453. }
  454.  
  455. readStream();
  456.  
  457. shadowRoot.querySelector("#CloseOverlay").classList.add('show');
  458. shadowRoot.querySelector("#highlight_menu").classList.remove('show'); //Hide the mini menu on the page
  459. shadowRoot.querySelectorAll("#AIBox, .animated-border, #AIBox.AnswerBox").forEach(el => el.classList.add('show')); //Show the AI input and box
  460.  
  461. var SpeechRecognition = SpeechRecognition || window.webkitSpeechRecognition;
  462. const recognition = new SpeechRecognition();
  463. recognition.interimResults = true; //Show partial results
  464. recognition.continuous = true; //Keep listening until stopped
  465.  
  466. var transcript = ""; //Add words
  467. shadowRoot.querySelector("#CloseOverlay").onclick = function() {
  468. [...shadowRoot.querySelector("#finalanswer div").childNodes].slice(0, -1).forEach(node => node.remove()); //Reset the text content
  469. shadowRoot.querySelectorAll("#AIBox, .animated-border, #AIBox.AnswerBox").forEach(el => el.classList.remove('show')); //Hide the AI input and box
  470. this.classList.remove('show');
  471. recognition.stop(); //Stop recognizing audio
  472. speechSynthesis.cancel(); //Stop speaking
  473. request.abort(); //Abort any ongoing request
  474. if (shadowRoot.querySelector("#gemini").style.display === 'none') {
  475. shadowRoot.querySelector("#AddContext").remove(); //Return original prompt input styles
  476. shadowRoot.querySelector("#context").classList.remove('show');
  477. shadowRoot.querySelector("#prompt").placeholder = 'Enter your prompt to Gemini'; //Return default placeholder
  478. }
  479. };
  480.  
  481. shadowRoot.querySelector("#TopPause").onclick = function() {
  482. request.abort();
  483. };
  484.  
  485. recognition.onend = function() {
  486. isRecognizing = false;
  487. shadowRoot.querySelectorAll('.state1, .state2, .state3').forEach((state, index) => { //ForEach SVG animation state
  488. index.toString().match(/1|2/) && (state.style.display = 'none'); //Show only the 1 state
  489. state.classList.remove('animate'+index); //Stop the voice recording animation
  490. });
  491.  
  492. transcript ? Generate(transcript, shadowRoot.querySelector("#prompt").className) : shadowRoot.querySelector("#finalanswer").innerHTML = toHTML(`<br>No audio detected. Please try again or check your mic settings.ㅤㅤㅤㅤㅤㅤㅤㅤㅤ<br><br>`); //Call the AI API if transcript audio words were detected or show an error message
  493. }; //Finish the recognition end event listener
  494.  
  495. recognition.onresult = function(event) { //Handle voice recognition results
  496. transcript = ""; //Clear the transcript at the start of the event
  497. for (var i = 0; i < event.results.length; i++) { //For all transcript results
  498. transcript += event.results[i][0].transcript + ' '; //Concatenate all intermediate transcripts
  499. }
  500. shadowRoot.querySelector("#msg").innerText = transcript.slice(0, 240) + (transcript.length > 240 ? '…' : ''); //Display recognized words
  501. };
  502.  
  503. shadowRoot.querySelector("#dictate").onclick = function() {
  504. if (isRecognizing) {
  505. recognition.stop();
  506. } else {
  507. isRecognizing = true;
  508. recognition.start();
  509. shadowRoot.querySelectorAll('.state1, .state2, .state3').forEach((state, index) => { //ForEach SVG animation state
  510. state.style.display = 'unset'; //Show all states
  511. state.classList.add('animate'+index); //Start the voice recording animation
  512. });
  513. }
  514. };
  515.  
  516. speechSynthesis.onvoiceschanged = () => desiredVoice = speechSynthesis.getVoices().find(v => v.name === "Microsoft Zira - English (United States)"); //Find and store the desired voice
  517. speechSynthesis.onvoiceschanged(); //Handle cases where the event doesn't fire
  518.  
  519. shadowRoot.querySelectorAll("#speak, #SpeakingPause").forEach(function(el) {
  520. el.onclick = function() { //When the speak or the bottom pause BTNs are clicked
  521. if (speechSynthesis.speaking) {
  522. speechSynthesis.cancel();
  523. shadowRoot.querySelector("#speak").style.display = 'inline-flex'; //Show the play BTN
  524. shadowRoot.querySelector("#SpeakingPause").classList.remove('show'); //Hide the pause BTN
  525. }
  526. else
  527. {
  528. shadowRoot.querySelector("#speak").style.display = 'none'; //Hide the play BTN
  529. shadowRoot.querySelector("#SpeakingPause").classList.add('show');
  530.  
  531. var audio = new SpeechSynthesisUtterance(shadowRoot.querySelector("#finalanswer").innerText.replace(/.*?\(..-..\)|[^\p{L}\p{N}\s%.,!?]/gui, '')); //Play the AI response text, removing non-alphanumeric characters and lang locales for better pronunciation
  532. audio.voice = desiredVoice; //Use the desiredVoice
  533. speechSynthesis.speak(audio); //Speak the text
  534.  
  535. audio.onend = (event) => {
  536. shadowRoot.querySelector("#speak").style.display = 'inline-flex'; //Show the play BTN
  537. shadowRoot.querySelector("#SpeakingPause").classList.remove('show');
  538. };
  539. }
  540. };
  541. });
  542.  
  543. shadowRoot.querySelector("#NewAnswer").onclick = function() {
  544. recognition.stop(); //Stop recognizing audio
  545. speechSynthesis.cancel(); //Stop speaking
  546. Generate(Prompt, button); //Call the AI API
  547. };
  548. } //Finishes the onloadstart event listener
  549. });//Finishes the GM.xmlHttpRequest function
  550. } //Finishes the Generate function
  551.  
  552. shadowRoot.querySelector("#prompt").addEventListener("keydown", (event) => {
  553. if (event.key === "Enter") {
  554. Generate(shadowRoot.querySelector("#prompt").value, shadowRoot.querySelector("#prompt").className); //Call the AI API
  555. shadowRoot.querySelector("#prompt").value = ''; //Erase the prompt text
  556. }
  557. if (event.key === "Tab") {
  558. if (shadowRoot.querySelector("#prompt").placeholder.match('using')) { //If the input bar contains the word "using"
  559. shadowRoot.querySelector("#AddContext").remove(); //Return original prompt input styles
  560. shadowRoot.querySelector("#context").classList.remove('show'); //Hide the context view
  561. shadowRoot.querySelector("#prompt").placeholder = 'Enter your prompt to Gemini'; //Return default placeholder
  562. }
  563. else
  564. {
  565. shadowRoot.querySelector("#context").classList.add('show'); //Show the context view
  566. shadowRoot.querySelector("#prompt").placeholder = `Gemini is using ${location.host.replace('www.','')} for context...`; //Change placeholder
  567. shadowRoot.querySelector("#highlight_menu").insertAdjacentHTML('beforebegin', ` <style id="AddContext"> #gemini { display: none; } #prompt { left: 12%; width: 75%; } #tabcontext { display: none; } .animated-border { --color-OrangeORLilac: #FF8051; /* Change the border effect color to orange */ } </style> `); //Show the context bar
  568. }
  569. }
  570. setTimeout(() => { //Wait for the code above to execute
  571. shadowRoot.querySelector("#prompt").focus(); //Refocus on the input bar
  572. }, 0);
  573. });
  574.  
  575. shadowRoot.querySelectorAll("#AIBTN").forEach(function(button) {
  576. button.onmousedown = function(event, i) { //When the Explore or the Translate BTNs are clicked
  577. if (GM_getValue("APIKey") === undefined || GM_getValue("APIKey") === null || GM_getValue("APIKey") === '') { //Set up the API Key if it isn't already set
  578. GM_setValue("APIKey", prompt('Enter your API key\n*Press OK\n\nYou can get a free API key at https://aistudio.google.com/app/apikey'));
  579. }
  580. if (GM_getValue("APIKey") !== null && GM_getValue("APIKey") !== '') {
  581. Generate(SelectedText, this.className); //Call the AI API
  582. }
  583. };
  584. });
  585.  
  586. if (document.body.textContent !== '' || document.body.innerText !== '') //If the body has any text
  587. {
  588. document.body.appendChild(HtmlMenu); //Add the script menu div container
  589. }
  590.  
  591. shadowRoot.querySelector('#CopyBTN').onmousedown = function() {
  592. GM_setClipboard(SelectedText);
  593. };
  594.  
  595. window.addEventListener('scroll', async function() {
  596. shadowRoot.querySelector("#highlight_menu").classList.remove('show'); //Hide the menu
  597. });
  598. }