├── .gitignore ├── src ├── .DS_Store ├── icons │ └── chat-128.png ├── html │ └── popup.html └── js │ ├── background.js │ ├── highlight.js │ └── popup.js ├── README.md ├── manifest.json └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongyishi/gpt-index-extension/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /src/icons/chat-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hongyishi/gpt-index-extension/HEAD/src/icons/chat-128.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gpt-index-extension 2 | GPT Index Extension for Google Chrome 3 | 4 | To use: 5 | 1. Download this repo as a ZIP file from GitHub. 6 | 2. Unzip the file and you should have a folder named gpt-index-extension. 7 | 3. In Chrome go to the extensions page (chrome://extensions). 8 | 4. Enable Developer Mode. 9 | 5. Click on Load Unpacked and choose the extension folder. 10 | 11 | This extension asks for your OpenAI API key to use during webpage indexing and querying. Specifically it's only passed into the LangChain function call and never stored server side. You can also leave the API field blank and it will use mine, but please don't abuse or I will remove that feature. 12 | -------------------------------------------------------------------------------- /src/html/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
GPT Index Extension by Hongyi 10 |
11 | 12 | 13 |
14 | 15 | 19 |
20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GPT Index", 3 | "manifest_version": 3, 4 | "version": "0.1", 5 | "description": "Call GPT Index server to index the current web page", 6 | "permissions": [ 7 | "activeTab", 8 | "storage", 9 | "webRequest", 10 | "tabs", 11 | "scripting" 12 | ], 13 | "background": { 14 | "service_worker": "src/js/background.js" 15 | }, 16 | "content_scripts": [{ 17 | "all_frames": true, 18 | "run_at": "document_end", 19 | "matches": [""], 20 | "js": ["src/js/highlight.js"] 21 | }], 22 | "host_permissions": [ 23 | "" 24 | ], 25 | "action": { 26 | "default_popup": "src/html/popup.html", 27 | "default_icon": { 28 | "128": "src/icons/chat-128.png" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 hongyishi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/js/background.js: -------------------------------------------------------------------------------- 1 | chrome.windows.onFocusChanged.addListener((windowId) => { 2 | console.log('On focus change: ' + windowId); 3 | handleActiveTab(); 4 | }); 5 | 6 | chrome.tabs.onActivated.addListener((e) => { 7 | console.log('On activated', e); 8 | handleActiveTab(); 9 | }); 10 | chrome.tabs.onUpdated.addListener((e) => { 11 | console.log('On updated', e); 12 | handleActiveTab(); 13 | }); 14 | 15 | const handleActiveTab = () => { 16 | chrome.tabs.query({ active: true, currentWindow: true, lastFocusedWindow: true }, function (tabs) { 17 | if (tabs[0]) { 18 | last_tab_id = tabs[0].id; 19 | chrome.windows.get(tabs[0].windowId, (w) => { 20 | if (w && w.focused) { 21 | currentUrl = tabs[0].url; 22 | console.log('ACTIVE TAB -> ' + tabs[0].url); 23 | } else { 24 | console.log('ACTIVE TAB WITHOUT FOCUS -> ' + tabs[0].url); 25 | } 26 | }); 27 | } else { 28 | console.log('NO ACTIVE TAB'); 29 | } 30 | }) 31 | }; 32 | 33 | var currentUrl; 34 | 35 | chrome.runtime.onMessage.addListener( 36 | function (request, sender, sendResponse) { 37 | if (request.request === "url") 38 | sendResponse({ response: currentUrl }); 39 | 40 | if (request.request === "update") { 41 | chrome.tabs.update({ url: request.requestUrl }); 42 | } 43 | if (request.request === "highlight") { 44 | console.log("highlight: " + last_tab_id); 45 | text_refs = request.text_refs; 46 | console.log("text_refs: "); 47 | console.log(text_refs); 48 | for (let i = 0; i < text_refs.length; i++) { 49 | index = text_refs[i]; 50 | console.log("source text: "); 51 | console.log(index["source_text"]); 52 | chrome.tabs.sendMessage(last_tab_id, 53 | { 54 | message: "highlight", 55 | sourceText: index["source_text"] 56 | }, function(response) {}); 57 | } 58 | } 59 | } 60 | ); 61 | 62 | var last_tab_id; -------------------------------------------------------------------------------- /src/js/highlight.js: -------------------------------------------------------------------------------- 1 | const highlightedMarkTemplate = document.createElement('mark'); 2 | 3 | function highlight(text) { 4 | TEXT_TEST_LENGTH = 20; 5 | NUM_OVERLAP_SUCCESS_THRESHOLD = 5; 6 | LEFT_GUARDRAIL = 0; 7 | 8 | nodeText = ""; 9 | treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); 10 | while (nodeText.length < TEXT_TEST_LENGTH) { 11 | nodeText = treeWalker.nextNode().data; 12 | } 13 | // console.log(nodeText); 14 | innerHTML = document.body.innerHTML; 15 | 16 | LEFT_GUARDRAIL = innerHTML.indexOf(nodeText.slice(0, TEXT_TEST_LENGTH)); 17 | // console.log("LEFT_GUARDRAIL: " + LEFT_GUARDRAIL); 18 | if (LEFT_GUARDRAIL < 0) { 19 | return; 20 | } 21 | 22 | docLength = text.length; 23 | 24 | startCounter = 0; 25 | posStartSum = 0; 26 | lastDocPos = 0; 27 | for (var i = 0; i < docLength - TEXT_TEST_LENGTH; i+=TEXT_TEST_LENGTH) { 28 | lastDocPos = i + TEXT_TEST_LENGTH; 29 | span = text.slice(i, i+TEXT_TEST_LENGTH); 30 | // console.log(span); 31 | index = innerHTML.indexOf(span); 32 | if (index > LEFT_GUARDRAIL && index != -1) { 33 | startCounter++; 34 | posStartSum += index; 35 | } 36 | if (startCounter >= NUM_OVERLAP_SUCCESS_THRESHOLD) { 37 | break; 38 | } 39 | } 40 | // console.log("startCounter: " + startCounter); 41 | // console.log("posStartSum: " + posStartSum); 42 | if (startCounter >= NUM_OVERLAP_SUCCESS_THRESHOLD) { 43 | startIndex = Math.floor(posStartSum/startCounter); 44 | 45 | endCounter = 0; 46 | posEndSum = 0; 47 | for (var i = docLength - TEXT_TEST_LENGTH; i > lastDocPos; i-=TEXT_TEST_LENGTH) { 48 | span = text.slice(i, i+TEXT_TEST_LENGTH); 49 | // console.log(span); 50 | index = innerHTML.indexOf(span); 51 | if (index > startIndex && index != -1) { 52 | endCounter++; 53 | posEndSum += index; 54 | } 55 | if (endCounter >= NUM_OVERLAP_SUCCESS_THRESHOLD) { 56 | break; 57 | } 58 | } 59 | // console.log("endCounter: " + endCounter); 60 | // console.log("posEndSum: " + posEndSum); 61 | if (endCounter >= NUM_OVERLAP_SUCCESS_THRESHOLD) { 62 | endIndex = Math.floor(posEndSum/endCounter); 63 | 64 | // find
in the document near startIndex and endIndex 65 | lineIndexStart = innerHTML.indexOf("
", startIndex); 66 | lineIndexEnd = innerHTML.indexOf("
", endIndex); 67 | // console.log("lineIndexStart: " + lineIndexStart); 68 | // console.log("lineIndexEnd: " + lineIndexEnd); 69 | // console.log("startIndex: " + startIndex); 70 | // console.log("endIndex: " + endIndex); 71 | if (lineIndexEnd - lineIndexStart < (endIndex - startIndex)/2) { 72 | index = startIndex; 73 | while (index < endIndex) { 74 | // find

in the document 75 | lineIndexStart = innerHTML.indexOf("

", index) + "

".length; 76 | lineIndexEnd = innerHTML.indexOf("

", lineIndexStart); 77 | // console.log("lineIndexStart: " + lineIndexStart); 78 | // console.log("lineIndexEnd: " + lineIndexEnd); 79 | // console.log(innerHTML.substring(lineIndexStart, lineIndexEnd)); 80 | document.body.innerHTML = innerHTML.slice(0, lineIndexStart) + "" + innerHTML.substring(lineIndexStart, lineIndexEnd) + "" + innerHTML.substring(lineIndexEnd); 81 | innerHTML = document.body.innerHTML; 82 | index = lineIndexEnd; 83 | } 84 | } 85 | else{ 86 | // console.log("lineIndexStart: " + lineIndexStart); 87 | // console.log("lineIndexEnd: " + lineIndexEnd); 88 | document.body.innerHTML = innerHTML.slice(0, lineIndexStart) + "" + innerHTML.substring(lineIndexStart, lineIndexEnd) + "" + innerHTML.substring(lineIndexEnd); 89 | } 90 | // console.log(document.body.innerHTML); 91 | document.querySelector("mark").scrollIntoView(); 92 | } 93 | } 94 | }; 95 | 96 | chrome.runtime.onMessage.addListener( 97 | function(request, sender, sendResponse) { 98 | if (request.message === "highlight") { 99 | console.log("highlight"); 100 | highlight(request.sourceText); 101 | } 102 | return true; 103 | } 104 | ); -------------------------------------------------------------------------------- /src/js/popup.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { 2 | document.getElementById("index-button").addEventListener("click", indexWebPage); 3 | document.getElementById("query-button").addEventListener("click", sendQuery); 4 | chrome.storage.local.get(["query_response"]).then((result) => { 5 | console.log("Value currently is " + result.query_response); 6 | if( typeof result.query_response === 'undefined' || result.query_response === null ) { 7 | console.log('result.query_response is undefined or null'); 8 | } 9 | else { 10 | document.getElementById("query-results").innerHTML = result.query_response; 11 | } 12 | }); 13 | } 14 | 15 | // index web page 16 | function indexWebPage() { 17 | document.getElementById("index-button").disabled = true; 18 | console.log("index web page"); 19 | 20 | // // Get user's OpenAI API key 21 | var apiKey = document.getElementById("api-key-input").value; 22 | // if (apiKey == "") { 23 | // alert("Please enter your OpenAI API key"); 24 | // return; 25 | // } 26 | (async () => { 27 | const response = await chrome.runtime.sendMessage({ request: "url" }); 28 | // do something with response here, not outside the function 29 | console.log(response.response); 30 | var currentUrl = response.response; 31 | // Send a request to the server to check if the web page has already been indexed 32 | $.ajax({ 33 | url: "http://gptindexextensionserver-env.eba-ssmvy7mt.us-west-2.elasticbeanstalk.com/check-index", 34 | type: "POST", 35 | data: JSON.stringify({ 36 | url: currentUrl, 37 | apiKey: apiKey, 38 | }), 39 | headers: { 40 | "Content-Type": "application/json" 41 | }, 42 | success: function (response) { 43 | if (response === "True") { 44 | // Show query prompt 45 | document.getElementById("query-prompt").style.display = "block"; 46 | } else { 47 | // Send request to index web page 48 | $.ajax({ 49 | url: "http://gptindexextensionserver-env.eba-ssmvy7mt.us-west-2.elasticbeanstalk.com/index-webpage", 50 | type: "POST", 51 | data: JSON.stringify({ 52 | url: currentUrl, 53 | apiKey: apiKey 54 | }), 55 | headers: { 56 | "Content-Type": "application/json" 57 | }, 58 | success: function (response) { 59 | if (response === "done") { 60 | // Show query prompt 61 | document.getElementById("query-prompt").style.display = "block"; 62 | document.getElementById("index-button").disabled = false; 63 | } else { 64 | alert("Error indexing web page, please try again."); 65 | document.getElementById("index-button").disabled = false; 66 | } 67 | }, 68 | error: function (error) { 69 | alert("Error indexing web page, please try again."); 70 | document.getElementById("index-button").disabled = false; 71 | } 72 | }); 73 | } 74 | }, 75 | error: function (error) { 76 | alert("Error checking index status, please try again."); 77 | document.getElementById("index-button").disabled = false; 78 | } 79 | }); 80 | 81 | })(); 82 | 83 | } 84 | 85 | // Send query to server 86 | function sendQuery() { 87 | console.log("send query"); 88 | document.getElementById("query-button").disabled = true; 89 | 90 | // Get user's OpenAI API key 91 | var apiKey = document.getElementById("api-key-input").value; 92 | // if (apiKey == "") { 93 | // alert("Please enter your OpenAI API key"); 94 | // return; 95 | // } 96 | (async () => { 97 | const response = await chrome.runtime.sendMessage({ request: "url" }); 98 | // do something with response here, not outside the function 99 | console.log(response.response); 100 | var currentUrl = response.response; 101 | var query = document.getElementById("query-input").value; 102 | document.getElementById("query-results").innerHTML = "Querying..."; 103 | $.ajax({ 104 | url: "http://gptindexextensionserver-env.eba-ssmvy7mt.us-west-2.elasticbeanstalk.com/query", 105 | type: "POST", 106 | data: JSON.stringify({ 107 | url: currentUrl, 108 | query: query, 109 | apiKey: apiKey 110 | }), 111 | headers: { 112 | "Content-Type": "application/json" 113 | }, 114 | success: function (response) { 115 | console.log("query success response: " + response); 116 | // Display query results 117 | document.getElementById("query-results").innerHTML = response.response_str; 118 | chrome.storage.local.set({ query_response: response.response_str }).then(() => { 119 | console.log("Value is set to " + response.response_str); 120 | }); 121 | console.log("text_refs: " + response.text_refs) 122 | chrome.runtime.sendMessage({ request: "highlight", text_refs: response.text_refs }); 123 | 124 | document.getElementById("query-button").disabled = false; 125 | }, 126 | error: function (error) { 127 | alert("Error querying indexed web page, please try again."); 128 | document.getElementById("query-button").disabled = false; 129 | } 130 | }); 131 | })(); 132 | } --------------------------------------------------------------------------------