commit 06d7c3af5ce46c2f30228b0ab80303ec79ea12d3 Author: Achiya Elyasaf <10044875+eggsterino@users.noreply.github.com> Date: Mon Jul 31 15:00:05 2023 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b4d482 --- /dev/null +++ b/.gitignore @@ -0,0 +1,94 @@ +# Created by https://www.toptal.com/developers/gitignore/api/intellij+all +# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +# End of https://www.toptal.com/developers/gitignore/api/intellij+all + +LeafLLM*.zip diff --git a/README.md b/README.md new file mode 100644 index 0000000..5cf568c --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# LeafLLM: an AI-powered Overleaf +This Chrome extension adds the power of large-language models (LLMs) to Overleaf through a Chrome extension. + +The extension originated from [GPT4Overleaf](https://github.com/e3ntity/gpt4overleaf). + +## Manual installation +1. Clone the repository +2. Open Chrome and go to `chrome://extensions/` +3. Enable developer mode +4. Click "Load unpacked" and select the repository folder + +## Configuration +The plugin can be configured by clicking the plugin button in the Chrome toolbar. It requires inserting an API key from [OpenAI](https://platform.openai.com/account/api-keys). You also need to choose which tools you wish to enable. + +## Usage +These are the tools that are currently available: + +### Auto-complete +Select a text and press `Alt+c` to trigger the auto-complete tool. + +### Improve +Select a text and press `Alt+i` to trigger the improvement tool. The original text will be commented out and the improved text will be inserted below it. + +### Ask +Select a text and press `Alt+a` to trigger the ask tool. The original text will be deleted and the answer will be inserted in its place. + +For example: "Create a table 4x3 that the first row is bold face" will be replaced with, e.g.,: +```latex +\begin{tabular}{|c|c|c|} +\hline +\textbf{Column 1} & \textbf{Column 2} & \textbf{Column 3}\\ +\hline +Entry 1 & Entry 2 & Entry 3\\ +\hline +Entry 4 & Entry 5 & Entry 6\\ +\hline +Entry 7 & Entry 8 & Entry 9\\ +\hline +\end{tabular} +``` + +You can then, for example: +1. Write before the table: Place the following tabular inside a table environment, center it, and give the following title: "The comparison of the three approaches" +2. Select the sentence and the table +3. Press `Alt+a` to trigger the ask tool. + +The result will be: +```latex +\begin{table}[h] +\centering +\caption{The comparison of the three approaches} +\begin{tabular}{|c|c|c|} +\hline +\textbf{Column 1} & \textbf{Column 2} & \textbf{Column 3}\\ +\hline +Entry 1 & Entry 2 & Entry 3\\ +\hline +Entry 4 & Entry 5 & Entry 6\\ +\hline +Entry 7 & Entry 8 & Entry 9\\ +\hline +\end{tabular} +\end{table} +``` + +## Issues +If you encounter any issues, please open an issue in the project's repository. + +## Privacy +The plugin does not collect any data. The only data that is sent is the text that you select and the API key that you provide. The data is sent to OpenAI's servers only. diff --git a/deploy.ps1 b/deploy.ps1 new file mode 100644 index 0000000..3c3242c --- /dev/null +++ b/deploy.ps1 @@ -0,0 +1,22 @@ +# Define the output ZIP file name +$zipFileName = "LeafLLM.zip" + +# Get the current directory path +$currentDirectory = Get-Location + +# Delete the ZIP file if it already exists +if (Test-Path $zipFileName) { + Remove-Item $zipFileName -Force + Write-Host "Existing ZIP file '$zipFileName' deleted." +} + +# Create an array of directories and files to exclude from the ZIP +$excludeItems = @(".idea", ".git", ".gitignore", "deploy.sh", "deploy.ps1") + +# Get the files and folders in the current directory excluding the specified items +$itemsToZip = Get-ChildItem -Path $currentDirectory -Exclude $excludeItems + +# Compress the items to a ZIP archive +Compress-Archive -Path $itemsToZip.FullName -DestinationPath $zipFileName + +Write-Host "ZIP file created: $zipFileName" diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..2997ea3 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,3 @@ +echo "Packaging extension for submission to the Chrome Web Store." + +zip -r LeafLLM.zip ./* -x .git/**\* -x .idea/**\* -x .gitignore -x deploy.sh -x README.md diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..68f1f9b --- /dev/null +++ b/manifest.json @@ -0,0 +1,46 @@ +{ + "action": { + "default_popup": "popup/popup.html", + "default_icon": "popup/icon.png" + }, + "content_scripts": [ + { + "js": ["scripts/jquery.js", "scripts/content.js"], + "matches": ["https://*.overleaf.com/project/*"] + } + ], + "description": "LLM-based tools for Overleaf", + "icons": { + "16": "popup/icon_16.png", + "48": "popup/icon_48.png", + "128": "popup/icon_128.png" + }, + "commands": { + "Complete": { + "suggested_key": { + "default": "Alt+C" + }, + "description": "Complete selected text" + }, + "Improve": { + "suggested_key": { + "default": "Alt+I" + }, + "description": "Improve selected text" + }, + "Ask": { + "suggested_key": { + "default": "Alt+A" + }, + "description": "Use the selected text to ask GPT. It adds to the beginning of the selected text: 'In Latex, '" + } + }, + "background": { + "service_worker": "scripts/service-worker.js" + }, + "permissions": ["storage", "tabs"], + "manifest_version": 3, + "name": "LeafLLM", + "homepage_url": "https://github.com/achiyae/LeafLLM", + "version": "1.0.0" +} diff --git a/popup/gpt4overleaf_screenshot.png b/popup/gpt4overleaf_screenshot.png new file mode 100644 index 0000000..670cd91 Binary files /dev/null and b/popup/gpt4overleaf_screenshot.png differ diff --git a/popup/icon.png b/popup/icon.png new file mode 100644 index 0000000..c87f8a7 Binary files /dev/null and b/popup/icon.png differ diff --git a/popup/icon_128.png b/popup/icon_128.png new file mode 100644 index 0000000..d4952ba Binary files /dev/null and b/popup/icon_128.png differ diff --git a/popup/icon_16.png b/popup/icon_16.png new file mode 100644 index 0000000..0fca9a7 Binary files /dev/null and b/popup/icon_16.png differ diff --git a/popup/icon_48.png b/popup/icon_48.png new file mode 100644 index 0000000..24cbc0d Binary files /dev/null and b/popup/icon_48.png differ diff --git a/popup/popup.css b/popup/popup.css new file mode 100644 index 0000000..47e634c --- /dev/null +++ b/popup/popup.css @@ -0,0 +1,175 @@ +body { + font-family: Arial, sans-serif; + background-color: #f5f5f5; + margin: 0; + padding: 20px; +} + +.extension-title { + font-size: 24px; + color: #4a4a4a; + margin-bottom: 20px; +} + +.form-container { + background-color: #fff; + border-radius: 5px; + margin: 10px 0; + padding: 20px; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1); +} + +.api-token-text { + font-size: 16px; + color: #4a4a4a; + margin-bottom: 10px; +} + +.api-token-status { + font-weight: bold; +} + +.api-token-input { + width: 100%; + padding: 8px; + font-size: 14px; + border: 1px solid #ccc; + border-radius: 4px; + margin-bottom: 10px; +} + +.btn { + font-size: 14px; + padding: 8px 16px; + background-color: #4a4a4a; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; + margin: 5px 0; + transition: background-color 0.3s; + width: 300px; +} + +.btn:hover { + background-color: #333; +} + +.message-box { + font-size: 14px; + padding: 20px; + color: #4a4a4a; +} + +.message-box .error { + color: #ff0000; +} + +.message-box .message { + color: #4a4a4a; +} + +#controls { + margin-top: 30px; + display: flex; + flex-direction: column; +} + +.control { + display: flex; + justify-content: space-between; + margin-bottom: 10px; +} + +.input { + display: flex; + align-items: center; + font-weight: bold; + color: #4a4a4a; +} + +.input span:first-child { + margin-right: 5px; +} + +.effect { + font-size: 14px; + color: #4a4a4a; +} +.settings-form { + margin-top: 30px; +} + +.settings-text { + font-size: 18px; + color: #4a4a4a; + margin-bottom: 20px; +} + +.settings-container { + display: flex; + flex-direction: column; +} + +.settings-item { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} + +.settings-item-text { + font-size: 16px; + color: #4a4a4a; +} + +.switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: 0.4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + transition: 0.4s; +} + +input:checked + .slider { + background-color: #4a4a4a; +} + +input:checked + .slider:before { + transform: translateX(26px); +} + +.slider.round { + border-radius: 34px; +} + +.slider.round:before { + border-radius: 50%; +} diff --git a/popup/popup.html b/popup/popup.html new file mode 100644 index 0000000..5e66b10 --- /dev/null +++ b/popup/popup.html @@ -0,0 +1,58 @@ + + + + + + + +

LeafLLM: an AI-powered Overleaf

+
+
+
ALTI
+
Improve selected text.
+
+
+
ALTC
+
Complete selected text.
+
+
+
ALTA
+
Ask GPT.
+
+
+
+
Settings
+
+
+
Text Improvement
+ +
+
+
Text Completion
+ +
+
+
Ask
+ +
+
+
+
+
API Token is not set
+ +
To get an API key, go to https://platform.openai.com/account/api-keys
+ + +
+
+ + diff --git a/popup/popup.js b/popup/popup.js new file mode 100644 index 0000000..68a1296 --- /dev/null +++ b/popup/popup.js @@ -0,0 +1,105 @@ +const apiKeyRegex = /sk-[a-zA-Z0-9]{48}/; + +const settings = [ + { key: "textCompletion", name: "text-completion" }, + { key: "textImprovement", name: "text-improvement" }, + { key: "textAsk", name: "text-ask" }, +]; + +function addMessage(message) { + $("#message-box").append(`
${message}
`); +} + +function addErrorMessage(message) { + $("#message-box").append(`
${message}
`); +} + +function clearMessages() { + $("#message-box").empty(); +} + +async function refreshStorage() { + chrome.storage.local.get("openAIAPIKey").then(({ openAIAPIKey }) => { + $("#api-token-form .api-token-status").text(chrome.runtime.lastError || !openAIAPIKey ? "not set" : "set"); + }); + + chrome.storage.local.get(settings.map(({ key }) => key)).then((storage) => { + settings.forEach(({ key, name }) => { + $(`#settings-form input[name='${name}']:checkbox`).prop("checked", storage[key]); + }); + }); +} + +async function handleAPITokenSet(event) { + event.preventDefault(); + event.stopPropagation(); + + clearMessages(); + + const input = $("#api-token-form").find("input[name='api-token']"); + const openAIAPIKey = input.val(); + input.val(""); + + if (!openAIAPIKey || !apiKeyRegex.test(openAIAPIKey)) { + addErrorMessage("Invalid API Token."); + return; + } + + try { + await chrome.storage.local.set({ openAIAPIKey }); + } catch (error) { + console.log(error); + addErrorMessage("Failed to set API Token."); + return; + } + + await refreshStorage(); +} + +async function handleAPITokenClear(event) { + event.preventDefault(); + event.stopPropagation(); + + clearMessages(); + + try { + await chrome.storage.local.remove("openAIAPIKey"); + } catch (error) { + console.log(error); + addErrorMessage("Failed to remove API Token."); + return; + } + + await refreshStorage(); +} + +function makeHandleSettingChange(key) { + return async (event) => { + event.preventDefault(); + event.stopPropagation(); + + clearMessages(); + + const value = event.target.checked; + + try { + await chrome.storage.local.set({ [key]: value }); + } catch (error) { + console.log(error); + addErrorMessage(`Failed to set ${key} setting.`); + return; + } + + await refreshStorage(); + }; +} + +$(document).ready(async function () { + $("#api-token-form .submit").on("click", handleAPITokenSet); + $("#api-token-form .clear").on("click", handleAPITokenClear); + + settings.forEach(({ key, name }) => + $(`#settings-form input[name='${name}']:checkbox`).on("change", makeHandleSettingChange(key)) + ); + await refreshStorage(); +}); diff --git a/scripts/content.js b/scripts/content.js new file mode 100644 index 0000000..c789420 --- /dev/null +++ b/scripts/content.js @@ -0,0 +1,169 @@ +class OpenAIAPI { + static defaultModel = 'text-davinci-003' + + constructor(apiKey) { + this.apiKey = apiKey + } + + query(endpoint, data) { + return new Promise((resolve, reject) => { + const url = `https://api.openai.com/v1/${endpoint}` + + if (!data.model) data.model = OpenAIAPI.defaultModel + + const xhr = new XMLHttpRequest() + xhr.open('POST', url, true) + xhr.setRequestHeader('Content-Type', 'application/json') + xhr.setRequestHeader('Authorization', `Bearer ${this.apiKey}`) + xhr.onreadystatechange = function () { + if (xhr.readyState !== 4) return + if (xhr.status !== 200) return reject('Failed to query OpenAI API.') + + const jsonResponse = JSON.parse(xhr.responseText) + + if (!jsonResponse.choices) return reject('Failed to query OpenAI API.') + + return resolve(jsonResponse.choices) + } + + xhr.send(JSON.stringify(data)) + }) + } + + async completeText(text) { + const data = { + max_tokens: 512, + prompt: text, + n: 1, + temperature: 0.5 + } + + const result = await this.query('completions', data) + + return result[0].text + } + + async improveText(text) { + const data = { + model: 'code-davinci-edit-001', + input: text, + instruction: + 'Correct any spelling mistakes, grammar mistakes, and improve the overall style of the (latex) text.', + n: 1, + temperature: 0.5 + } + + const result = await this.query('edits', data) + + return result[0].text + } +} + +function replaceSelectedText(replacementText, selection) { + const sel = selection === undefined ? window.getSelection() : selection + + if (sel.rangeCount) { + const range = sel.getRangeAt(0) + range.deleteContents() + range.insertNode(document.createTextNode(replacementText)) + } +} + +async function settingIsEnabled(setting) { + let result + try { + result = await chrome.storage.local.get(setting) + } catch (error) { + return false + } + return result[setting] +} + +function commentText(text) { + const regexPattern = /\n/g + const replacementString = '\n%' + let comment = text.replace(regexPattern, replacementString) + if (!comment.startsWith('%')) { + comment = '%' + comment + } + return comment +} + +async function improveTextHandler(openAI) { + if (!(await settingIsEnabled('textImprovement'))) return + const selection = window.getSelection() + const selectedText = selection.toString() + if (!selectedText) return + const editedText = await openAI.improveText(selectedText) + const commentedText = commentText(selectedText) + replaceSelectedText(commentedText + '\n' + editedText, selection) +} + +async function completeTextHandler(openAI) { + if (!(await settingIsEnabled('textCompletion'))) return + const selection = window.getSelection() + const selectedText = selection.toString() + if (!selectedText) return + const editedText = await openAI.completeText(selectedText) + replaceSelectedText(selectedText + editedText, selection) +} + +async function askHandler(openAI) { + if (!(await settingIsEnabled('textAsk'))) return + const selection = window.getSelection() + const selectedText = selection.toString() + if (!selectedText) return + const editedText = await openAI.completeText('In Latex, ' + selectedText) + replaceSelectedText(editedText, selection) +} + +let currentAPIKey +let openAI = undefined + +function cleanup() { +} + +function setAPIKey(key) { + if (currentAPIKey === key) return + cleanup() + currentAPIKey = key + if (currentAPIKey) { + openAI = new OpenAIAPI(currentAPIKey) + console.log('AI4Overleaf: OpenAI API key set, enabling AI4Overleaf features.') + } else { + openAI = undefined + console.log('AI4Overleaf: OpenAI API key is not set, AI4Overleaf features are disabled.') + } +} + +function handleCommand(command) { + console.log('Handling command') + if (command === 'Improve') { + improveTextHandler(openAI) + } else if (command === 'Complete') { + completeTextHandler(openAI) + } else if (command === 'Ask') { + askHandler(openAI) + } +} + +chrome.runtime.onMessage.addListener( + function (request, sender, sendResponse) { + console.log(`Received request: ${JSON.stringify(request)}`) + if (request.command === 'setup') { + setAPIKey(request.apiKey) + } else { + if (!openAI) { + chrome.storage.local.get('openAIAPIKey').then(({ openAIAPIKey }) => { + setAPIKey(openAIAPIKey) + if (openAI) { + handleCommand(request.command) + } + }) + } else { + handleCommand(request.command) + } + } + } +) + diff --git a/scripts/jquery.js b/scripts/jquery.js new file mode 100644 index 0000000..fde8714 --- /dev/null +++ b/scripts/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.4 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),v={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.4",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.cssHas=ce(function(){try{return C.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),d.cssHas||y.push(":has"),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType&&e.documentElement||e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 { + if (command !== commandName) return + console.log(`Command ${command} triggered`) + sendMessage({ command: command }) + }) +} + +async function setup() { + addListener('Improve') + addListener('Complete') + addListener('Ask') + /*const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true }) + if (tab?.url?.startsWith('chrome://')) return undefined + console.log('tab'+tab) + chrome.scripting.executeScript({ + target: { tabId: tab.id }, + files: ['scripts/content.js'] + }).then(() => { + + })*/ +} + +setup()