I’m developing a browser extension called TextCondenser that lets users select text and get condensed versions using AI. The extension is mostly working but I’m having trouble with the AI service integration and managing API costs.
How it should work:
- User selects some text on any webpage
- A “Condense Text” button shows up next to the selection
- Clicking it sends the text to an AI service for condensation
- The result appears in a popup and gets stored in a sidebar panel
Most features are done but the condensation function isn’t working right. Here’s my code:
background.js
chrome.runtime.onInstalled.addListener(() => {
chrome.sidePanel
.setPanelBehavior({ openPanelOnActionClick: true })
.catch((err) => console.error("Panel Setup Error:", err));
});
content.js
document.addEventListener("mouseup", function (evt) {
let highlightedText = window.getSelection().toString().trim();
let currentButton = document.getElementById("condense-btn");
if (!highlightedText) {
if (currentButton) currentButton.remove();
return;
}
if (!currentButton) {
currentButton = document.createElement("button");
currentButton.id = "condense-btn";
currentButton.innerText = "Condense Text";
currentButton.style.position = "absolute";
currentButton.style.zIndex = "9999";
currentButton.style.backgroundColor = "#2196f3";
currentButton.style.color = "white";
currentButton.style.border = "none";
currentButton.style.padding = "10px";
currentButton.style.cursor = "pointer";
currentButton.style.borderRadius = "4px";
document.body.appendChild(currentButton);
}
let bounds = window.getSelection().getRangeAt(0).getBoundingClientRect();
currentButton.style.top = `${bounds.bottom + window.scrollY + 8}px`;
currentButton.style.left = `${bounds.left + window.scrollX}px`;
currentButton.onclick = async function () {
currentButton.innerText = "Processing...";
let condensed = await condenseContent(highlightedText);
alert("Condensed Version:\n" + condensed);
currentButton.remove();
};
document.addEventListener("click", function hideBtn(evt) {
if (!currentButton.contains(evt.target)) {
currentButton.remove();
document.removeEventListener("click", hideBtn);
}
});
});
async function condenseContent(content) {
const token = "Your-API-Token-Here";
const endpoint = "https://api.openai.com/v1/chat/completions";
try {
const result = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: `Make this shorter: ${content}` }],
max_tokens: 80
})
});
if (!result.ok) {
let errorData = await result.text();
console.error("Request Failed:", errorData);
throw new Error("Request Failed: " + errorData);
}
const responseData = await result.json();
return responseData.choices?.[0]?.message?.content || "Failed to condense text.";
} catch (err) {
console.error("Condensation failed:", err);
return "Failed to condense text.";
}
}
manifest.json
{
"manifest_version": 3,
"name": "TextCondenser",
"version": "1.0",
"description": "Condense and store text snippets.",
"permissions": ["storage", "sidePanel"],
"action": {
"default_icon": {
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"side_panel": {
"default_path": "panel.html"
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_end"
}
]
}
panel.html
<!DOCTYPE html>
<html>
<head>
<title>Condensed Text History</title>
<style>
body {
font-family: sans-serif;
padding: 15px;
width: 320px;
}
.text-item {
border-bottom: 1px solid #ccc;
padding: 12px;
margin-bottom: 8px;
}
.remove-btn {
background-color: #e53e3e;
color: white;
border: none;
padding: 6px;
cursor: pointer;
}
</style>
</head>
<body>
<h2>Condensed History</h2>
<div id="text-history"></div>
<script src="panel.js"></script>
</body>
</html>
panel.js
document.addEventListener("DOMContentLoaded", function () {
const historyContainer = document.getElementById("text-history");
function refreshHistory() {
chrome.storage.local.get(["condensedTexts"], function (data) {
historyContainer.innerHTML = "";
const texts = data.condensedTexts || [];
texts.forEach((text, idx) => {
showTextItem(text, idx);
});
});
}
function showTextItem(text, idx) {
const itemDiv = document.createElement("div");
itemDiv.classList.add("text-item");
itemDiv.innerHTML = `
<p>${text}</p>
<button class="remove-btn" data-index="${idx}">Remove</button>
`;
itemDiv.querySelector(".remove-btn").addEventListener("click", function () {
removeTextItem(idx);
});
historyContainer.appendChild(itemDiv);
}
function removeTextItem(idx) {
chrome.storage.local.get(["condensedTexts"], function (data) {
let texts = data.condensedTexts || [];
texts.splice(idx, 1);
chrome.storage.local.set({ condensedTexts: texts }, function () {
refreshHistory();
});
});
}
refreshHistory();
});
Any ideas what might be wrong with my condensation logic?