├── .gitignore ├── .env.example ├── popup.js ├── popup.html ├── background.js ├── manifest.json ├── package.json ├── server.js ├── README.md ├── vertex.js └── scripts └── complete.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | .env -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | GOOGLE_CLIENT_ID= 2 | GOOGLE_CLIENT_EMAIL= 3 | GOOGLE_PRIVATE_KEY= 4 | GOOGLE_TOKEN_URL= -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", function () { 2 | chrome.runtime.sendMessage({ 3 | ai_complete: true, 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AI Complete 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.onMessage.addListener(() => { 2 | chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { 3 | chrome.scripting 4 | .executeScript({ 5 | target: { tabId: tabs[0].id }, 6 | files: ["scripts/complete.js"], 7 | }) 8 | .then(() => { 9 | console.log("Executed script"); 10 | }); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Whatsapp AI Chrome Extension", 4 | "description": "Chrome extension that uses google's Vertex AI to give you AI auto completion for your texts in whatsapp web :) ", 5 | "version": "1.0", 6 | "action": { 7 | "default_popup": "popup.html" 8 | }, 9 | "permissions": ["activeTab", "declarativeContent", "scripting"], 10 | "background": { 11 | "service_worker": "background.js" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whatsapp-ai", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "cors": "^2.8.5", 14 | "dotenv": "^16.3.1", 15 | "express": "^4.18.2", 16 | "google-auth-library": "^8.9.0", 17 | "nodemon": "^3.0.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cors = require("cors"); 3 | const app = express(); 4 | const { autoComplete } = require("./vertex.js"); 5 | 6 | app.use(express.json()); 7 | app.use( 8 | cors({ 9 | origin: "*", 10 | }) 11 | ); 12 | 13 | app.post("/api/ai_complete", async (req, res) => { 14 | try { 15 | const { history, output_user } = req.body; 16 | const data = await autoComplete(history, output_user); 17 | return res.json(data); 18 | } catch (error) { 19 | console.error(error); 20 | } 21 | }); 22 | 23 | app.listen(5000, () => { 24 | console.log("Server is running on port 5000"); 25 | }); 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Whatsapp-AI-Extension 2 | Chrome extension that uses google's Vertex AI to give you AI auto completion for your texts in whatsapp web :) 3 | 4 | Video link: https://www.youtube.com/watch?v=4d855aFlUIc 5 | 6 | To use this extension, first copy the `.env.example` file and rename it to `.env` 7 | 8 | You have to log into google cloud console and enable the 'Google Vertex API' under the credentials and create a new API key. 9 | Download the JSON file for the API key and fill in the necessary fields in the .env file 10 | 11 | Once you have done that, install the necessary dependencies for the project by running `npm install` at the root of the project. 12 | 13 | Run `node server.js` to start up the backend which will handle the actual AI API call to vertex 14 | 15 | Now you may head into your chrome browser and go to the url `chrome://extensions`. Click on the 'Load unpacked' button and choose the project directory. 16 | This will load in the extension locally into chrome. Now you may head into your extensions list and you will find the extension. 17 | 18 | You will have to click on the extension once everytime you enter whatsapp web in order to trigger the button to show up under the text input 19 | -------------------------------------------------------------------------------- /vertex.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const { GoogleAuth } = require("google-auth-library"); 3 | require("dotenv").config(); 4 | 5 | const PROJECT_ID = "roadsmart-1412"; 6 | 7 | const autoComplete = async (history, output_user) => { 8 | const auth = new GoogleAuth({ 9 | credentials: { 10 | client_id: process.env.GOOGLE_CLIENT_ID, 11 | client_email: process.env.GOOGLE_CLIENT_EMAIL, 12 | private_key: process.env.GOOGLE_PRIVATE_KEY, 13 | token_url: process.env.GOOGLE_TOKEN_URL, 14 | }, 15 | scopes: "https://www.googleapis.com/auth/cloud-platform", 16 | }); 17 | const token = await auth.getAccessToken(); 18 | const headers = { 19 | Authorization: `Bearer ${token}`, 20 | "Content-Type": "application/json", 21 | }; 22 | 23 | const body = JSON.stringify({ 24 | instances: [ 25 | { 26 | prompt: ` 27 | you are an ai embedded in an AI powered chat app. I will pass you a context of the 20 latest messages of the chat labelled by who said it, and you will give me options on how to appropriately respond in the next message, provide 3 options, each option delimited by a line break character so that i can parse the options easily. 28 | 29 | example: 30 | 31 | Elliott: yo are you coming to the dinner tnight?\n 32 | Sithu: where and when?\n 33 | Timothy: hmm i need to check with my parents\n 34 | Elliott: we're going to holland village for some drinks\n 35 | Timothy: 36 | 37 | output: 38 | * I'll check with them and let you know 39 | * Hmm I think i'll pass for tonight 40 | * Alright let's go! I love holland village 41 | 42 | be sure that the output is in the format of a bulleted list, and that the output is a string with the options delimited by a line break character. 43 | there should not be backticks in the output, and the output should not be wrapped in a code block. 44 | 45 | ${history.map((h) => `${h.user}: ${h.text}`).join("\n")} 46 | ${output_user}: 47 | `, 48 | }, 49 | ], 50 | parameters: { 51 | temperature: 1, 52 | maxOutputTokens: 256, 53 | topK: 40, 54 | topP: 0.95, 55 | }, 56 | }); 57 | 58 | try { 59 | const response = await axios.post( 60 | `https://us-central1-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/us-central1/publishers/google/models/text-bison:predict`, 61 | body, 62 | { 63 | headers, 64 | } 65 | ); 66 | 67 | if (response.status === 200) { 68 | const data = response.data; 69 | return data; 70 | } else { 71 | console.error(response.data); 72 | } 73 | } catch (error) { 74 | console.error(error); 75 | } 76 | }; 77 | 78 | module.exports = { 79 | autoComplete, 80 | }; 81 | -------------------------------------------------------------------------------- /scripts/complete.js: -------------------------------------------------------------------------------- 1 | function getAIComplete(button_el) { 2 | var you = ""; 3 | var chat_history = Array.from( 4 | document 5 | .querySelector('[data-testid="conversation-panel-body"]') 6 | .querySelectorAll("[data-pre-plain-text]") 7 | ) 8 | .map((row) => traverseRow(row)) 9 | .filter((row) => row !== undefined); 10 | 11 | fetch("http://localhost:5000/api/ai_complete", { 12 | method: "POST", 13 | headers: { 14 | "Content-Type": "application/json", 15 | }, 16 | body: JSON.stringify({ 17 | history: chat_history, 18 | output_user: you, 19 | }), 20 | }) 21 | .then((response) => { 22 | return response.json(); 23 | }) 24 | .then((data) => { 25 | button_el.innerHTML = "AI Complete"; 26 | const predictions = data.predictions[0].content 27 | .split("\n") 28 | .map((row) => row.replace("* ", "")); 29 | let textinput = document.querySelector("[data-testid='compose-box']"); 30 | textinput.style.position = "relative"; 31 | const option_box = document.createElement("div"); 32 | option_box.style = `position: absolute;transform: translateY(-200%);left: 51px;background-color: white;padding: 6px;border: 2px solid black;border-radius: 5px;z-index: 999;display: flex;flex-direction: column;gap: 4px;`; 33 | const del_button = document.createElement("div"); 34 | del_button.style = `position:absolute;top: -12px;left: -12px;background-color: black;border-radius: 50%;width: 24px;color: white;height: 24px;display: grid;place-items: center;cursor: pointer;`; 35 | del_button.innerHTML = "X"; 36 | option_box.appendChild(del_button); 37 | del_button.onclick = () => { 38 | option_box.remove(); 39 | }; 40 | predictions.forEach((prediction) => { 41 | const prediction_box = document.createElement("span"); 42 | prediction_box.style = `padding:0.5rem;background-color: rgb(217, 253, 211);color: black; cursor: pointer;-webkit-user-select: all;-ms-user-select: all;user-select: all;`; 43 | prediction_box.onclick = () => { 44 | // copy to clipboard 45 | navigator.clipboard.writeText(prediction); 46 | }; 47 | prediction_box.innerHTML = prediction; 48 | option_box.appendChild(prediction_box); 49 | }); 50 | textinput.appendChild(option_box); 51 | }) 52 | .catch(console.error); 53 | } 54 | 55 | let compose_box = document.querySelector('[data-testid="compose-box"]'); 56 | let ai_button = document.createElement("div"); 57 | compose_box.appendChild(ai_button); 58 | ai_button.style = ` 59 | text-align: center; 60 | margin: 1rem; 61 | `; 62 | let ai_text = document.createElement("span"); 63 | ai_text.innerHTML = "AI Complete"; 64 | ai_button.appendChild(ai_text); 65 | ai_text.style = ` 66 | background-color: blue; 67 | color: white; 68 | padding: 3px 6px; 69 | cursor: pointer; 70 | border-radius: 4px; 71 | `; 72 | ai_text.onclick = () => { 73 | ai_text.innerHTML = "AI Completing... Please wait..."; 74 | getAIComplete(ai_text); 75 | }; 76 | 77 | function traverseRow(element) { 78 | const user = element 79 | .getAttribute("data-pre-plain-text") 80 | .split("] ")[1] 81 | .replace(": ", "") 82 | .trim(); 83 | if ( 84 | window.getComputedStyle(element.parentNode.parentNode).backgroundColor === 85 | "rgb(217, 253, 211)" 86 | ) { 87 | you = user; 88 | } 89 | const data = { 90 | user: user, 91 | text: "", 92 | }; 93 | traverseDiv(element, 0, data); 94 | if (data.text !== "") { 95 | return data; 96 | } 97 | } 98 | 99 | function traverseDiv(element, number, data) { 100 | if (number === 30) return; 101 | if (element.hasChildNodes()) { 102 | const childNodes = element.childNodes; 103 | for (let i = 0; i < childNodes.length; i++) { 104 | const childNode = childNodes[i]; 105 | if (childNode.nodeType === Node.ELEMENT_NODE) { 106 | traverseDiv(childNode, number + 1, data); 107 | } else if (childNode.nodeType === Node.TEXT_NODE) { 108 | // if (childNode.parentNode.hasAttribute("data-testid")) { 109 | // if (childNode.parentNode.getAttribute("data-testid") === "author") { 110 | // data.author = childNode.textContent; 111 | // } 112 | // } 113 | if (childNode.parentNode.parentNode.hasAttribute("dir")) { 114 | if (childNode.parentNode.parentNode.getAttribute("dir") === "ltr") { 115 | data.text = childNode.textContent; 116 | } 117 | } 118 | } 119 | } 120 | } 121 | } 122 | --------------------------------------------------------------------------------