├── .env.example ├── extension ├── icon.png ├── manifest.json ├── background.js └── content.js ├── .gitignore ├── plugins ├── Gangster.js ├── Default.js └── Image.js ├── package.json ├── config.js ├── server.js └── README.md /.env.example: -------------------------------------------------------------------------------- 1 | OPEN_AI_API_KEY= 2 | OPEN_AI_ORG= 3 | -------------------------------------------------------------------------------- /extension/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coleam00/chatgpt-chrome-extension/HEAD/extension/icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | extension.crx 3 | extension.pem 4 | .env 5 | server-old.js 6 | server-tweet-genie.js -------------------------------------------------------------------------------- /plugins/Gangster.js: -------------------------------------------------------------------------------- 1 | export default { 2 | rules: [`All your responses should sound like a 1940s gangster`], 3 | }; 4 | -------------------------------------------------------------------------------- /plugins/Default.js: -------------------------------------------------------------------------------- 1 | export default { 2 | rules: [ 3 | `If I say create something I mean do some creative writing about it, not browse the internet.`, 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "node server.js " 4 | }, 5 | "dependencies": { 6 | "body-parser": "^1.20.1", 7 | "chatgpt": "^4.0.0", 8 | "cors": "^2.8.5", 9 | "dotenv": "^16.0.3", 10 | "dotenv-safe": "^8.2.0", 11 | "express": "^4.18.2", 12 | "node-fetch": "^3.3.1", 13 | "openai": "^3.2.1", 14 | "ora": "^6.1.2" 15 | }, 16 | "type": "module" 17 | } 18 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | import Default from "./plugins/Default.js"; 2 | import Image from "./plugins/Image.js"; 3 | import Gangster from "./plugins/Gangster.js"; 4 | 5 | const config = { 6 | plugins: [ 7 | // Stop telling me you can't browse the internet, etc 8 | Default, 9 | // Add image generation ability 10 | //Image, 11 | // Talk like a 1940's gangster 12 | //Gangster, 13 | ], 14 | }; 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Ask ChatGPT", 4 | "version": "1.0.0", 5 | "permissions": ["contextMenus"], 6 | "icons": { 7 | "16": "icon.png", 8 | "48": "icon.png", 9 | "128": "icon.png" 10 | }, 11 | "background": { 12 | "service_worker": "background.js" 13 | }, 14 | "content_scripts": [ 15 | { 16 | "matches": [""], 17 | "js": ["content.js"] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /extension/background.js: -------------------------------------------------------------------------------- 1 | // Create a context menu item 2 | chrome.contextMenus.create({ 3 | id: "ask-chatgpt", 4 | title: "Ask ChatGPT", 5 | contexts: ["all"], 6 | }); 7 | 8 | // Listen for when the user clicks on the context menu item 9 | chrome.contextMenus.onClicked.addListener((info, tab) => { 10 | if (info.menuItemId === "ask-chatgpt") { 11 | // Send a message to the content script 12 | chrome.tabs.sendMessage(tab.id, { type: "ASK_CHATGPT" }); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import bodyParser from "body-parser"; 3 | import cors from "cors"; 4 | import { Configuration, OpenAIApi } from 'openai'; 5 | import dotenv from "dotenv-safe"; 6 | dotenv.config(); 7 | 8 | // OpenAI API init 9 | const configuration = new Configuration({ 10 | organization: process.env.OPEN_AI_ORG, 11 | apiKey: process.env.OPEN_AI_API_KEY, 12 | }); 13 | const openai = new OpenAIApi(configuration); 14 | 15 | const app = express().use(cors()).use(bodyParser.json()); 16 | const model = "gpt-3.5-turbo"; 17 | const port = 3000; 18 | 19 | app.post("/", async (req, res) => { 20 | const response = await openai.createChatCompletion({ 21 | model: model, 22 | messages: [ 23 | {role: "user", content: req.body.prompt} 24 | ], 25 | max_tokens: 1000, 26 | }); 27 | 28 | const reply = response.data.choices[0].message.content; 29 | 30 | console.log(reply); 31 | 32 | res.json({"reply": reply.trimStart()}); 33 | }); 34 | 35 | app.listen(port, () => { 36 | console.log(`ChromeGPT API listening on port ${port}`) 37 | }) 38 | -------------------------------------------------------------------------------- /plugins/Image.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | 3 | export default { 4 | rules: [ 5 | `You are an AI that's good at describing images.`, 6 | `First check if my message includes the word "image", "photo", "picture", or "drawing"`, 7 | `If it does include one of those words then at the very end of your reply you should include an image description enclosed in double curly brackets.`, 8 | `If it does not include one of those words then don't add an image description.`, 9 | ], 10 | parse: async (reply) => { 11 | // Match anything between {{ }} 12 | const regex = /\{\{([^\]]+?)\}\}/g; 13 | const matches = reply.match(regex); 14 | if (matches?.length) { 15 | for (const match of matches) { 16 | // Get image description between curly brackets 17 | const imageDescription = match.replace(regex, "$1").trim(); 18 | // Search for image on Lexica 19 | const image = await fetch( 20 | `https://lexica.art/api/v1/search?q=${encodeURIComponent( 21 | imageDescription 22 | )}`, 23 | { 24 | method: "GET", 25 | } 26 | ) 27 | .then((response) => response.json()) 28 | .then((response) => { 29 | if (response?.images) { 30 | return `https://image.lexica.art/md/${response?.images[0]?.id}`; 31 | } 32 | }) 33 | .catch((error) => { 34 | // Ignore error 35 | }); 36 | 37 | // Replace description with image URL 38 | reply = image 39 | ? reply.replace(`\{\{${imageDescription}\}\}`, image) 40 | : reply; 41 | } 42 | } 43 | return reply; 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatGPT Chrome Extension 🤖 ✨ 2 | 3 | A Chrome extension that adds [ChatGPT](https://chat.openai.com) to every text box on the internet! Use it to write tweets, revise emails, fix coding bugs, or whatever else you need, all without leaving the site you're on. Includes a plugin system for greater control over ChatGPT behavior and ability to interact with 3rd party APIs. 4 | 5 | ![](https://i.imgur.com/CPMOyG7.gif) 6 | 7 | ## Install 8 | 9 | First clone this repo on your local machine 10 | 11 | Then install dependencies 12 | 13 | ```bash 14 | npm install 15 | ``` 16 | 17 | Copy `.env-example` into a new file named `.env` and add your ChatGPT API Key. 18 | 19 | Run the server so the extension can communicate with ChatGPT. 20 | 21 | ```bash 22 | node server.js 23 | ``` 24 | 25 | This will automate interaction with ChatGPT through OpenAI's API, thanks to the [chatgpt-api](https://github.com/transitive-bullshit/chatgpt-api) library. 26 | 27 | Add the extension 28 | 29 | 1. Go to chrome://extensions in your Google Chrome browser 30 | 2. Check the Developer mode checkbox in the top right-hand corner 31 | 3. Click "Load Unpacked" to see a file-selection dialog 32 | 4. Select your local `chatgpt-chrome-extension/extension` directory 33 | 34 | You'll now see "Ask ChatGPT" if you right click in any text input or content editable area. 35 | 36 | ## Troubleshooting 37 | 38 | If ChatGPT is taking a very long time to respond or not responding at all then it could mean that their servers are currently overloaded. You can confirm this by going to [chat.openai.com/chat](https://chat.openai.com/chat) and seeing whether their website works directly. 39 | 40 | ## Plugins 41 | 42 | Plugins have the ability to inform ChatGPT of specific conversation rules and parse replies from ChatGPT before they are sent to the browser. 43 | 44 | [Default](/plugins/Default.js) - Sets some default conversation rules 🧑‍🏫 45 | 46 | [Image](/plugins/Image.js) - Tells ChatGPT to describe things visually when asked for an image and then replaces the description with a matching AI generated image from [Lexica](http://lexica.art) 📸 47 | 48 | Your really cool plugin - Go make a plugin, do a pull-request and I'll add it the list 🤝 49 | 50 | ## Related 51 | 52 | Huge thanks to Travis Fischer for creating [chatgpt-api](https://github.com/transitive-bullshit/chatgpt-api) 53 | 54 | ## License 55 | 56 | MIT © Gabe Ragland (follow me on Twitter) 57 | -------------------------------------------------------------------------------- /extension/content.js: -------------------------------------------------------------------------------- 1 | // Listen for messages from the background script 2 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 3 | if (message.type === "ASK_CHATGPT") { 4 | let originalActiveElement; 5 | let text; 6 | 7 | // If there's an active text input 8 | if ( 9 | document.activeElement && 10 | (document.activeElement.isContentEditable || 11 | document.activeElement.nodeName.toUpperCase() === "TEXTAREA" || 12 | document.activeElement.nodeName.toUpperCase() === "INPUT") 13 | ) { 14 | // Set as original for later 15 | originalActiveElement = document.activeElement; 16 | // Use selected text or all text in the input 17 | text = 18 | document.getSelection().toString().trim() || 19 | document.activeElement.textContent.trim(); 20 | } else { 21 | // If no active text input use any selected text on page 22 | text = document.getSelection().toString().trim(); 23 | } 24 | 25 | if (!text) { 26 | alert( 27 | "No text found. Select this option after right clicking on a textarea that contains text or on a selected portion of text." 28 | ); 29 | return; 30 | } 31 | 32 | showLoadingCursor(); 33 | 34 | // Send the text to the API endpoint 35 | fetch("http://localhost:3000", { 36 | method: "POST", 37 | headers: { 38 | "Content-Type": "application/json", 39 | }, 40 | body: JSON.stringify({ message: text }), 41 | }) 42 | .then((response) => response.json()) 43 | .then(async (data) => { 44 | // Use original text element and fallback to current active text element 45 | const activeElement = 46 | originalActiveElement || 47 | (document.activeElement.isContentEditable && document.activeElement); 48 | 49 | if (activeElement) { 50 | if ( 51 | activeElement.nodeName.toUpperCase() === "TEXTAREA" || 52 | activeElement.nodeName.toUpperCase() === "INPUT" 53 | ) { 54 | // Insert after selection 55 | activeElement.value = 56 | activeElement.value.slice(0, activeElement.selectionEnd) + 57 | `\n\n${data.reply}` + 58 | activeElement.value.slice( 59 | activeElement.selectionEnd, 60 | activeElement.length 61 | ); 62 | } else { 63 | // Special handling for contenteditable 64 | const replyNode = document.createTextNode(`\n\n${data.reply}`); 65 | const selection = window.getSelection(); 66 | 67 | if (selection.rangeCount === 0) { 68 | selection.addRange(document.createRange()); 69 | selection.getRangeAt(0).collapse(activeElement, 1); 70 | } 71 | 72 | const range = selection.getRangeAt(0); 73 | range.collapse(false); 74 | 75 | // Insert reply 76 | range.insertNode(replyNode); 77 | 78 | // Move the cursor to the end 79 | selection.collapse(replyNode, replyNode.length); 80 | } 81 | } else { 82 | // Alert reply since no active text area 83 | alert(`ChatGPT says: ${data.reply}`); 84 | } 85 | 86 | restoreCursor(); 87 | }) 88 | .catch((error) => { 89 | restoreCursor(); 90 | alert( 91 | "Error. Make sure you're running the server by following the instructions on https://github.com/gragland/chatgpt-chrome-extension. Also make sure you don't have an adblocker preventing requests to localhost:3000." 92 | ); 93 | throw new Error(error); 94 | }); 95 | } 96 | }); 97 | 98 | const showLoadingCursor = () => { 99 | const style = document.createElement("style"); 100 | style.id = "cursor_wait"; 101 | style.innerHTML = `* {cursor: wait;}`; 102 | document.head.insertBefore(style, null); 103 | }; 104 | 105 | const restoreCursor = () => { 106 | document.getElementById("cursor_wait").remove(); 107 | }; 108 | --------------------------------------------------------------------------------