self.importScripts('https://cdn.jsdelivr.net/npm/localforage@1.10.0/dist/localforage.min.js') localforage.config({name:'localforage',storeName:'keyvaluepairs'}) const TKEY='threads_v1' const now=()=>Date.now() const gid=()=>Math.random().toString(36).slice(2,9) const titleFrom=t=>(String(t||'').replace(/\s+/g,' ').trim().slice(0,60)||'Untitled') async function loadThreads(){const v=await localforage.getItem(TKEY);return Array.isArray(v)?v:[]} async function saveThreads(v){await localforage.setItem(TKEY,v)} async function pickLatestThread(threads){if(!threads.length)return null;let best=threads[0],bu=+best.updatedAt||0;for(const t of threads){const u=+t.updatedAt||0;if(u>bu){best=t;bu=u}}return best} async function ensureThreadForWrite(reqMeta){ let threads=await loadThreads() let th=await pickLatestThread(threads) if(!th){ const id=gid() const firstUser=(reqMeta.messages||[]).find(m=>m&&m.role==='user')?.content||'' th={id,title:titleFrom(firstUser),pinned:false,updatedAt:now(),messages:[]} threads.unshift(th) await saveThreads(threads) } return th.id } async function appendAssistantPlaceholder(threadId,meta){ let threads=await loadThreads() let th=threads.find(t=>t.id===threadId) if(!th){th={id:threadId,title:'Untitled',pinned:false,updatedAt:now(),messages:[]};threads.unshift(th)} const mid='sw_'+gid() const msg={id:mid,role:'assistant',content:'',sune_name:meta?.sune_name||'',model:meta?.model||'',avatar:meta?.avatar||'',sw:true} th.messages=[...(Array.isArray(th.messages)?th.messages:[]),msg] th.updatedAt=now() await saveThreads(threads) return mid } async function updateAssistantContent(threadId,mid,content){ let threads=await loadThreads() const th=threads.find(t=>t.id===threadId) if(!th)return const i=(th.messages||[]).findIndex(m=>m&&m.id===mid) if(i<0)return th.messages[i].content=content th.updatedAt=now() await saveThreads(threads) } async function parseAndPersist(stream,threadId,mid){ const reader=stream.getReader() const dec=new TextDecoder('utf-8') let buf='',full='',lastWrite=0 const flush=async(force=false)=>{const t=Date.now();if(force||t-lastWrite>250){await updateAssistantContent(threadId,mid,full);lastWrite=t}} while(true){ const {value,done}=await reader.read() if(done)break buf+=dec.decode(value,{stream:true}) let idx while((idx=buf.indexOf('\n\n'))!==-1){ const chunk=buf.slice(0,idx).trim() buf=buf.slice(idx+2) if(!chunk)continue if(chunk.startsWith('data:')){ const data=chunk.slice(5).trim() if(data==='[DONE]'){await flush(true);return} try{ const json=JSON.parse(data) const d=json&&json.choices&&json.choices[0]&&json.choices[0].delta&&json.choices[0].delta.content||'' if(d){full+=d;await flush(false)} }catch(_){} } } } await flush(true) } async function handleOpenRouterEvent(event){ const req=event.request const url=new URL(req.url) const isTarget=url.hostname==='openrouter.ai'&&/\/api\/v1\/chat\/completions$/.test(url.pathname) if(!isTarget)return fetch(req) let reqMeta={} try{const t=await req.clone().text();reqMeta=JSON.parse(t||'{}')}catch(_){} if(!reqMeta||!reqMeta.stream){return fetch(req)} const res=await fetch(req) if(!res.body)return res const [toClient,toTap]=res.body.tee() event.waitUntil((async()=>{ try{ const msgs=Array.isArray(reqMeta.messages)?reqMeta.messages:[] const meta={sune_name:'',model:reqMeta.model||'',avatar:''} const threadId=await ensureThreadForWrite({messages:msgs}) const mid=await appendAssistantPlaceholder(threadId,meta) await parseAndPersist(toTap,threadId,mid) }catch(_){} })()) const headers=new Headers(res.headers) return new Response(toClient,{status:res.status,statusText:res.statusText,headers}) } self.addEventListener('install',e=>{self.skipWaiting()}) self.addEventListener('activate',e=>{e.waitUntil(self.clients.claim())}) self.addEventListener('fetch',e=>{e.respondWith(handleOpenRouterEvent(e))})