Data publikacji: 20 lutego 2025 r.
Niezawodne pobieranie dużych modeli AI jest trudnym zadaniem. Jeśli użytkownicy utracą połączenie z internetem lub zamkną Twoją witrynę bądź aplikację internetową, utracą częściowo pobrane pliki modelu i po powrocie na stronę będą musieli zacząć od nowa. Korzystając z interfejsu Background Fetch API jako ulepszenia progresywnego, możesz znacznie zwiększyć wygodę użytkowników.
Rejestrowanie skryptu service worker
Interfejs Background Fetch API wymaga, aby aplikacja zarejestrowała proces roboczy usługi.
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
const registration = await navigator.serviceWorker.register('sw.js');
console.log('Service worker registered for scope', registration.scope);
});
}
Aktywowanie pobierania w tle
Podczas pobierania przeglądarka wyświetla użytkownikowi postęp i umożliwia mu anulowanie pobierania. Po zakończeniu pobierania przeglądarka uruchamia service worker, a aplikacja może podjąć działania na podstawie odpowiedzi.
Interfejs Background Fetch API może nawet przygotować pobieranie do rozpoczęcia w trybie offline. Gdy użytkownik ponownie połączy się z siecią, pobieranie się rozpocznie. Jeśli użytkownik przejdzie w tryb offline, proces zostanie wstrzymany do czasu, aż użytkownik ponownie będzie online.
W poniższym przykładzie użytkownik klika przycisk, aby pobrać model Gemma 2B. Przed pobraniem sprawdzamy, czy model został już pobrany i zapisany w pamięci podręcznej, aby nie używać niepotrzebnych zasobów. Jeśli nie jest w pamięci podręcznej, rozpoczynamy pobieranie w tle.
const FETCH_ID = 'gemma-2b';
const MODEL_URL =
'https://storage.googleapis.com/jmstore/kaggleweb/grader/g-2b-it-gpu-int4.bin';
downloadButton.addEventListener('click', async (event) => {
// If the model is already downloaded, return it from the cache.
const modelAlreadyDownloaded = await caches.match(MODEL_URL);
if (modelAlreadyDownloaded) {
const modelBlob = await modelAlreadyDownloaded.blob();
// Do something with the model.
console.log(modelBlob);
return;
}
// The model still needs to be downloaded.
// Feature detection and fallback to classic `fetch()`.
if (!('BackgroundFetchManager' in self)) {
try {
const response = await fetch(MODEL_URL);
if (!response.ok || response.status !== 200) {
throw new Error(`Download failed ${MODEL_URL}`);
}
const modelBlob = await response.blob();
// Do something with the model.
console.log(modelBlob);
return;
} catch (err) {
console.error(err);
}
}
// The service worker registration.
const registration = await navigator.serviceWorker.ready;
// Check if there's already a background fetch running for the `FETCH_ID`.
let bgFetch = await registration.backgroundFetch.get(FETCH_ID);
// If not, start a background fetch.
if (!bgFetch) {
bgFetch = await registration.backgroundFetch.fetch(FETCH_ID, MODEL_URL, {
title: 'Gemma 2B model',
icons: [
{
src: 'icon.png',
size: '128x128',
type: 'image/png',
},
],
downloadTotal: await getResourceSize(MODEL_URL),
});
}
});
Funkcja getResourceSize() zwraca rozmiar pobierania w bajtach. Możesz to zrobić, wysyłając HEADżądanie.
const getResourceSize = async (url) => {
try {
const response = await fetch(url, { method: 'HEAD' });
if (response.ok) {
return response.headers.get('Content-Length');
}
console.error(`HTTP error: ${response.status}`);
return 0;
} catch (error) {
console.error('Error fetching content size:', error);
return 0;
}
};
Postęp pobierania raportu
Gdy pobieranie w tle się rozpocznie, przeglądarka zwróci wartość BackgroundFetchRegistration.
Możesz użyć tego zdarzenia, aby informować użytkownika o postępach pobierania.progress
bgFetch.addEventListener('progress', (e) => {
// There's no download progress yet.
if (!bgFetch.downloadTotal) {
return;
}
// Something went wrong.
if (bgFetch.failureReason) {
console.error(bgFetch.failureReason);
}
if (bgFetch.result === 'success') {
return;
}
// Update the user about progress.
console.log(`${bgFetch.downloaded} / ${bgFetch.downloadTotal}`);
});
Powiadamianie użytkowników i klienta o zakończeniu pobierania
Gdy pobieranie w tle się powiedzie, usługa Service Worker w Twojej aplikacji otrzyma zdarzenie backgroundfetchsuccess.
Ten kod jest zawarty w usłudze Service Worker. Wywołanie
updateUI()
pod koniec umożliwia zaktualizowanie interfejsu przeglądarki, aby powiadomić użytkownika o pomyślnym pobraniu w tle. Na koniec poinformuj klienta o zakończeniu pobierania, np. za pomocą metody postMessage().
self.addEventListener('backgroundfetchsuccess', (event) => {
// Get the background fetch registration.
const bgFetch = event.registration;
event.waitUntil(
(async () => {
// Open a cache named 'downloads'.
const cache = await caches.open('downloads');
// Go over all records in the background fetch registration.
// (In the running example, there's just one record, but this way
// the code is future-proof.)
const records = await bgFetch.matchAll();
// Wait for the response(s) to be ready, then cache it/them.
const promises = records.map(async (record) => {
const response = await record.responseReady;
await cache.put(record.request, response);
});
await Promise.all(promises);
// Update the browser UI.
event.updateUI({ title: 'Model downloaded' });
// Inform the clients that the model was downloaded.
self.clients.matchAll().then((clientList) => {
for (const client of clientList) {
client.postMessage({
message: 'download-complete',
id: bgFetch.id,
});
}
});
})(),
);
});
Odbieranie wiadomości z procesu service worker
Aby otrzymać na kliencie wysłaną wiadomość o pomyślnym zakończeniu pobierania, nasłuchuj zdarzeń message. Po otrzymaniu wiadomości z procesu roboczego usługi możesz pracować z modelem AI i przechowywać go za pomocą interfejsu Cache API.
navigator.serviceWorker.addEventListener('message', async (event) => {
const cache = await caches.open('downloads');
const keys = await cache.keys();
for (const key of keys) {
const modelBlob = await cache
.match(key)
.then((response) => response.blob());
// Do something with the model.
console.log(modelBlob);
}
});
Anulowanie pobierania w tle
Aby umożliwić użytkownikowi anulowanie trwającego pobierania, użyj metody abort() interfejsu BackgroundFetchRegistration.
const registration = await navigator.serviceWorker.ready;
const bgFetch = await registration.backgroundFetch.get(FETCH_ID);
if (!bgFetch) {
return;
}
await bgFetch.abort();
Zapisywanie modelu w pamięci podręcznej
Pobieranie modeli do pamięci podręcznej, aby użytkownicy pobierali model tylko raz. Interfejs Background Fetch API poprawia proces pobierania, ale w przypadku AI po stronie klienta zawsze warto używać jak najmniejszego modelu.
Te interfejsy API pomagają tworzyć lepsze wrażenia użytkowników związane z AI po stronie klienta.
Prezentacja
Pełną implementację tego podejścia znajdziesz w wersji demonstracyjnej i jej kodzie źródłowym.
Podziękowania
Ten przewodnik został sprawdzony przez François Beauforta, Andre Bandarrę, Sebastiana Benza, Maud Nalpas i Alexandrę Klepper.