I Built an AI Powered Google Extension in 15 Minutes
Build 'Super Summarizer', a Chrome extension that uses ChatGPT to condense web pages into three main points with a readability score. Learn about creating files, setting up permissions, and working with APIs.
Planted August 14, 2023
I Built an AI Powered Google Extension in 15 Minutes
This blog post accompanies the YouTube video: Watch on YouTube
Ever wanted to quickly understand the key points of a lengthy article or webpage? In this tutorial, we’ll build “Super Summarizer” - a Chrome extension that uses ChatGPT to extract the three main points from any webpage and provide a readability score.
Overview
We’ll create a Chrome extension that:
- Extracts text content from web pages
- Sends it to OpenAI’s ChatGPT API
- Returns three key points and a readability score
- Works on any webpage with a simple click
Project Structure
super-summarizer/├── manifest.json├── popup.html├── popup.js├── content.js└── styles.css
Setting Up the Manifest
The manifest.json
file defines our extension’s configuration:
{ "manifest_version": 3, "name": "Super Summarizer", "version": "1.0", "description": "AI-powered webpage summarizer using ChatGPT", "permissions": [ "activeTab", "storage" ], "host_permissions": [ "https://api.openai.com/*" ], "action": { "default_popup": "popup.html", "default_title": "Super Summarizer" }, "content_scripts": [ { "matches": ["<all_urls>"], "js": ["content.js"] } ]}
Key Permissions Explained
- activeTab: Access to the currently active tab
- storage: Store API keys and preferences
- host_permissions: Make requests to OpenAI API
- content_scripts: Inject JavaScript to extract page content
Creating the Popup Interface
popup.html
<!DOCTYPE html><html><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body> <div class="container"> <h1>🚀 Super Summarizer</h1>
<div id="setup-section"> <h3>Setup API Key</h3> <input type="password" id="api-key" placeholder="Enter OpenAI API Key"> <button id="save-key">Save</button> <p class="info">Get your API key from <a href="https://platform.openai.com/api-keys" target="_blank">OpenAI</a></p> </div>
<div id="summarizer-section" style="display: none;"> <button id="summarize-btn">📝 Summarize This Page</button>
<div id="loading" style="display: none;"> <p>🤖 AI is analyzing the page...</p> </div>
<div id="results" style="display: none;"> <h3>Summary</h3> <div id="summary-content"></div>
<h3>Readability Score</h3> <div id="readability-score"></div>
<button id="reset-btn">Analyze Another Page</button> </div>
<div id="error" style="display: none;"> <p class="error-message"></p> </div> </div> </div>
<script src="popup.js"></script></body></html>
styles.css
body { width: 350px; padding: 0; margin: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}
.container { padding: 20px;}
h1 { color: #2563eb; text-align: center; margin-bottom: 20px; font-size: 18px;}
input { width: 100%; padding: 10px; border: 1px solid #d1d5db; border-radius: 6px; margin-bottom: 10px; box-sizing: border-box;}
button { background: #2563eb; color: white; border: none; padding: 10px 15px; border-radius: 6px; cursor: pointer; font-size: 14px; width: 100%; margin-bottom: 10px;}
button:hover { background: #1d4ed8;}
.info { font-size: 12px; color: #6b7280; text-align: center;}
.info a { color: #2563eb;}
#results { background: #f8fafc; padding: 15px; border-radius: 6px; margin-top: 15px;}
.summary-point { background: white; padding: 10px; margin: 5px 0; border-radius: 4px; border-left: 3px solid #2563eb;}
.readability-score { font-size: 24px; font-weight: bold; text-align: center; padding: 10px; border-radius: 6px;}
.score-excellent { background: #10b981; color: white; }.score-good { background: #f59e0b; color: white; }.score-poor { background: #ef4444; color: white; }
.error-message { color: #ef4444; background: #fef2f2; padding: 10px; border-radius: 6px;}
#loading { text-align: center; color: #6b7280;}
Content Script for Page Analysis
content.js
// Content script to extract page textfunction extractPageContent() { // Remove script and style elements const elementsToRemove = document.querySelectorAll('script, style, nav, header, footer, aside'); elementsToRemove.forEach(el => el.remove());
// Get main content const contentSelectors = [ 'main', 'article', '[role="main"]', '.content', '.post-content', '.article-content' ];
let mainContent = null; for (const selector of contentSelectors) { mainContent = document.querySelector(selector); if (mainContent) break; }
// Fallback to body if no main content found const textSource = mainContent || document.body;
// Extract and clean text let text = textSource.innerText || textSource.textContent || '';
// Clean up the text text = text .replace(/\s+/g, ' ') // Replace multiple spaces with single space .replace(/\n\s*\n/g, '\n') // Remove empty lines .trim();
// Limit text length for API efficiency (approximately 3000 words) if (text.length > 12000) { text = text.substring(0, 12000) + '...'; }
return { text: text, title: document.title, url: window.location.href };}
// Listen for messages from popupchrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.action === 'extractContent') { const content = extractPageContent(); sendResponse(content); }});
Popup Logic and API Integration
popup.js
class SuperSummarizer { constructor() { this.apiKey = null; this.initializeUI(); this.loadApiKey(); }
async initializeUI() { // DOM elements this.setupSection = document.getElementById('setup-section'); this.summarizerSection = document.getElementById('summarizer-section'); this.apiKeyInput = document.getElementById('api-key'); this.saveKeyBtn = document.getElementById('save-key'); this.summarizeBtn = document.getElementById('summarize-btn'); this.loadingDiv = document.getElementById('loading'); this.resultsDiv = document.getElementById('results'); this.errorDiv = document.getElementById('error'); this.resetBtn = document.getElementById('reset-btn');
// Event listeners this.saveKeyBtn.addEventListener('click', () => this.saveApiKey()); this.summarizeBtn.addEventListener('click', () => this.summarizePage()); this.resetBtn.addEventListener('click', () => this.resetResults());
// Enter key on API key input this.apiKeyInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') this.saveApiKey(); }); }
async loadApiKey() { const result = await chrome.storage.sync.get(['openai_api_key']); if (result.openai_api_key) { this.apiKey = result.openai_api_key; this.showSummarizerSection(); } }
async saveApiKey() { const apiKey = this.apiKeyInput.value.trim(); if (!apiKey) { this.showError('Please enter a valid API key'); return; }
// Test API key if (await this.testApiKey(apiKey)) { await chrome.storage.sync.set({ openai_api_key: apiKey }); this.apiKey = apiKey; this.showSummarizerSection(); } else { this.showError('Invalid API key. Please check and try again.'); } }
async testApiKey(apiKey) { try { const response = await fetch('https://api.openai.com/v1/models', { headers: { 'Authorization': `Bearer ${apiKey}` } }); return response.ok; } catch (error) { return false; } }
showSummarizerSection() { this.setupSection.style.display = 'none'; this.summarizerSection.style.display = 'block'; }
async summarizePage() { try { this.showLoading();
// Get current tab content const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
// Extract content from the page const content = await chrome.tabs.sendMessage(tab.id, { action: 'extractContent' });
if (!content || !content.text) { throw new Error('Could not extract content from this page'); }
// Send to OpenAI API const summary = await this.callOpenAI(content);
// Display results this.displayResults(summary, content.title);
} catch (error) { this.showError(error.message || 'Failed to summarize page'); } finally { this.hideLoading(); } }
async callOpenAI(content) { const prompt = `Please analyze the following webpage content and provide:1. Three key points (bullet points)2. A readability score from 1-10 (10 being easiest to read)
Content:${content.text}
Please respond in this JSON format:{ "keyPoints": ["point 1", "point 2", "point 3"], "readabilityScore": 7, "readabilityDescription": "Easy to read"}`;
const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}` }, body: JSON.stringify({ model: 'gpt-3.5-turbo', messages: [ { role: 'system', content: 'You are a helpful assistant that summarizes web content and provides readability scores.' }, { role: 'user', content: prompt } ], max_tokens: 500, temperature: 0.7 }) });
if (!response.ok) { throw new Error(`API request failed: ${response.status}`); }
const data = await response.json(); const aiResponse = data.choices[0].message.content;
try { return JSON.parse(aiResponse); } catch (error) { // Fallback parsing if JSON is malformed return this.parseAIResponse(aiResponse); } }
parseAIResponse(response) { // Fallback parsing for non-JSON responses const lines = response.split('\n'); const keyPoints = []; let readabilityScore = 5;
lines.forEach(line => { if (line.includes('•') || line.includes('-') || line.includes('1.') || line.includes('2.') || line.includes('3.')) { keyPoints.push(line.replace(/^[•\-\d\.]\s*/, '').trim()); } if (line.toLowerCase().includes('readability') && /\d+/.test(line)) { const match = line.match(/\d+/); if (match) readabilityScore = parseInt(match[0]); } });
return { keyPoints: keyPoints.slice(0, 3), readabilityScore: readabilityScore, readabilityDescription: this.getReadabilityDescription(readabilityScore) }; }
getReadabilityDescription(score) { if (score >= 8) return 'Very Easy to Read'; if (score >= 6) return 'Easy to Read'; if (score >= 4) return 'Moderate Difficulty'; return 'Difficult to Read'; }
displayResults(summary, pageTitle) { const summaryContent = document.getElementById('summary-content'); const readabilityDiv = document.getElementById('readability-score');
// Display key points summaryContent.innerHTML = summary.keyPoints .map(point => `<div class="summary-point">• ${point}</div>`) .join('');
// Display readability score const scoreClass = this.getScoreClass(summary.readabilityScore); readabilityDiv.innerHTML = ` <div class="readability-score ${scoreClass}"> ${summary.readabilityScore}/10 - ${summary.readabilityDescription} </div> `;
this.resultsDiv.style.display = 'block'; }
getScoreClass(score) { if (score >= 7) return 'score-excellent'; if (score >= 4) return 'score-good'; return 'score-poor'; }
showLoading() { this.loadingDiv.style.display = 'block'; this.resultsDiv.style.display = 'none'; this.errorDiv.style.display = 'none'; }
hideLoading() { this.loadingDiv.style.display = 'none'; }
showError(message) { this.errorDiv.querySelector('.error-message').textContent = message; this.errorDiv.style.display = 'block'; this.resultsDiv.style.display = 'none'; }
resetResults() { this.resultsDiv.style.display = 'none'; this.errorDiv.style.display = 'none'; }}
// Initialize the extensiondocument.addEventListener('DOMContentLoaded', () => { new SuperSummarizer();});
Installation and Testing
Loading the Extension
- Open Chrome and navigate to
chrome://extensions/
- Enable “Developer mode” in the top right
- Click “Load unpacked” and select your extension folder
- The extension should appear in your extensions list
Getting an OpenAI API Key
- Visit OpenAI’s API Keys page
- Create an account or sign in
- Generate a new API key
- Copy and paste it into the extension
Testing the Extension
- Navigate to any article or blog post
- Click the extension icon in the toolbar
- Enter your API key (first time only)
- Click “Summarize This Page”
- Wait for the AI analysis to complete
Enhancements and Ideas
Possible Improvements
- Save summaries - Store previous summaries locally
- Export functionality - Export summaries as PDF or text
- Multiple AI models - Support for different AI providers
- Custom prompts - Allow users to customize the analysis prompt
- Batch processing - Summarize multiple tabs at once
Advanced Features
// Add to popup.js for saving summariesasync saveSummary(summary, pageTitle, url) { const summaries = await chrome.storage.local.get(['saved_summaries']) || { saved_summaries: [] };
summaries.saved_summaries.unshift({ id: Date.now(), title: pageTitle, url: url, summary: summary, timestamp: new Date().toISOString() });
// Keep only last 50 summaries summaries.saved_summaries = summaries.saved_summaries.slice(0, 50);
await chrome.storage.local.set(summaries);}
Security Considerations
- API Key Storage - Store securely using Chrome’s storage API
- Content Security Policy - Properly configure CSP in manifest
- Permissions - Request only necessary permissions
- Error Handling - Don’t expose sensitive information in errors
- Rate Limiting - Implement client-side rate limiting for API calls
Additional Resources
- Watch the video on YouTube
- Chrome Extension Documentation
- OpenAI API Documentation
- Chrome Extension Samples
Next Steps
- Publish to Chrome Web Store
- Add support for other browsers (Firefox, Edge)
- Implement offline caching
- Add user preferences and customization
- Create a options page for advanced settings
This post is part of my YouTube tutorial series. Subscribe to my channel for more tutorials!