SPB Git

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.8 KB · 59 lines javascript
Raw Blame History
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