cache: Cache API primary + OPFS fallback, add storage diagnostics
Browse files
main.js
CHANGED
|
@@ -123,9 +123,10 @@ function setStatus(msg) {
|
|
| 123 |
pill.className = "status-pill " + t;
|
| 124 |
}
|
| 125 |
|
| 126 |
-
// ---
|
| 127 |
-
//
|
| 128 |
-
//
|
|
|
|
| 129 |
const CACHE_DIR = "datause-models";
|
| 130 |
|
| 131 |
function hashKey(str) {
|
|
@@ -137,27 +138,57 @@ function hashKey(str) {
|
|
| 137 |
return (h >>> 0).toString(16).padStart(8, "0");
|
| 138 |
}
|
| 139 |
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
const root = await navigator.storage.getDirectory();
|
| 142 |
return root.getDirectoryHandle(CACHE_DIR, { create: true });
|
| 143 |
}
|
| 144 |
|
| 145 |
-
async function
|
|
|
|
| 146 |
try {
|
| 147 |
-
|
| 148 |
-
const dir = await getCacheDir();
|
| 149 |
const name = hashKey(key);
|
| 150 |
await dir.getFileHandle(name + ".ok"); // marker proves a complete prior write
|
| 151 |
const handle = await dir.getFileHandle(name);
|
| 152 |
const file = await handle.getFile();
|
| 153 |
return await file.arrayBuffer();
|
| 154 |
-
} catch
|
|
|
|
|
|
|
|
|
|
| 155 |
}
|
| 156 |
|
| 157 |
-
async function
|
|
|
|
| 158 |
try {
|
| 159 |
-
|
| 160 |
-
const dir = await getCacheDir();
|
| 161 |
const name = hashKey(key);
|
| 162 |
try { await dir.removeEntry(name + ".ok"); } catch { /* not present yet */ }
|
| 163 |
const handle = await dir.getFileHandle(name, { create: true });
|
|
@@ -165,7 +196,23 @@ async function cacheSet(key, buffer) {
|
|
| 165 |
await writable.write(buffer);
|
| 166 |
await writable.close();
|
| 167 |
await dir.getFileHandle(name + ".ok", { create: true });
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
}
|
| 170 |
|
| 171 |
// --- download with progress + cache -------------------------------------------
|
|
@@ -201,8 +248,7 @@ async function loadFile(url, label) {
|
|
| 201 |
log(`downloaded ${label} (${(received / 1e6).toFixed(1)} MB)`, "ok");
|
| 202 |
showProgress(1, label);
|
| 203 |
cacheSet(url, buffer.buffer)
|
| 204 |
-
.then(() => log(`cached ${label}`, "ok"))
|
| 205 |
-
.catch(() => log(`cache skipped for ${label} (quota?)`, "warn")); // never block on OPFS write
|
| 206 |
return buffer.buffer;
|
| 207 |
}
|
| 208 |
|
|
@@ -568,6 +614,12 @@ async function init() {
|
|
| 568 |
.then((ok) => log(`persistent storage ${ok ? "granted" : "not granted"}`, ok ? "ok" : "warn"))
|
| 569 |
.catch(() => {});
|
| 570 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 571 |
setStatus("Requesting WebGPU adapter…");
|
| 572 |
const adapter = await navigator.gpu.requestAdapter();
|
| 573 |
if (!adapter) { setStatus("WebGPU adapter not found"); log("WebGPU adapter not found", "warn"); return null; }
|
|
|
|
| 123 |
pill.className = "status-pill " + t;
|
| 124 |
}
|
| 125 |
|
| 126 |
+
// --- Browser model cache --------------------------------------------------------
|
| 127 |
+
// Primary: Cache API (same backend transformers.js uses, proven in HF Spaces).
|
| 128 |
+
// Fallback: OPFS, used when the Cache API is unavailable or rejects a write.
|
| 129 |
+
const CACHE_NAME = "datause-models";
|
| 130 |
const CACHE_DIR = "datause-models";
|
| 131 |
|
| 132 |
function hashKey(str) {
|
|
|
|
| 138 |
return (h >>> 0).toString(16).padStart(8, "0");
|
| 139 |
}
|
| 140 |
|
| 141 |
+
// Cache API backend ----------------------------------------------------------
|
| 142 |
+
async function readCacheApi(key) {
|
| 143 |
+
if (typeof caches === "undefined") return null;
|
| 144 |
+
try {
|
| 145 |
+
const cache = await caches.open(CACHE_NAME);
|
| 146 |
+
const resp = await cache.match(key);
|
| 147 |
+
if (!resp) return null;
|
| 148 |
+
return await resp.arrayBuffer();
|
| 149 |
+
} catch (e) {
|
| 150 |
+
log(`cache read failed (Cache API): ${e?.name || e}`, "warn");
|
| 151 |
+
return null;
|
| 152 |
+
}
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
async function writeCacheApi(key, buffer) {
|
| 156 |
+
if (typeof caches === "undefined") return false;
|
| 157 |
+
try {
|
| 158 |
+
const cache = await caches.open(CACHE_NAME);
|
| 159 |
+
await cache.put(key, new Response(buffer));
|
| 160 |
+
return true;
|
| 161 |
+
} catch (e) {
|
| 162 |
+
log(`cache write failed (Cache API): ${e?.name || e}`, "warn");
|
| 163 |
+
return false;
|
| 164 |
+
}
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
// OPFS backend ---------------------------------------------------------------
|
| 168 |
+
async function opfsCacheDir() {
|
| 169 |
const root = await navigator.storage.getDirectory();
|
| 170 |
return root.getDirectoryHandle(CACHE_DIR, { create: true });
|
| 171 |
}
|
| 172 |
|
| 173 |
+
async function readOpfs(key) {
|
| 174 |
+
if (!navigator.storage?.getDirectory) return null;
|
| 175 |
try {
|
| 176 |
+
const dir = await opfsCacheDir();
|
|
|
|
| 177 |
const name = hashKey(key);
|
| 178 |
await dir.getFileHandle(name + ".ok"); // marker proves a complete prior write
|
| 179 |
const handle = await dir.getFileHandle(name);
|
| 180 |
const file = await handle.getFile();
|
| 181 |
return await file.arrayBuffer();
|
| 182 |
+
} catch (e) {
|
| 183 |
+
log(`cache read failed (OPFS): ${e?.name || e}`, "warn");
|
| 184 |
+
return null;
|
| 185 |
+
}
|
| 186 |
}
|
| 187 |
|
| 188 |
+
async function writeOpfs(key, buffer) {
|
| 189 |
+
if (!navigator.storage?.getDirectory) return false;
|
| 190 |
try {
|
| 191 |
+
const dir = await opfsCacheDir();
|
|
|
|
| 192 |
const name = hashKey(key);
|
| 193 |
try { await dir.removeEntry(name + ".ok"); } catch { /* not present yet */ }
|
| 194 |
const handle = await dir.getFileHandle(name, { create: true });
|
|
|
|
| 196 |
await writable.write(buffer);
|
| 197 |
await writable.close();
|
| 198 |
await dir.getFileHandle(name + ".ok", { create: true });
|
| 199 |
+
return true;
|
| 200 |
+
} catch (e) {
|
| 201 |
+
log(`cache write failed (OPFS): ${e?.name || e}`, "warn");
|
| 202 |
+
return false;
|
| 203 |
+
}
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
// Unified entry points -------------------------------------------------------
|
| 207 |
+
async function cacheGet(key) {
|
| 208 |
+
const viaCacheApi = await readCacheApi(key);
|
| 209 |
+
if (viaCacheApi) return viaCacheApi;
|
| 210 |
+
return readOpfs(key);
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
async function cacheSet(key, buffer) {
|
| 214 |
+
const ok = await writeCacheApi(key, buffer);
|
| 215 |
+
if (!ok) await writeOpfs(key, buffer);
|
| 216 |
}
|
| 217 |
|
| 218 |
// --- download with progress + cache -------------------------------------------
|
|
|
|
| 248 |
log(`downloaded ${label} (${(received / 1e6).toFixed(1)} MB)`, "ok");
|
| 249 |
showProgress(1, label);
|
| 250 |
cacheSet(url, buffer.buffer)
|
| 251 |
+
.then(() => log(`cached ${label}`, "ok"));
|
|
|
|
| 252 |
return buffer.buffer;
|
| 253 |
}
|
| 254 |
|
|
|
|
| 614 |
.then((ok) => log(`persistent storage ${ok ? "granted" : "not granted"}`, ok ? "ok" : "warn"))
|
| 615 |
.catch(() => {});
|
| 616 |
}
|
| 617 |
+
log(`env: caches=${typeof caches !== "undefined"} opfs=${!!navigator.storage?.getDirectory} embedded=${window.self !== window.top}`, "ok");
|
| 618 |
+
if (navigator.storage?.estimate) {
|
| 619 |
+
navigator.storage.estimate()
|
| 620 |
+
.then(({ usage, quota }) => log(`storage: ${(usage / 1e6).toFixed(1)} MB used / ${(quota / 1e6).toFixed(1)} MB quota`, "ok"))
|
| 621 |
+
.catch(() => {});
|
| 622 |
+
}
|
| 623 |
setStatus("Requesting WebGPU adapter…");
|
| 624 |
const adapter = await navigator.gpu.requestAdapter();
|
| 625 |
if (!adapter) { setStatus("WebGPU adapter not found"); log("WebGPU adapter not found", "warn"); return null; }
|