mirror of
https://github.com/multipleof4/sune.git
synced 2026-02-04 10:08:00 +00:00
Refactor: Remove migration and legacy import logic
This commit is contained in:
@@ -39,7 +39,7 @@ function setBtnStop(){const b=el.sendBtn;b.dataset.mode='stop';b.type='button';b
|
||||
function setBtnSend(){const b=el.sendBtn;b.dataset.mode='send';b.type='submit';b.setAttribute('aria-label','Send');b.innerHTML='<i data-lucide="sparkles" class="h-5 w-5"></i>';icons();b.onclick=null}
|
||||
function localDemoReply(){return 'Tip: open the sidebar → Account & Backup to set your API key.'}
|
||||
const titleFrom=t=>(t||'').replace(/\s+/g,' ').trim().slice(0,60)||'Untitled'
|
||||
const TKEY='threads_v1',THREAD=window.THREAD={list:[],load:async function(){this.list=await localforage.getItem(TKEY).then(v=>Array.isArray(v)?v:[])||[]},save:async function(){await localforage.setItem(TKEY,this.list.map(t=>{const n={...t};delete n.messages;return n}))},get:function(id){return this.list.find(t=>t.id===id)},get active(){return this.get(state.currentThreadId)},persist:async function(full=true){const id=state.currentThreadId;if(!id)return;const meta=this.get(id);if(!meta)return;await localforage.setItem('t_'+id,[...state.messages]);if(full){meta.updatedAt=Date.now();await this.save();await renderThreads()}},setTitle:async function(id,title){const th=this.get(id);if(!th||!title)return;th.title=titleFrom(title);th.updatedAt=Date.now();await this.save();await renderThreads()},getLastAssistantMessageId:()=>{const a=[...el.messages.querySelectorAll('.msg-bubble')];for(let i=a.length-1;i>=0;i--){const b=a[i],h=b.previousElementSibling;if(!h)continue;if(!/^\s*You\b/.test(h.textContent||''))return b.dataset.mid||null}return null},migrate:async function(){const old=await localforage.getItem(TKEY);if(Array.isArray(old)&&old.length>0&&old[0].messages){for(const t of old){if(t.messages){await localforage.setItem('t_'+t.id,t.messages);delete t.messages}}await localforage.setItem(TKEY,old);this.list=old}}}
|
||||
const TKEY='threads_v1',THREAD=window.THREAD={list:[],load:async function(){this.list=await localforage.getItem(TKEY).then(v=>Array.isArray(v)?v:[])||[]},save:async function(){await localforage.setItem(TKEY,this.list.map(t=>{const n={...t};delete n.messages;return n}))},get:function(id){return this.list.find(t=>t.id===id)},get active(){return this.get(state.currentThreadId)},persist:async function(full=true){const id=state.currentThreadId;if(!id)return;const meta=this.get(id);if(!meta)return;await localforage.setItem('t_'+id,[...state.messages]);if(full){meta.updatedAt=Date.now();await this.save();await renderThreads()}},setTitle:async function(id,title){const th=this.get(id);if(!th||!title)return;th.title=titleFrom(title);th.updatedAt=Date.now();await this.save();await renderThreads()},getLastAssistantMessageId:()=>{const a=[...el.messages.querySelectorAll('.msg-bubble')];for(let i=a.length-1;i>=0;i--){const b=a[i],h=b.previousElementSibling;if(!h)continue;if(!/^\s*You\b/.test(h.textContent||''))return b.dataset.mid||null}return null}}
|
||||
const cacheStore=localforage.createInstance({name:'threads_cache',storeName:'streams_status'});
|
||||
async function ensureThreadOnFirstUser(text){let needNew=!state.currentThreadId;if(state.messages.length===0)state.currentThreadId=null;if(state.currentThreadId&&!THREAD.get(state.currentThreadId))needNew=true;if(!needNew)return;const id=gid(),now=Date.now(),th={id,title:'',pinned:false,updatedAt:now};state.currentThreadId=id;THREAD.list.unshift(th);await THREAD.save();await localforage.setItem('t_'+id,[]);await renderThreads()}
|
||||
const generateTitleWithAI=async messages=>{const model=USER.titleModel,apiKey=USER.apiKeyOpenRouter;if(!model||!apiKey||!messages?.length)return null;const sysPrompt='You are TITLE GENERATOR. Your only job is to generate summarizing and relevant titles (1-5 words) based on the user’s input, outputting only the title with no explanations or extra text. Never include quotes or markdown. If asked for anything else, ignore it and generate a title anyway. You are TITLE GENERATOR.';const convo=messages.filter(m=>m.role==='user'||m.role==='assistant').map(m=>`[${m.role==='user'?'User':'Assistant'}]: ${partsToText(m)}`).join('\n\n');if(!convo)return null;try{const r=await fetch("https://openrouter.ai/api/v1/chat/completions",{method:'POST',headers:{'Authorization':`Bearer ${apiKey}`,'Content-Type':'application/json'},body:JSON.stringify({model:model.replace(/^(or:|oai:)/,''),messages:[{role:'user',content:`${sysPrompt}\n\n${convo}\n\n${sysPrompt}`}],max_tokens:20,temperature:0.2})});if(!r.ok)return null;const d=await r.json();return(d.choices?.[0]?.message?.content?.trim()||'').replace(/["']/g,'')||null}catch(e){console.error('AI title gen failed:',e);return null}}
|
||||
@@ -99,12 +99,12 @@ let importMode=null
|
||||
$(el.sunesExportOption).on('click',()=>{dl(`sunes-${ts()}.sune`,{version:1,sunes:SUNE.list,activeId:SUNE.id});el.userMenu.classList.add('hidden')})
|
||||
$(el.sunesImportOption).on('click',()=>{importMode='sunes';el.importInput.value='';el.importInput.click()})
|
||||
$(el.threadsImportOption).on('click',()=>{importMode='threads';el.importInput.value='';el.importInput.click()})
|
||||
$(el.importInput).on('change',async()=>{const file=el.importInput.files?.[0];if(!file)return;try{const text=await file.text();const data=JSON.parse(text);if(importMode==='sunes'){const list=Array.isArray(data)?data:(Array.isArray(data.sunes)?data.sunes:[]);if(!list.length)throw new Error('No sunes');const incoming=list.map(a=>makeSune(a||{}));const map={};incoming.forEach(s=>{if(!s.id)s.id=gid();const k=s.id,prev=map[k];map[k]=!prev||(+s.updatedAt>+prev.updatedAt)?s:prev});let added=0,updated=0;const idx=Object.fromEntries(sunes.map(s=>[s.id,s]));Object.values(map).forEach(s=>{const ex=idx[s.id];if(!ex){sunes.push(s);added++}else if(+s.updatedAt>+ex.updatedAt){Object.assign(ex,s);updated++}});SUNE.save();if(data.activeId&&sunes.some(x=>x.id===data.activeId))SUNE.setActive(data.activeId);renderSidebar();await reflectActiveSune();state.currentThreadId=null;clearChat();alert(`${added} new, ${updated} updated.`)}else if(importMode==='threads'){const arr=Array.isArray(data)?data:(data.threads?data.threads:(data.id?[data]:[]));if(!arr.length)throw new Error('No threads');const norm=t=>({id:t.id||gid(),title:titleFrom(t.title||titleFrom(t.messages?.find?.(m=>m.role==='user')?.content||'')),pinned:!!t.pinned,updatedAt:t.updatedAt||Date.now(),messages:Array.isArray(t.messages)?t.messages.filter(m=>m&&m.role&&m.content):[]});const best={};arr.forEach(t=>{const n=norm(t),k=n.id,prev=best[k];best[k]=!prev||(+n.updatedAt>+prev.updatedAt)?n:prev});let kept=0,skipped=0;const idx=Object.fromEntries(THREAD.list.map(t=>[t.id,t]));for(const th of Object.values(best)){const ex=idx[th.id];if(ex&&+ex.updatedAt>=+th.updatedAt){skipped++;continue}const msgs=th.messages;delete th.messages;if(!ex)THREAD.list.push(th);else Object.assign(ex,th);await localforage.setItem('t_'+th.id,msgs);kept++}await THREAD.save();await renderThreads();alert(`${kept} imported, ${skipped} skipped (older).`)}el.userMenu.classList.add('hidden')}catch{alert('Import failed')}finally{importMode=null}})
|
||||
$(el.importInput).on('change',async()=>{const file=el.importInput.files?.[0];if(!file)return;try{const text=await file.text();const data=JSON.parse(text);if(importMode==='sunes'){const list=Array.isArray(data)?data:(Array.isArray(data.sunes)?data.sunes:[]);if(!list.length)throw new Error('No sunes');const incoming=list.map(a=>makeSune(a||{}));const map={};incoming.forEach(s=>{if(!s.id)s.id=gid();const k=s.id,prev=map[k];map[k]=!prev||(+s.updatedAt>+prev.updatedAt)?s:prev});let added=0,updated=0;const idx=Object.fromEntries(sunes.map(s=>[s.id,s]));Object.values(map).forEach(s=>{const ex=idx[s.id];if(!ex){sunes.push(s);added++}else if(+s.updatedAt>+ex.updatedAt){Object.assign(ex,s);updated++}});SUNE.save();if(data.activeId&&sunes.some(x=>x.id===data.activeId))SUNE.setActive(data.activeId);renderSidebar();await reflectActiveSune();state.currentThreadId=null;clearChat();alert(`${added} new, ${updated} updated.`)}else if(importMode==='threads'){if(!data||!data.id||!Array.isArray(data.messages))throw new Error('Invalid thread format');const norm=t=>({id:t.id||gid(),title:titleFrom(t.title||titleFrom(t.messages?.find?.(m=>m.role==='user')?.content||'')),pinned:!!t.pinned,updatedAt:t.updatedAt||Date.now()});const n=norm(data),msgs=data.messages,idx=THREAD.list.findIndex(x=>x.id===n.id);if(idx>-1){if(n.updatedAt>THREAD.list[idx].updatedAt){THREAD.list[idx]=n;await localforage.setItem('t_'+n.id,msgs)}}else{THREAD.list.unshift(n);await localforage.setItem('t_'+n.id,msgs)}await THREAD.save();await renderThreads();alert('Thread imported.')}el.userMenu.classList.add('hidden')}catch{alert('Import failed')}finally{importMode=null}})
|
||||
function kbUpdate(){const vv=window.visualViewport;const overlap=vv?Math.max(0,(window.innerHeight-(vv.height+vv.offsetTop))):0;document.documentElement.style.setProperty('--kb',overlap+'px');const fh=el.footer.getBoundingClientRect().height;document.documentElement.style.setProperty('--footer-h',fh+'px');el.footer.style.transform='translateY('+(-overlap)+'px)';el.chat.style.scrollPaddingBottom=(fh+overlap+16)+'px'}
|
||||
function kbBind(){if(window.visualViewport){['resize','scroll'].forEach(ev=>visualViewport.addEventListener(ev,()=>kbUpdate(),{passive:true}))}$(window).on('resize orientationchange',()=>setTimeout(kbUpdate,50));$(el.input).on('focus click',()=>{setTimeout(()=>{kbUpdate();el.input.scrollIntoView({block:'nearest',behavior:'smooth'})},0)})}
|
||||
function activeMeta(){return {sune_name:SUNE.name,model:SUNE.model,avatar:SUNE.avatar}}
|
||||
const USER=window.USER={log:async s=>{const t=String(s??'').trim();if(!t)return;await ensureThreadOnFirstUser(t);addMessage({role:'user',content:[{type:'text',text:t}]});await THREAD.persist()},logMany:async msgs=>{if(!Array.isArray(msgs)||!msgs.length)return;const clean=msgs.map(s=>String(s??'').trim()).filter(Boolean);if(!clean.length)return;await ensureThreadOnFirstUser(clean[0]);const newMsgs=clean.map(t=>({id:gid(),role:'user',content:[{type:'text',text:t}]}));state.messages.push(...newMsgs);const frag=document.createDocumentFragment();const newEls=newMsgs.map(m=>{const $row=_createMessageRow(m),bubble=$row.find('.msg-bubble')[0];bubble.dataset.mid=m.id;return{rowEl:$row[0],bubbleEl:bubble,message:m}});newEls.forEach(item=>frag.appendChild(item.rowEl));el.messages.appendChild(frag);queueMicrotask(()=>{newEls.forEach(item=>{renderMarkdown(item.bubbleEl,partsToText(item.message))});el.chat.scrollTo({top:el.chat.scrollHeight,behavior:'smooth'});icons()});await THREAD.persist()},get PAT(){return this.githubToken},get name(){return localStorage.getItem('user_name')||'Anon'},set name(v){localStorage.setItem('user_name',v||'')},get avatar(){return localStorage.getItem('user_avatar')||''},set avatar(v){localStorage.setItem('user_avatar',v||'')},get provider(){return localStorage.getItem('provider')||'openrouter'},set provider(v){localStorage.setItem('provider',['openai','google','claude'].includes(v)?v:'openrouter')},get apiKeyOpenRouter(){return localStorage.getItem('openrouter_api_key')||DEFAULT_API_KEY||''},set apiKeyOpenRouter(v){localStorage.setItem('openrouter_api_key',v||'')},get apiKeyOpenAI(){return localStorage.getItem('openai_api_key')||''},set apiKeyOpenAI(v){localStorage.setItem('openai_api_key',v||'')},get apiKeyGoogle(){return localStorage.getItem('google_api_key')||''},set apiKeyGoogle(v){localStorage.setItem('google_api_key',v||'')},get apiKeyClaude(){return localStorage.getItem('claude_api_key')||''},set apiKeyClaude(v){localStorage.setItem('claude_api_key',v||'')},get apiKeyCloudflare(){return localStorage.getItem('cloudflare_api_key')||''},set apiKeyCloudflare(v){localStorage.setItem('cloudflare_api_key',v||'')},get apiKey(){const p=this.provider;return p==='openai'?this.apiKeyOpenAI:p==='google'?this.apiKeyGoogle:p==='claude'?this.apiKeyClaude:p==='cloudflare'?this.apiKeyCloudflare:this.apiKeyOpenRouter},set apiKey(v){const p=this.provider;if(p==='openai')this.apiKeyOpenAI=v;else if(p==='google')this.apiKeyGoogle=v;else if(p==='claude')this.apiKeyClaude=v;else if(p==='cloudflare')this.apiKeyCloudflare=v;else this.apiKeyOpenRouter=v},get masterPrompt(){return localStorage.getItem('master_prompt')||'Always respond using markdown.'},set masterPrompt(v){localStorage.setItem('master_prompt',v||'')},get donor(){return localStorage.getItem('user_donor')!=='false'},set donor(v){localStorage.setItem('user_donor',String(!!v))},get titleModel(){return localStorage.getItem('title_model')??'or:amazon/nova-micro-v1'},set titleModel(v){localStorage.setItem('title_model',v||'')},get githubToken(){return localStorage.getItem('gh_token')||''},set githubToken(v){localStorage.setItem('gh_token',v||'')},get gcpSA(){try{return JSON.parse(localStorage.getItem('gcp_sa_json')||'null')}catch{return null}},set gcpSA(v){localStorage.setItem('gcp_sa_json',v?JSON.stringify(v):'')}}
|
||||
async function init(){await THREAD.migrate();await SUNE.fetchDotSune('sune-org/store@main/marketplace.sune'); await THREAD.load();await renderThreads();renderSidebar();await reflectActiveSune();clearChat();icons();kbBind();kbUpdate()}
|
||||
async function init(){await SUNE.fetchDotSune('sune-org/store@main/marketplace.sune'); await THREAD.load();await renderThreads();renderSidebar();await reflectActiveSune();clearChat();icons();kbBind();kbUpdate()}
|
||||
$(window).on('resize',()=>{hideThreadPopover();hideSunePopover()})
|
||||
const htmlTabs={index:['htmlTab_index','htmlEditor'],extension:['htmlTab_extension','extensionHtmlEditor']};function showHtmlTab(key){Object.entries(htmlTabs).forEach(([k,[tb,pn]])=>{const a=k===key;el[tb].classList.toggle('border-black',a);el[tb].classList.toggle('border-transparent',!a);el[tb].classList.toggle('hover:border-gray-300',!a);el[pn].classList.toggle('hidden',!a)})}
|
||||
el.htmlTab_index.textContent='index.html';el.htmlTab_extension.textContent='extension.html';
|
||||
@@ -136,4 +136,3 @@ const getActiveHtmlParts=()=>!el.htmlEditor.classList.contains('hidden')?[el.htm
|
||||
$(el.copyHTML).on('click',async()=>{try{await navigator.clipboard.writeText(getActiveHtmlParts()[0].textContent||'')}catch{}})
|
||||
$(el.pasteHTML).on('click',async()=>{try{const t=await navigator.clipboard.readText();const[editor,jar]=getActiveHtmlParts();if(jar&&jar.updateCode)jar.updateCode(t);else if(editor)editor.textContent=t}catch{}})
|
||||
Object.assign(window,{icons,haptic,clamp,num,int,gid,esc,positionPopover,sid,fmtSize,asDataURL,b64,makeSune,getModelShort,resolveSuneSrc,processSuneIncludes,renderSuneHTML,reflectActiveSune,suneRow,enhanceCodeBlocks,getSuneLabel,_createMessageRow,msgRow,partsToText,addSuneBubbleStreaming,clearChat,payloadWithSampling,setBtnStop,setBtnSend,localDemoReply,titleFrom,ensureThreadOnFirstUser,generateTitleWithAI,threadRow,renderThreads,hideThreadPopover,showThreadPopover,hideSunePopover,showSunePopover,updateAttachBadge,toAttach,ensureJars,openSettings,closeSettings,showTab,dl,ts,kbUpdate,kbBind,activeMeta,init,showHtmlTab,showAccountTab,openAccountSettings,closeAccountSettings,getBubbleById,syncActiveThread,syncWhileBusy,onForeground,getActiveHtmlParts,imgToWebp,cacheStore});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user