// Wllama-compatible OPFS storage with bounded 4 MiB writes instead of one // main-thread/worker round trip per network packet. Metadata format and keys // remain Wllama's, so complete existing cache entries continue to work. // Let session-only inference run even when this browser has no OPFS support. export class UnavailableStorage { isSupported(){return true;} async read(){return null;} async getSize(){return -1;} async list(){return [];} async delete(){} async write(){throw new Error('Model storage is unavailable in this browser. Turn off Save model in browser and try again.');} } export class BufferedOPFS { isSupported(){return !!navigator.storage?.getDirectory;} async directory(){return (await navigator.storage.getDirectory()).getDirectoryHandle('cache',{create:true});} async read(key){try{return await(await(await this.directory()).getFileHandle(key)).getFile();}catch(e){if(e.name==='NotFoundError')return null;throw e;}} async getSize(key){return (await this.read(key))?.size??-1;} async list(){const entries=[];for await(const [key,h]of(await this.directory()).entries())if(h.kind==='file')entries.push({key,size:(await h.getFile()).size});return entries;} async delete(key){try{await(await this.directory()).removeEntry(key);}catch(e){if(e.name!=='NotFoundError')throw e;}} async write(key,stream){ const worker=new Worker(new URL('./cache-worker.js',import.meta.url),{type:'module'}); const call=(data,transfer=[])=>new Promise((resolve,reject)=>{worker.onmessage=({data})=>data.error?reject(new Error(data.error)):resolve();worker.onerror=e=>reject(new Error(e.message));worker.postMessage(data,transfer);}); const reader=stream.getReader();let slab=new Uint8Array(4*1024*1024),used=0,closed=false; try{ await call({action:'open',key}); while(true){ const {done,value}=await reader.read();if(done)break; let offset=0;while(offset{});await call({action:'close'}).catch(()=>{});}reader.releaseLock();worker.terminate();} } } // Retain the existing weight-cache keys so earlier opt-in downloads still work. export class WeightCache { constructor(){const storage=new BufferedOPFS();this.storage=storage.isSupported()?storage:new UnavailableStorage();} async getNameFromURL(url){const hash=await crypto.subtle.digest('SHA-1',new TextEncoder().encode(url));return [...new Uint8Array(hash)].map(b=>b.toString(16).padStart(2,'0')).join('')+'_'+url.split('/').pop();} open(name){return this.storage.read(name);} async list(){ const files=await this.storage.list(),metadata=new Map(); for(const file of files){if(file.key.startsWith('__metadata__')){const blob=await this.storage.read(file.key);try{metadata.set(file.key.slice(12),JSON.parse(await blob.text()));}catch{}}} return files.filter(f=>!f.key.startsWith('__metadata__')).map(f=>({name:f.key,size:f.size,metadata:metadata.get(f.key)||{}})); } async write(name,stream,metadata){await this.storage.write(name,stream);await this.storage.write('__metadata__'+name,new Blob([JSON.stringify(metadata)]).stream());} async deleteMany(predicate){for(const entry of await this.list())if(predicate(entry)){await this.storage.delete(entry.name);await this.storage.delete('__metadata__'+entry.name);}} }