spb/chat-spboucher Public
Private universal chat interface over the OpenRouter ecosystem — 400+ models, branching, streaming, usage tracking. Next.js 16 + SQLite, PWA, deployed on m4m64a at chat.spboucher.ai
TypeScript 78.8%
CSS 15.1%
JavaScript 4.9%
Shell 1.2%
1// Author: Simon-Pierre Boucher2// Contact: contact@spboucher.ai3// Project: chat.spboucher.ai45// Service worker: cache the app shell + static assets for instant cold starts.6// Conversation data stays network-first — the server DB is the single source of truth.78const CACHE = "spb-shell-v1";9const SHELL = ["/manifest.webmanifest", "/icons/icon.svg"];1011self.addEventListener("install", (event) => {12 event.waitUntil(caches.open(CACHE).then((c) => c.addAll(SHELL)));13 self.skipWaiting();14});1516self.addEventListener("activate", (event) => {17 event.waitUntil(18 caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))19 );20 self.clients.claim();21});2223self.addEventListener("fetch", (event) => {24 const url = new URL(event.request.url);25 if (event.request.method !== "GET") return;2627 // Never cache API traffic or streams.28 if (url.pathname.startsWith("/api/")) return;2930 // Static assets: cache-first (immutable Next chunks + icons).31 if (url.pathname.startsWith("/_next/static/") || url.pathname.startsWith("/icons/")) {32 event.respondWith(33 caches.match(event.request).then(34 (hit) =>35 hit ||36 fetch(event.request).then((res) => {37 const copy = res.clone();38 caches.open(CACHE).then((c) => c.put(event.request, copy));39 return res;40 })41 )42 );43 return;44 }4546 // Pages: network-first with cache fallback so the shell opens offline.47 event.respondWith(48 fetch(event.request)49 .then((res) => {50 if (res.ok && (url.pathname === "/" || url.pathname === "/login")) {51 const copy = res.clone();52 caches.open(CACHE).then((c) => c.put(event.request, copy));53 }54 return res;55 })56 .catch(() => caches.match(event.request).then((hit) => hit || caches.match("/")))57 );58});59