Voice & Browser8 min read
Building a Context-Aware Chrome AI Extension with Manifest V3 and Gemini
How to detect selected text type (code, email, product, data), serve context-appropriate AI actions in a popup, and build a shopping variant with real-time price analysis
- Chrome Extensions
- Manifest V3
- Gemini
- Service Worker
- TypeScript
- JavaScript
The insight behind this extension is simple: different types of selected text need different AI actions. Selected code should trigger "explain" and "refactor". A selected price should trigger "find cheaper". A selected address should trigger "open in Maps". A generic summarizer misses all of this. The extension classifies the selection first, then offers only relevant actions.
Architecture (Manifest V3)
User selects text on any HTTPS website
↓
Content Script (content.js)
├── Listens to mouseup / selectionchange events
├── Runs context detection heuristics (no API call yet)
└── Sends { selectedText, contextType, pageUrl } to background
↓
Service Worker (background.js — Manifest V3)
├── Receives selection message
├── Calls Gemini API with context-aware system prompt
└── Returns { actions[], analysis, context_type }
↓
UI Layer
├── Popup (popup.html): 3 quick-action buttons near selection
└── Side Panel (sidepanel.html): full analysis + all actionsContext Detection — Before Calling Gemini
Classifying the selection locally (no API call) means the popup appears instantly. Gemini is only called when the user clicks an action:
1// content.js — runs in page context
2function detectContextType(text) {
3 const patterns = {
4 code: [
5 /function\s+\w+\s*\(/,
6 /import\s+.*from/,
7 /def\s+\w+\s*\(/,
8 /\{[^}]{20,}\}/,
9 /=>\s*\{/,
10 ],
11 email: [
12 /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i,
13 /^(Dear|Hi|Hello|To whom)/m,
14 /^(Best regards|Sincerely|Thanks)/m,
15 ],
16 product: [
17 /\$\d+\.\d{2}/, // Price pattern
18 /\b(SKU|Model|Part No\.?|UPC):/i,
19 /\b(in stock|out of stock|ships in)/i,
20 /\b\d+(\.\d+)?\s*(mm|cm|inch|kg|lbs)\b/i, // Specs
21 ],
22 data: [
23 /\t.*\t.*\t/, // Tab-separated values
24 /\d+,\d+,\d+/, // CSV-like
25 /^[A-Z][^.!?]*\|[^|]+\|/m, // Pipe-separated table
26 ],
27 address: [
28 /\b\d{1,5}\s+\w+\s+(Street|St|Avenue|Ave|Road|Rd)/i,
29 /\b[A-Z]{2}\s+\d{5}(-\d{4})?\b/, // US ZIP
30 /\b[A-Z]{1,2}\d{1,2}\s*\d[A-Z]{2}\b/, // UK postcode
31 ],
32 }
33
34 for (const [type, regexes] of Object.entries(patterns)) {
35 const matchCount = regexes.filter(r => r.test(text)).length
36 if (matchCount >= 2) return type
37 if (matchCount === 1 && text.length < 200) return type // Short text — one match is enough
38 }
39
40 return 'general'
41}
42
43document.addEventListener('mouseup', () => {
44 const selected = window.getSelection().toString().trim()
45 if (selected.length > 10) {
46 chrome.runtime.sendMessage({
47 type: 'SELECTION',
48 text: selected,
49 contextType: detectContextType(selected),
50 pageUrl: window.location.hostname,
51 })
52 }
53})Context-Aware Gemini System Prompts
1// background.js (Service Worker)
2const SYSTEM_PROMPTS = {
3 code: `You are a senior software engineer reviewing code.
4Provide: 1) what this code does in one sentence, 2) any bugs or issues,
53) a refactored version if improvements exist. Be specific and technical.`,
6
7 email: `You are a professional communications coach.
8Review this email text for: tone, clarity, grammar, and professionalism.
9Suggest specific improvements. Keep suggestions actionable.`,
10
11 product: `You are a savvy comparison shopper.
12For this product text: extract the key specs, identify what type of product it is,
13and note any concerns (missing warranty info, unclear specs, pricing red flags).`,
14
15 data: `You are a data analyst.
16For this data: identify the structure (what columns/fields exist),
17summarize the key patterns, and highlight any outliers or anomalies.`,
18
19 address: `You are a local knowledge expert.
20For this address/location: provide context (what area it's in, what it's near),
21and suggest useful next actions (Maps directions, area info).`,
22
23 general: `You are a helpful AI assistant.
24Provide a clear, concise summary or explanation of the selected text.
25Identify the key points and any important context.`,
26}
27
28chrome.runtime.onMessage.addListener(async (msg, sender, sendResponse) => {
29 if (msg.type !== 'ANALYZE') return
30
31 const prompt = SYSTEM_PROMPTS[msg.contextType] || SYSTEM_PROMPTS.general
32 const response = await callGemini(prompt, msg.text)
33 sendResponse({ analysis: response, contextType: msg.contextType })
34 return true // Required for async response in Service Worker
35})Shopping Variant — Price Detection and Competitor Comparison
1// Shopping variant: extract structured product data from page
2function extractProductData() {
3 // Try JSON-LD first (most reliable)
4 const jsonLd = document.querySelector('script[type="application/ld+json"]')
5 if (jsonLd) {
6 try {
7 const data = JSON.parse(jsonLd.textContent)
8 if (data['@type'] === 'Product') {
9 return {
10 name: data.name,
11 price: data.offers?.price,
12 currency: data.offers?.priceCurrency,
13 availability: data.offers?.availability,
14 sku: data.sku,
15 source: 'json-ld',
16 }
17 }
18 } catch {}
19 }
20
21 // Fall back to Open Graph meta tags
22 const ogTitle = document.querySelector('meta[property="og:title"]')?.content
23 const ogPrice = document.querySelector('meta[property="product:price:amount"]')?.content
24 if (ogTitle && ogPrice) {
25 return { name: ogTitle, price: parseFloat(ogPrice), source: 'opengraph' }
26 }
27
28 // Heuristic: find price pattern near the selection
29 const priceMatch = document.body.innerText.match(/(£|\$|€)\s*(\d+\.?\d{0,2})/)
30 return priceMatch ? { price: parseFloat(priceMatch[2]), currency: priceMatch[1], source: 'heuristic' } : null
31}Privacy Design
- No background data collection: API calls happen only when the user explicitly clicks an action button — not on every text selection
- Only selected text is sent to Gemini, never the full page content
- The page URL domain (hostname only) is included for context — never the full URL with query parameters
- API key stored in chrome.storage.sync (synced across devices, not logged)
- Usage analytics stored locally only in chrome.storage.local — never sent anywhere
- Context types detected (code, email, product, data, address, general)
- 6
- API calls before user action
- 0
- Manifest V3 with Service Worker (required for Chrome Web Store)
- MV3
- Variants built (general, shopping, personal workspace)
- 3