├── .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 |
--------------------------------------------------------------------------------