UNPKG

ai-text-popover

Version:

A lightweight, plug-and-play JavaScript utility that allows users to **select text** on a webpage and get a simple explanation for it using Groq's LLMs. Perfect for **glossary-style popovers**, educational tools, or AI-assisted documentation.

157 lines (156 loc) 6.31 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.initAIPopover = initAIPopover; function initAIPopover({ apiKey, className = "ai-popover", modelName = "llama-3.3-70b-versatile", // default model }) { // for Next JS , React JS frameworks. if (typeof window === "undefined" || typeof document === "undefined") return; const popoverId = "ai-popover"; const numbersOfListtoSave = 10; // Prevent double injection if (document.getElementById(popoverId)) return; // Inject popover styles const style = document.createElement("style"); if (className !== "ai-popover") { style.innerHTML = ` .${className} { position: absolute !important; z-index: 9999; display: none; } `; } else { style.innerHTML = ` #${popoverId} { position: absolute; max-width: 300px; max-height: 450px; overflow: auto; background: white; color: black; padding: 10px; border: 1px solid #ccc; border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); z-index: 9999; display: none; font-family: inherit; } `; } document.head.appendChild(style); // Create popover element const popover = document.createElement("div"); popover.id = popoverId; popover.className = className; document.body.appendChild(popover); // Cache functions function getCachedData() { try { const data = localStorage.getItem("aiTextPopover"); return data ? JSON.parse(data) : []; } catch (_a) { return []; } } function setDatatoLocalStorage(key, value) { let cache = getCachedData(); cache = cache.filter((item) => item.key !== key); // Remove duplicates cache.unshift({ key, value }); if (cache.length > numbersOfListtoSave) { cache = cache.slice(0, numbersOfListtoSave); } localStorage.setItem("aiTextPopover", JSON.stringify(cache)); console.log("Cached:", key); } function getDatatoLocalStorage(key) { const cache = getCachedData(); const found = cache.find((item) => item.key === key); return found ? found.value : null; } // Selection handler document.addEventListener("mouseup", () => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e; const selection = window.getSelection(); if (!selection || selection.rangeCount === 0) return; const text = selection.toString().trim(); console.log("Selected text:", text); if (!text || text.length < 2) { console.log("Selection too short. Hiding."); popover.style.display = "none"; return; } const range = selection.getRangeAt(0); const rect = range.getBoundingClientRect(); // Position the popover popover.style.left = `${rect.left + window.scrollX}px`; popover.style.top = `${rect.bottom + window.scrollY + 8}px`; popover.innerText = "Loading..."; console.log("Showing popover"); popover.style.display = "block"; // Check localStorage first const cached = getDatatoLocalStorage(text); if (cached) { console.log("Using cached result"); popover.innerText = cached; return; } // Fetch explanation from API try { const res = yield fetch("https://api.groq.com/openai/v1/chat/completions", { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: modelName, messages: [ { role: "user", content: `Explain this in simple terms:\n\n"${text}"`, }, ], }), }); const data = yield res.json(); let reply = ((_c = (_b = (_a = data === null || data === void 0 ? void 0 : data.choices) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.content) || "No response from AI."; // ✅ Handle deprecated model errors gracefully if ((_e = (_d = data === null || data === void 0 ? void 0 : data.error) === null || _d === void 0 ? void 0 : _d.message) === null || _e === void 0 ? void 0 : _e.includes("model")) { console.error("Model deprecated or unavailable:", modelName); reply = `⚠️ The model "${modelName}" is no longer available. Please update to the latest supported model.`; } console.log("AI Response:", reply); popover.innerText = reply; setDatatoLocalStorage(text, reply); } catch (err) { console.error("Fetch error:", err); popover.innerText = "Error fetching AI response."; } })); // Hide on input focus document.addEventListener("focusin", (e) => { const target = e.target; if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) { console.log("Hiding popover due to focusin"); popover.style.display = "none"; } }); }