Chrome's built-in AI APIs
Summarise and translate on the visitor's own machine with Chrome's built-in AI APIs: detect them, ask availability() with the options you will use, create after a click, and fall back where there is no model.
What you'll learn
When you finish, you can do these.- 1Tell which visitors can use Chrome's built-in AI
- 2Ask Chrome if it can summarise, then do it after a click
- 3Translate a message, and fall back where there is no model
- 4Fix a summarizer that fails only for new visitors
A shoe shop's support agent, the person who answers customers, reads their messages on a support page. Some are long and some are in Spanish, so the agent wants three short points and an English version. The shop would rather not send a customer's words to an AI provider, a company that runs models on its own machines. Chrome can do both jobs with a model on the visitor's own computer, here the agent's, so the message stays there.
The Code tab holds that support page: App.vue shows a customer's message with a Summarise and a Translate button, and builtInAi.ts asks Chrome what it can do. By the end it works in desktop Chrome and says why it cannot everywhere else. You need one idea from Securing and testing AI features: a page should tell people what leaves their device.
Which APIs exist and what they need
Before any code, know which visitors have these APIs. Three have been stable on web pages since Chrome 138: Summarizer, Translator and LanguageDetector. The Prompt API, LanguageModel, takes free-form prompts. Writer, Rewriter and Proofreader are developer trials, behind a flag on Chrome's experiments page, so your visitors do not have them.
Translator and Language Detector use expert models, small models built for one job: one that recognises languages, and a language pack per pair of languages. The others use Gemini Nano, a language model that Chrome downloads the first time a site needs it. Nothing you pass to any of them is sent to Google.
Gemini Nano runs only on desktop: Windows, macOS, Linux and Chromebook Plus. The drive that holds the Chrome profile needs 22 GB free, and the machine needs a graphics card with more than 4 GB of memory, or 16 GB of RAM and 4 CPU cores. Chrome on Android and iOS has none of the models.
So plan for three kinds of visitor: Chrome that has or can fetch the model, Chrome on a machine that cannot run it, and every other browser.
Detect, ask, then create after a click
Every one of these APIs is used in the same four steps, shown here on the Summarise button. Detect its global, a name Chrome defines on self, the page's global object. Ask availability() whether it can work here. Call create() after a click to get an instance, the object that does the work. Then use it.
- Detect'Summarizer' in self
- availability()same options as create()
- Clickcreate(), may download
- summarizeStreaming()text arrives in chunks
In builtInAi.ts, the Code tab's second file, the summary's options sit in one object, SUMMARY_OPTIONS: short key points in plain text, from Spanish, written in English. summarizerStatus() asks with that same object, so the question you ask is the question you will act on:
export const SUMMARY_OPTIONS = {
type: 'key-points',
format: 'plain-text',
length: 'short',
expectedInputLanguages: ['es'],
outputLanguage: 'en',
}
export async function summarizerStatus(): Promise<string> {
if (!('Summarizer' in self)) return 'not in this browser'
return Summarizer.availability(SUMMARY_OPTIONS)
}
availability() answers with one of four words, which App.vue prints on its status line. unavailable means this device or these options cannot work, for example a language the model does not support. downloadable, the answer on a first visit in desktop Chrome, means it can work after a download. downloading means that download is under way, and available means you can create one now.
outputLanguage matters too: Gemini Nano only writes in the languages it was tested on, so name the one you want. From Chrome 149 it reads and writes English, Spanish, Japanese, German and French.
create() is where the download starts, and Chrome starts one only after the person has interacted with the page: a click, a tap or a key press. So call create() from summarise() in App.vue, the Summarise button's click handler:
const summarizer = await Summarizer.create({ ...SUMMARY_OPTIONS, monitor: monitor(p => (progress.value = `Downloading the model: ${p}%`)) })
progress.value = ''
source.value = 'this device'
for await (const chunk of summarizer.summarizeStreaming(message.value)) summary.value += chunk
A first download can take a while, so create() gets a monitor, which turns Chrome's download events into a percentage, p, on the page. summarizeStreaming() returns a stream you read with for await, so the points appear under Summary from: this device as the model writes them.
Translate, and fall back where there is no model
The Translate button follows the same four steps, but the Translator needs its options even to answer availability(), because each language pair is a separate download. So translatorStatus() in builtInAi.ts passes this object:
export const TRANSLATE_OPTIONS = { sourceLanguage: 'es', targetLanguage: 'en' }
translate() in App.vue, the Translate button's click handler, creates the translator with the same object and translates the message:
const translator = await Translator.create({ ...TRANSLATE_OPTIONS, monitor: monitor(p => (progress.value = `Downloading the language pack: ${p}%`)) })
translation.value = await translator.translate(message.value)
The English version appears at the bottom of the page. To protect privacy, Chrome reports every pair as downloadable until your site has created a translator for it, so other sites cannot learn which languages a visitor reads.
For visitors with no model, keep the same feature on a server route, a URL on your own server that sends the text to an AI provider. Say which path produced the result: the agent should know whether the message left the computer.
In the Code tab, fallbackSummary() stands in for that route, returning the message's first two sentences once the second Try-it task wires it in. Each API's status shows on the page, so a visitor in Safari reads "not in this browser" instead of meeting an error.
What goes wrong
The first mistake creates the summarizer as soon as the component mounts:
onMounted(async () => {
summarizer = await Summarizer.create(SUMMARY_OPTIONS)
})
On a fresh page load, while the answer is still downloadable, create() fails with NotAllowedError: Requires a user gesture when availability is "downloading" or "downloadable". Nobody has clicked yet. Once the model is downloaded, the same code works, so the bug only hits new visitors. The playground hides it: typing in its editor counts as interacting with the preview. Create it in the click handler instead:
async function summarise() {
const summarizer = await Summarizer.create({ ...SUMMARY_OPTIONS, monitor: monitor(p => (progress.value = `Downloading the model: ${p}%`)) })
}
The second mistake asks a different question than the one you act on:
return Summarizer.availability()
Without options, Chrome answers for the defaults. Say the shop adds Italian, expectedInputLanguages: ['it']: the status line still shows the answer for English, summarise() goes ahead, and the click ends in NotSupportedError: The requested language options are not supported. Ask with the same object and the status says unavailable before anyone clicks, so the fallback shows up front.
return Summarizer.availability(SUMMARY_OPTIONS)
What to remember
- Summarizer, Translator and Language Detector are stable in desktop Chrome. Gemini Nano needs a capable desktop and downloads on first use; phones and other browsers have no model.
- Detect the global, then call
availability()with exactly the options you will pass tocreate(),outputLanguageincluded. - The Translator needs its language pair even for
availability(). Keep a server route for everyone else, and tell the person which path ran. - Call
create()from a click handler, because a download needs the person to have interacted with the page. Show progress withmonitor.
Try it
Open the Code tab and change the starter code. Each task says what you should see.- In
builtInAi.ts, changetargetLanguageto'ja'. In desktop Chrome the line readsTranslator es → ja: downloadable, and Translate to ja fetches a new language pack before the Japanese text appears; then the line readsavailable. Set it to'es', the source language: the line saysunavailableand the button is disabled. Other browsers shownot in this browser. - Make the fallback real. In
summarise(), replace the "No model on this device" line withsummary.value = fallbackSummary(message.value)andsource.value = 'your server (stand-in)'. In Safari, Firefox or a Chrome that cannot run the model, Summarise now shows the first two sentences underSummary from: your server (stand-in). - In
translatorStatus(), deleteTRANSLATE_OPTIONSfrom theTranslator.availability(...)call. In Chrome the line now showsTypeError: Failed to execute 'availability' on 'Translator': 1 argument required, but only 0 present.Put the argument back and the status returns.