Chrome с версии 138 умеет запускать модель Gemini Nano прямо в браузере — on-device, без сети и без облака. Доступ к ней дают JS-API: Summarizer (выжимка текста), Translator (перевод), Prompt API и другие. Это тоже «локальная модель», только хост — браузер, а не Ollama или llama.cpp.
Что это даёт
- Инференс бесплатный. Модель работает на устройстве пользователя — 0 затрат на API, 0 трафика на бэкенд.
- Приватность. Текст не уходит со страницы.
- Тот же JS-API в Edge (но движок другой — Phi-4-mini вместо Gemini Nano, медленнее). Brave/Opera/Vivaldi публично не подтверждены — форки обычно отключают Google-инфраструктуру.
Но есть нюанс...
- API экспериментальный, всё может поменяться — флаги, имена методов и список языков ещё не стабильны.
- Модель надо скачать руками — 2–4 ГБ, через флаги и (см. ниже). У большинства посетителей её нет.
- Требования к железу: Chrome 138+, desktop, 22 ГБ свободного диска, от 4 ГБ VRAM.
- Summarizer знает 5 языков:
de, en, es, fr, ja. Русского нет — суммаризируем на EN, потом переводим через Translator.
Поэтому фичу стоит добавлять как необязательную надстройку: у кого модель есть — получают выжимку, у кого нет — видят страницу как обычно, без кнопки и без ошибок.
Как включить модель
- → Enabled BypassPerfRequirement
- → Enabled
- Перезапустить Chrome.
- → Optimization Guide On Device Model → Check for update. Когда скачается ~2–4 ГБ, версия перестанет быть
0.0.0.0.
Попробовать прямо здесь
Подключение в проект
API доступен как глобалы Summarizer и Translator — без npm-пакетов, без CDN.
Гейт по доступности
'Summarizer' in self отсеивает браузеры без API (Firefox, Safari, Chrome <138), но глобал есть и без скачанной модели — поэтому дальше нужен availability(). outputLanguage задаёт язык выжимки из 5 поддерживаемых (de, en, es, fr, ja); без него Chrome выбирает сам.
// 1. есть ли API в этом браузере
if (!('Summarizer' in self)) return; // Firefox/Safari/Chrome <138
// 2. есть ли модель (глобал есть и без неё)
const status = await Summarizer.availability({ outputLanguage: "en" });
// "unavailable" | "downloadable" | "downloading" | "available"
const canSummarize = status !== "unavailable";
Создание и вызов
const summarizer = await Summarizer.create({
type: "tldr", // tldr | key-points | teaser | headline
format: "markdown", // markdown | plain-text
length: "short", // short | medium | long
outputLanguage: "en",
sharedContext: "Be concise, active voice, keep facts and numbers.",
monitor(m) {
m.addEventListener("downloadprogress", (e) => {
console.log(`download ${Math.round(e.loaded * 100)}%`);
});
},
});
const summary = await summarizer.summarize(text, { context: "release notes" });
summarizer.destroy(); // освободить модель
Перевод выжимки (RU и др.)
Summarizer не поддерживает русский, поэтому суммаризируем на EN → переводим на язык браузера:
const status = await Translator.availability({
sourceLanguage: "en",
targetLanguage: navigator.language.split("-")[0], // "ru"
});
if (status !== "unavailable") {
const translator = await Translator.create({
sourceLanguage: "en",
targetLanguage: "ru",
});
const translated = await translator.translate(summary);
translator.destroy();
}
Собранные грабли
downloadprogressтикает даже на готовой модели — Chrome шлёт событие сloaded: 0при загрузке в RAM, не из сети. Мелькает «Downloading 0%». Решил показывать прогресс только еслиavailability() !== "available".- Детектор заглушки.
create()проходит,availability()отдаётavailable, аsummarize()возвращает"Model not available in Chromium...". Ловим regex/model not available/i+ проверка на пустой вывод → состояние ошибки, показываем оригинал, а не заглушку. sharedContext— не системный промпт. Влияет на тон мягко; при переводе EN→RU стилевые правки частично теряются. Это маленькая on-device модель, не general-purpose LLM — управляемость ограничена.- Тихая ошибка читается как «ничего не произошло». Если модели нет — показывать явное состояние («AI unavailable») и инструкцию с флагами, а не молча возвращать оригинал.