All articles

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 actions

Context 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:

javascript
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

javascript
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

javascript
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

Request a discovery call

30 minutesGoogle MeetFree