Continuing on the journey of browser extension, lets see how a browser extension can help with Email or Website summarization using a Generative AI API integration.
I used NodeJS as my backend to create a API based on VertexAI for summarization. Here is the a documentation to create an API using VertexAI.
Now getting into extension development, basic development content is already available in the blog post here and the important part if the popup.js and content.js
popup.js
document.getElementById("summarizeEmail").addEventListener("click", () => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, { action: "getEmailContent" }, (response) => {
if (response.emailContent) {
chrome.runtime.sendMessage({ action: "summarizeEmail", emailContent: response.emailContent }, (response) => {
document.getElementById("summary").innerText = response.summary;
});
}
});
});
});
document.getElementById("summarizeText").addEventListener("click", () => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.scripting.executeScript(
{
target: { tabId: tabs[0].id },
function: getSelectedText,
},
(results) => {
if (results && results[0] && results[0].result) {
chrome.runtime.sendMessage({ action: "summarizeText", textContent: results[0].result }, (response) => {
document.getElementById("summary").innerText = response.summary;
});
}
}
);
});
});
function getSelectedText() {
return window.getSelection().toString();
}
content.js
// Function to inject the "AI Summary" button into Gmail
const injectAISummaryButton = () => {
const existingButton = document.getElementById("ai-summary-button");
if (existingButton) {
return; // Button already exists, do not add it again
}
const targetElement = document.querySelector(".AO");
if (targetElement) {
const aiSummaryButton = document.createElement("button");
aiSummaryButton.id = "ai-summary-button";
aiSummaryButton.innerText = "AI Summary";
aiSummaryButton.style.position = "absolute";
aiSummaryButton.style.top = "10px";
aiSummaryButton.style.right = "10px";
aiSummaryButton.style.zIndex = 10000;
aiSummaryButton.style.backgroundColor = "#007bff";
aiSummaryButton.style.color = "#ffffff";
aiSummaryButton.style.border = "none";
aiSummaryButton.style.padding = "10px";
aiSummaryButton.style.cursor = "pointer";
aiSummaryButton.addEventListener("click", () => {
const emailContent = getEmailContent();
if (emailContent) {
chrome.runtime.sendMessage({ action: "summarizeEmail", emailContent: emailContent }, (response) => {
showAISummaryOverlay(response.summary);
});
}
});
targetElement.prepend(aiSummaryButton);
}
};
// Function to extract email content from Gmail's DOM
const getEmailContent = () => {
const emailContentElement = document.querySelector(".AO"); // Selector for the email body content
return emailContentElement ? emailContentElement.innerText : "";
};
// Function to create and show the AI Summary overlay
const showAISummaryOverlay = (summary) => {
// Remove existing overlay if present
const existingOverlay = document.getElementById("ai-summary-overlay");
if (existingOverlay) {
existingOverlay.remove();
}
// Create overlay elements
const overlay = document.createElement("div");
overlay.id = "ai-summary-overlay";
overlay.style.position = "fixed";
overlay.style.top = "0";
overlay.style.left = "0";
overlay.style.width = "100%";
overlay.style.height = "100%";
overlay.style.backgroundColor = "rgba(0, 0, 0, 0.7)";
overlay.style.zIndex = 10000;
overlay.style.display = "flex";
overlay.style.alignItems = "center";
overlay.style.justifyContent = "center";
const content = document.createElement("div");
content.style.backgroundColor = "white";
content.style.padding = "20px";
content.style.borderRadius = "10px";
content.style.maxWidth = "500px";
content.style.boxShadow = "0 0 10px rgba(0, 0, 0, 0.5)";
const title = document.createElement("h2");
title.innerText = "AI Summary";
title.style.marginTop = "0";
const summaryText = document.createElement("p");
summaryText.innerText = summary;
const closeButton = document.createElement("button");
closeButton.innerText = "Close";
closeButton.style.marginTop = "20px";
closeButton.style.padding = "10px";
closeButton.style.backgroundColor = "#007bff";
closeButton.style.color = "white";
closeButton.style.border = "none";
closeButton.style.borderRadius = "5px";
closeButton.style.cursor = "pointer";
closeButton.addEventListener("click", () => {
overlay.remove();
});
// Append elements
content.appendChild(title);
content.appendChild(summaryText);
content.appendChild(closeButton);
overlay.appendChild(content);
document.body.appendChild(overlay);
};
// Inject the "AI Summary" button when the content script is loaded
injectAISummaryButton();
// Observe changes in the Gmail DOM to inject the button when necessary
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === "childList") {
injectAISummaryButton();
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
// Listen for messages from the popup or background script
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "getEmailContent") {
const emailContent = getEmailContent();
sendResponse({ emailContent: emailContent });
}
});
background.js
const API_ENDPOINT = "http://localhost:3001/secure/ai";
const getSummary = (content, callback) => {
const encodedContent = encodeURIComponent(content);
fetch(API_ENDPOINT + "/summary", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt: "Please summarize the below uri encoded content. \n\n" + content }),
})
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok " + response.statusText);
}
return response.json();
})
.then((data) => {
if (callback && typeof callback === "function") {
callback(data);
}
})
.catch((error) => {
console.error("Error: " + error);
if (callback && typeof callback === "function") {
callback({ summary: "Error fetching summary." });
}
});
};
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: "summarizeText",
title: "AI Summary",
contexts: ["selection"],
});
});
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === "summarizeText") {
const selectedText = info.selectionText;
console.log("Selected Text: " + selectedText);
getSummary(selectedText, (data) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: (summary) => {
const summaryElement = document.createElement("div");
summaryElement.style.position = "fixed";
summaryElement.style.bottom = "10px";
summaryElement.style.right = "10px";
summaryElement.style.backgroundColor = "white";
summaryElement.style.border = "1px solid black";
summaryElement.style.padding = "10px";
summaryElement.style.zIndex = 10000;
summaryElement.innerText = summary;
document.body.appendChild(summaryElement);
},
args: [data.message],
});
});
}
});
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "summarizeEmail") {
const selectedText = request.emailContent;
console.log("Selected Email Content: " + selectedText);
getSummary(selectedText, (data) => {
sendResponse({ summary: data.message });
});
return true; // Will respond asynchronously.
}
});
Explanation on whats happening,
- Listen to the gmail page for the page loads, Once the page is loaded invoke the injectAISummaryButton in the content.js
- “AI Summary button will be injected at the top right corner of the page”
- A context menu is also added when a text is selected. The click event i registered in the popup.js.
- Upon selecting the text in any web page and clicking on the context menu. or clicking on the “AI Summary” on gmail page will send the data of the email over the api and show the summarized response to the user.
Refer the full chrome extension plugin at GitHub