├── sib.gif ├── sib.png ├── icon128.png ├── icon16.png ├── icon48.png ├── old ├── icon128.png ├── icon16.png └── icon48.png ├── offscreen.html ├── manifest.json ├── README.md ├── options.js ├── content.js ├── index.html ├── offscreen.js └── background.js /sib.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enesakar/sayitbetter/HEAD/sib.gif -------------------------------------------------------------------------------- /sib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enesakar/sayitbetter/HEAD/sib.png -------------------------------------------------------------------------------- /icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enesakar/sayitbetter/HEAD/icon128.png -------------------------------------------------------------------------------- /icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enesakar/sayitbetter/HEAD/icon16.png -------------------------------------------------------------------------------- /icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enesakar/sayitbetter/HEAD/icon48.png -------------------------------------------------------------------------------- /old/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enesakar/sayitbetter/HEAD/old/icon128.png -------------------------------------------------------------------------------- /old/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enesakar/sayitbetter/HEAD/old/icon16.png -------------------------------------------------------------------------------- /old/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enesakar/sayitbetter/HEAD/old/icon48.png -------------------------------------------------------------------------------- /offscreen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Say It Better", 3 | "version": "1.0", 4 | "manifest_version": 3, 5 | "background": { 6 | "service_worker": "background.js" 7 | }, 8 | "description": "A Chrome extension that prompts ChatGPT to enhance the selected text and enhance your writing skills.", 9 | "action": { 10 | "default_popup": "index.html", 11 | "default_icon": { 12 | "16": "icon16.png", 13 | "48": "icon48.png", 14 | "128": "icon128.png" 15 | } 16 | }, 17 | "icons": { 18 | "16": "icon16.png", 19 | "48": "icon48.png", 20 | "128": "icon128.png" 21 | }, 22 | "permissions": ["offscreen", "clipboardWrite", "contextMenus", "activeTab", "storage", "scripting"] 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Say It Better 3 | 4 | A [Chrome extension](https://chrome.google.com/webstore/detail/say-it-better/jjmeigcgllplllpdnmbbigmepfmofcif) that prompts ChatGPT to enhance the selected text and enhance your writing skills. 5 | 6 | Stack: 7 | - API: OpenAI `gpt-3.5-turbo` 8 | - Backend: [Vercel Edge](https://vercel.com/features/edge-functions) 9 | - Rate limiting and caching: [Upstash Redis](https://upstash.com/) 10 | 11 | ### Usage 12 | - Install [the extension](https://chrome.google.com/webstore/detail/say-it-better/jjmeigcgllplllpdnmbbigmepfmofcif). 13 | 14 | - Select any text in your browser, right click and select `Say It Better`. 15 | 16 | - The improved version of your text will be copied to your clipboard. 17 | 18 | ![alt text](sib.gif "Say It Better") 19 | 20 | -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | const configForm = document.getElementById('configForm'); 3 | const saveButton = document.getElementById('saveButton'); 4 | 5 | // Load saved configuration from chrome storage 6 | chrome.storage.local.get('config', (data) => { 7 | if (data.config) { 8 | const selectedRadio = configForm.querySelector(`input[value="${data.config}"]`); 9 | if (selectedRadio) { 10 | selectedRadio.checked = true; 11 | } 12 | } 13 | else { 14 | const selectedRadio = configForm.querySelector(`input[value="plain"]`); 15 | if (selectedRadio) { 16 | selectedRadio.checked = true; 17 | } 18 | } 19 | }); 20 | 21 | // Save configuration to chrome storage 22 | saveButton.addEventListener('click', () => { 23 | const selectedConfig = configForm.config.value; 24 | chrome.storage.local.set({ config: selectedConfig }, () => { 25 | document.getElementById("sib_saved").innerText = "Tone saved!"; 26 | setTimeout(() => { 27 | window.close(); 28 | }, "600"); 29 | }); 30 | }); 31 | }); 32 | 33 | -------------------------------------------------------------------------------- /content.js: -------------------------------------------------------------------------------- 1 | let popup; 2 | 3 | function createDiv(text) { 4 | if (text) { 5 | popup = document.createElement('div'); 6 | popup.style.width = 'auto'; 7 | popup.style.height = 'auto'; 8 | popup.style.background = 'rgba(0, 0, 0, 0.7)'; 9 | popup.style.color = 'white'; 10 | popup.style.padding = '25px'; 11 | popup.style.borderRadius = '5px'; 12 | popup.style.position = 'fixed'; 13 | popup.style.top = '50%'; 14 | popup.style.left = '50%'; 15 | popup.style.transform = 'translate(-50%, -50%)'; 16 | popup.style.zIndex = '9999'; 17 | 18 | const closeButton = document.createElement("button"); 19 | closeButton.innerText = "X"; 20 | closeButton.style.position = "absolute"; 21 | closeButton.style.top = "0"; 22 | closeButton.style.color = 'white'; 23 | closeButton.style.right = "0"; 24 | closeButton.style.border = "none"; 25 | closeButton.style.background = "none"; 26 | closeButton.style.padding = "10"; 27 | closeButton.style.margin = "10"; 28 | closeButton.style.fontSize = "18px"; 29 | closeButton.style.cursor = "pointer"; 30 | closeButton.onclick = removePopup; 31 | 32 | const content = document.createElement("div"); 33 | content.id = "sayitbetter_popup"; 34 | content.style.whiteSpace = "pre"; 35 | content.innerText = text; 36 | 37 | // popup.appendChild(closeButton); 38 | popup.appendChild(content); 39 | document.body.appendChild(popup); 40 | 41 | // Remove the div when clicked 42 | popup.addEventListener('click', () => { 43 | removePopup(); 44 | }); 45 | } 46 | } 47 | 48 | function removePopup() { 49 | if (popup) { 50 | popup.remove(); 51 | popup = null; 52 | } 53 | } 54 | 55 | function updateDiv(text) { 56 | if (text) { 57 | const newDiv = document.getElementById('sayitbetter_popup'); 58 | newDiv.textContent = text; 59 | 60 | setTimeout(() => { 61 | removePopup(); 62 | }, "1000"); 63 | } 64 | } 65 | 66 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 67 | if (message.action === 'createDiv') { 68 | createDiv(message.text); 69 | } 70 | if (message.action === 'updateDiv') { 71 | updateDiv(message.text); 72 | } 73 | }); 74 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Say It Better! 8 | 66 | 67 | 68 | 69 |
70 |

Select Tone:

71 | 75 | 79 | 83 | 87 | 91 |
92 | 93 |
94 |
95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /offscreen.js: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Once the message has been posted from the service worker, checks are made to 16 | // confirm the message type and target before proceeding. This is so that the 17 | // module can easily be adapted into existing workflows where secondary uses for 18 | // the document (or alternate offscreen documents) might be implemented. 19 | 20 | // Registering this listener when the script is first executed ensures that the 21 | // offscreen document will be able to receive messages when the promise returned 22 | // by `offscreen.createDocument()` resolves. 23 | chrome.runtime.onMessage.addListener(handleMessages); 24 | 25 | // This function performs basic filtering and error checking on messages before 26 | // dispatching the 27 | // message to a more specific message handler. 28 | async function handleMessages(message) { 29 | // Return early if this message isn't meant for the offscreen document. 30 | if (message.target !== 'offscreen-doc') { 31 | return; 32 | } 33 | 34 | // Dispatch the message to an appropriate handler. 35 | switch (message.type) { 36 | case 'copy-data-to-clipboard': 37 | handleClipboardWrite(message.data); 38 | break; 39 | default: 40 | console.warn(`Unexpected message type received: '${message.type}'.`); 41 | } 42 | } 43 | 44 | // We use a