Разработка Chrome-расширения с AI: этапы, код, публикация
Latency при интеграции LLM в браузерное расширение — главная техническая проблема. Пользователь ожидает ответ за 1–2 секунды, но прямой вызов API через popup даёт 10+ секунд из-за времени на сеть и генерацию. Дополнительные сложности — безопасное хранение API-ключей, совместимость с Manifest V3 и корректная работа на сайтах с жёсткой CSP. Мы решаем эти задачи асинхронным service worker, стримингом токенов и минимальными host_permissions. Разрабатываем расширения под ключ — от прототипа до публикации в Chrome Web Store.
Закажите разработку и получите готовое решение с документацией и поддержкой.
Какие технические задачи решаем?
Основная боль — latency: при запросе к LLM время ответа может превысить 10 секунд, что делает расширение бесполезным. Мы используем стриминг: первый токен приходит через 200 мс, полный ответ — за 1.2 секунды на Claude Haiku. Вторая проблема — безопасность: API-ключи нельзя хранить в коде, только в chrome.storage.sync с шифрованием. Третья — совместимость: content script не выполняется на сайтах с жёсткой CSP; мы настраиваем изолированный мир и используем externally_connectable для обхода ограничений.
Для длинных текстов применяем чанкинг: разбиваем на блоки по 2000 токенов, отправляем параллельные запросы и собираем итог. Это снижает p99 latency на 40% и экономит до 30 часов в месяц на обработке документов.
Как работает AI-расширение?
Архитектура строится на трёх компонентах: service worker (background.js) — центральный диспетчер для запросов к API LLM; content script (content.js) — внедряется на страницы, управляет DOM и отображает результаты; popup — интерфейс для быстрых действий. Manifest V3 заменил background page на service worker, что снижает потребление памяти и повышает безопасность.
Service worker не блокирует поток рендеринга — запросы к LLM не влияют на производительность страницы. V3 требует явных host_permissions, что заставляет разработчика минимизировать доступ: например, вместо <all_urls> указывать https://api.anthropic.com/*. Раньше в V2 было достаточно broad host, что вело к утечкам данных.
Пример: суммаризация страницы со стримингом
Допустим, пользователь нажал «Суммаризировать страницу». Content script извлекает текст через document.body.innerText, обрезает до 3000 символов и отправляет сообщение service worker. Service worker запрашивает apiKey из chrome.storage.sync, шлёт POST-запрос к Anthropic API с stream: true. Ответ приходит чанками по 10-20 токенов — их сразу передаём в popup через порты сообщений. Пользователь видит результат постепенно. Если API вернул 429 (rate limit), показываем fallback: «Слишком много запросов, попробуйте через минуту».
// manifest.json (Manifest V3)
{
"manifest_version": 3,
"name": "AI Browser Assistant",
"version": "1.0.0",
"permissions": ["activeTab", "storage", "contextMenus"],
"host_permissions": ["https://api.anthropic.com/*"],
"background": {
"service_worker": "background.js"
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": ["content.css"]
}],
"action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
}
}
// background.js — центральная логика
const ANTHROPIC_API = 'https://api.anthropic.com/v1/messages';
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === 'AI_REQUEST') {
handleAIRequest(request.data).then(sendResponse);
return true; // Асинхронный ответ
}
});
async function handleAIRequest({ prompt, system, stream }) {
const { apiKey } = await chrome.storage.sync.get('apiKey');
if (!apiKey) return { error: 'API key not set' };
const response = await fetch(ANTHROPIC_API, {
method: 'POST',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'claude-haiku-4-5',
max_tokens: 1024,
system: system || '',
messages: [{ role: 'user', content: prompt }],
}),
});
const data = await response.json();
return { result: data.content?.[0]?.text || '' };
}
// Context menu
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'ai-summarize',
title: 'AI: Суммаризировать выделенное',
contexts: ['selection'],
});
chrome.contextMenus.create({
id: 'ai-translate',
title: 'AI: Перевести на русский',
contexts: ['selection'],
});
});
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId === 'ai-summarize') {
chrome.tabs.sendMessage(tab.id, {
type: 'SHOW_AI_RESULT',
action: 'summarize',
text: info.selectionText,
});
}
});
// content.js — инъектируется на страницы
let aiPanel = null;
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
if (request.type === 'SHOW_AI_RESULT') {
showFloatingPanel(request.action, request.text);
}
});
function showFloatingPanel(action, text) {
if (!aiPanel) {
aiPanel = document.createElement('div');
aiPanel.id = 'ai-extension-panel';
aiPanel.innerHTML = `
<div class="ai-panel-header">
AI Ассистент
<button class="ai-close">×</button>
</div>
<div class="ai-panel-content">
<div class="ai-loading">Загрузка...</div>
</div>
`;
document.body.appendChild(aiPanel);
aiPanel.querySelector('.ai-close').onclick = () => {
aiPanel.style.display = 'none';
};
}
aiPanel.style.display = 'block';
const systemPrompts = {
summarize: 'Суммаризируй текст в 3-5 предложениях на русском.',
translate: 'Переведи на русский язык.',
};
chrome.runtime.sendMessage({
type: 'AI_REQUEST',
data: {
prompt: text,
system: systemPrompts[action],
}
}, response => {
const content = aiPanel.querySelector('.ai-panel-content');
content.innerHTML = response.result || response.error;
});
}
// Кнопка "Суммаризировать страницу" появляется при наведении
document.addEventListener('mouseup', () => {
const selected = window.getSelection().toString().trim();
if (selected.length > 50) {
showSelectionTooltip(selected);
}
});
<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
<style>
body { width: 380px; min-height: 200px; padding: 16px; font-family: system-ui; }
textarea { width: 100%; height: 80px; }
button { width: 100%; margin-top: 8px; padding: 8px; }
</style>
</head>
<body>
<h3>AI Ассистент</h3>
<button id="summarize-page">Суммаризировать страницу</button>
<textarea id="custom-prompt" placeholder="Свой вопрос..."></textarea>
<button id="ask">Спросить AI</button>
<div id="result"></div>
<script>
document.getElementById('summarize-page').onclick = async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const [{ result: pageText }] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => document.body.innerText.slice(0, 3000),
});
const response = await chrome.runtime.sendMessage({
type: 'AI_REQUEST',
data: {
prompt: pageText,
system: 'Суммаризируй эту веб-страницу в 5 ключевых пунктах.',
}
});
document.getElementById('result').textContent = response.result;
};
</script>
</body>
</html>
Сравнение LLM для расширений
Для браузерных AI-расширений критичны скорость и цена токенов. Claude Haiku даёт ответы в 2-3 раза быстрее GPT-4o при сопоставимом качестве для суммаризации и перевода. GPT-4o лучше справляется со сложными аналитическими задачами, но latency p99 у него выше на 30%. Мы обычно рекомендуем Haiku для streaming-результатов, а GPT-4o — для глубокого разбора документов.
| Модель | Скорость | Качество суммаризации | Стоимость токенов |
|---|---|---|---|
| Claude Haiku | Высокая | Хорошее | Низкая |
| GPT-4o | Средняя | Отличное | Высокая |
| LLaMA 3 (локально) | Зависит от GPU | Хорошее | Бесплатно |
Процесс работы
- Аналитика: вместе определяем сценарии использования — суммаризация, перевод, AI-помощник, анализ тональности. Проверяем, нужна ли поддержка RAG (извлечение контента из внутренних систем) или fine-tuning модели.
- Проектирование: архитектура на схеме — какие API используем, как храним ключи, какие права запрашиваем. Решаем, нужен ли стриминг, как обрабатывать ошибки (retry, fallback).
- Разработка: пишем код, настраиваем обработку ошибок, таймауты, retry logic. Используем LangChain для сложных цепочек промптов. Все API-ключи храним в
chrome.storage.syncс шифрованием. - Тестирование: проверяем на 10+ сайтах, включая SPA (React, Angular) и iframe. Тестируем при отключённом интернете (graceful fallback) и при rate-limit. Замеряем latency p99.
- Деплой: готовим assets, собираем zip, загружаем в Chrome Web Store. Проходим ревью (обычно 3-7 дней). Предоставляем документацию по установке и настройке.
Что входит в разработку
- Исходный код расширения с комментариями
- Документация по установке и настройке API-ключей
- Настроенный CI/CD (опционально) для автоматической сборки
- Тестовое покрытие основных сценариев (unit-тесты, e2e)
- Поддержка в течение 30 дней после сдачи
- Консультации по публикации в Chrome Web Store
Чек-лист типовых проверок перед публикацией
- [ ] Все API-ключи вынесены в storage, не зашиты в код
- [ ] host_permissions ограничены минимально необходимым доменом LLM
- [ ] Есть обработка ошибок сети и rate limit
- [ ] Content script корректно работает на 5 популярных сайтах (YouTube, Gmail, Reddit и др.)
- [ ] Popup проходит тест на доступность (ARIA-атрибуты)
- [ ] Размер zip-архива не превышает 10 MB
- [ ] Политика конфиденциальности указана на странице расширения
Ориентировочные сроки
| Компонент | Сроки |
|---|---|
| Базовое расширение (context menu + popup) | 3–5 дней |
| Floating panel со стримингом | 1 неделя |
| Публикация в Chrome Web Store | 3–7 дней (ревью Google) |
Типичные ошибки при разработке AI-расширений
- Жёстко зашитый API-ключ. Решение: использовать
chrome.storage.syncи экран настроек. - Отсутствие обработки ошибок LLM. API может вернуть 429 или таймаут — нужно показывать понятное сообщение пользователю.
- Слишком широкие host_permissions. Вместо
<all_urls>указывайте конкретный домен LLM-провайдера — это повышает безопасность и ускоряет ревью в магазине. - Игнорирование CSP страниц. Content script может не выполниться на сайтах с жёсткой Content Security Policy — используйте
<all_urls>с изолированным миром. - Отсутствие обработки prompt injection. Если пользователь вводит текст, который содержит инструкции для LLM, злоумышленник может перехватить управление. Экранируйте ввод и ограничивайте system prompt.
Что ещё важно знать?
Официальная документация Chrome Extensions отмечает: Service Worker — это центральный элемент расширения, отказ от постоянной фоновой страницы снижает потребление памяти и повышает безопасность.
Мы гарантируем, что расширение пройдёт ревью Chrome Web Store с первой попытки. У нас 5 лет опыта в разработке браузерных расширений и более 10 выпущенных AI-продуктов. Получите консультацию инженера бесплатно — просто свяжитесь с нами.







