Something went wrong. Try again.
a tiny pet with a GPT-2 brain, living in your browser
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103// tokenling service worker: offline copies of the page, the app and the// runtime, and the owner's opt-in alerts. It is served beside the page so its// scope is the page's directory. The brain is not handled here: transformers.js// keeps it in Cache Storage itself.//// The page tells the worker what to keep ({type: "keep", urls, prune}), so the// worker knows nothing about the site's layout. Kept files are served cache// first: they live under versioned or hashed paths and never change. The page// itself is fetched network first, so a new version arrives as soon as the// visitor is online.const APP_CACHE = "tokenling-app";const ALERT_CACHE = "tokenling-alerts";const alertsUrl = () => new URL("tokenling-alerts.json", self.registration.scope).href;
self.addEventListener("install", () => self.skipWaiting());self.addEventListener("activate", (e) => e.waitUntil(self.clients.claim()));
self.addEventListener("message", (e) => { const m = e.data || {}; if (m.type === "keep") e.waitUntil(keep(m.urls || [], !!m.prune)); else if (m.type === "alerts") e.waitUntil(showDue());});
async function keep(urls, prune) { const cache = await caches.open(APP_CACHE); const want = new Set(urls.filter((u) => u.startsWith(self.registration.scope))); for (const u of want) { if (await cache.match(u)) continue; try { const res = await fetch(u); if (res.ok) await cache.put(u, res); } catch { /* offline: try again next visit */ } } if (!prune) return; for (const r of await cache.keys()) if (!want.has(r.url)) await cache.delete(r);}
const pageKey = (url) => url.split(/[?#]/)[0];
self.addEventListener("fetch", (e) => { const req = e.request; if (req.method !== "GET" || !req.url.startsWith(self.registration.scope)) return; // transformers.js owns the model cache. Do not proxy its long download: // Firefox can end the service worker's response stream before it finishes. // Match the file, not a models/ path: deployments may override MODELS_URL. if (new URL(req.url).pathname.endsWith(".onnx")) return; if (req.mode === "navigate") { e.respondWith(page(req)); return; } e.respondWith(caches.open(APP_CACHE).then((c) => c.match(req.url)).then((hit) => hit || fetch(req)));});
// Network first, falling back to the saved copy offline or after 6 s.async function page(req) { const cache = await caches.open(APP_CACHE); const saved = () => cache.match(pageKey(req.url)).then((r) => r || cache.match(self.registration.scope)); try { const res = await Promise.race([ fetch(req), new Promise((_, no) => setTimeout(() => no(new Error("slow")), 6000)), ]); if (res.ok && (await cache.match(pageKey(req.url)))) await cache.put(pageKey(req.url), res.clone()); return res; } catch (err) { const r = await saved(); if (r) return r; return fetch(req); // nothing saved yet: let the browser show its own error }}
// Alerts: the page leaves a plan of what the pet will need and when; this// shows what is due, unless a tokenling window is in front.async function showDue() { const cache = await caches.open(ALERT_CACHE); const res = await cache.match(alertsUrl()); if (!res) return; const plan = await res.json(); const wins = await self.clients.matchAll({ type: "window", includeUncontrolled: true }); const scope = self.registration.scope; const petPage = (w) => [scope, scope + "index.html"].includes(pageKey(w.url)); if (wins.some((w) => petPage(w) && w.visibilityState === "visible")) return; const now = Date.now(); let changed = false; for (const it of plan.items || []) { if (it.sent || it.at > now) continue; await self.registration.showNotification(it.title, { body: it.body, tag: `tokenling-${it.kind}`, icon: plan.icon }); it.sent = true; changed = true; } if (changed) await cache.put(alertsUrl(), new Response(JSON.stringify(plan), { headers: { "content-type": "application/json" } }));}
self.addEventListener("periodicsync", (e) => { if (e.tag === "tokenling-alerts") e.waitUntil(showDue());});
self.addEventListener("notificationclick", (e) => { e.notification.close(); e.waitUntil((async () => { const wins = await self.clients.matchAll({ type: "window", includeUncontrolled: true }); if (wins[0]) return wins[0].focus(); return self.clients.openWindow(self.registration.scope); })());});