Some checks failed
Build & Push Docker Image / build-and-push (push) Failing after 11s
Tonemark is a SvelteKit PWA for transcribing YouTube videos, audio and video files, and microphone recordings using a local Whisper backend. Features: - Dark glassmorphic UI with electric-lime accent (5 switchable themes) - Rail nav (desktop) / tab bar (mobile) layout - Drop zone, YouTube URL input, and live audio recording inputs - Audio mode waveform cards (none / standard / aggressive / auto) - Real-time transcription progress with animated waveform - Job queue with SSE streaming updates - Push notifications on job completion - PWA with native SvelteKit service worker - SRT / TXT / MD / JSON transcript downloads Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
96 lines
2.7 KiB
TypeScript
96 lines
2.7 KiB
TypeScript
/// <reference no-default-lib="true"/>
|
|
/// <reference lib="esnext" />
|
|
/// <reference lib="webworker" />
|
|
/// <reference types="@sveltejs/kit" />
|
|
|
|
import { build, files, version } from '$service-worker';
|
|
|
|
declare const self: ServiceWorkerGlobalScope;
|
|
|
|
const CACHE = `tonemark-${version}`;
|
|
const ASSETS = [...build, ...files];
|
|
|
|
self.addEventListener('install', (event) => {
|
|
event.waitUntil(
|
|
caches.open(CACHE).then((cache) => cache.addAll(ASSETS))
|
|
);
|
|
self.skipWaiting();
|
|
});
|
|
|
|
self.addEventListener('activate', (event) => {
|
|
event.waitUntil(
|
|
caches.keys().then((keys) =>
|
|
Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
|
|
)
|
|
);
|
|
self.clients.claim();
|
|
});
|
|
|
|
self.addEventListener('fetch', (event) => {
|
|
if (event.request.method !== 'GET') return;
|
|
|
|
event.respondWith(
|
|
(async () => {
|
|
const url = new URL(event.request.url);
|
|
const cache = await caches.open(CACHE);
|
|
|
|
// Serve cached assets instantly
|
|
if (ASSETS.includes(url.pathname)) {
|
|
const cached = await cache.match(url.pathname);
|
|
if (cached) return cached;
|
|
}
|
|
|
|
// Network-first for everything else
|
|
try {
|
|
const response = await fetch(event.request);
|
|
if (response instanceof Response && response.status === 200) {
|
|
cache.put(event.request, response.clone());
|
|
}
|
|
return response;
|
|
} catch {
|
|
const cached = await cache.match(event.request);
|
|
if (cached) return cached;
|
|
throw new Error('Offline and no cached response');
|
|
}
|
|
})()
|
|
);
|
|
});
|
|
|
|
// ── Push notifications ──────────────────────────────────────────────────────
|
|
|
|
self.addEventListener('push', (event) => {
|
|
const data = (event.data?.json() as { jobId?: string; title?: string; body?: string } | null) ?? {};
|
|
const title = data.title ?? 'Tonemark';
|
|
const body = data.body ?? 'Your transcript is ready.';
|
|
const jobId = data.jobId;
|
|
|
|
event.waitUntil(
|
|
self.registration.showNotification(title, {
|
|
body,
|
|
icon: '/icons/android-icon-192.png',
|
|
badge: '/icons/android-icon-192.png',
|
|
tag: jobId ? `job-${jobId}` : 'tonemark',
|
|
data: { jobId }
|
|
})
|
|
);
|
|
});
|
|
|
|
self.addEventListener('notificationclick', (event) => {
|
|
event.notification.close();
|
|
const jobId = (event.notification.data as { jobId?: string } | null)?.jobId;
|
|
const url = jobId ? `/jobs/${jobId}` : '/';
|
|
|
|
event.waitUntil(
|
|
self.clients
|
|
.matchAll({ type: 'window', includeUncontrolled: true })
|
|
.then((clients) => {
|
|
const target = clients.find((c) =>
|
|
jobId ? c.url.includes(`/jobs/${jobId}`) : c.url === self.location.origin + '/'
|
|
);
|
|
if (target) return target.focus();
|
|
return self.clients.openWindow(url);
|
|
})
|
|
);
|
|
});
|
|
|