mirror of
https://github.com/multipleof4/sune.git
synced 2026-02-04 10:08:00 +00:00
Fix: Unicode base64 and empty repo sync logic
This commit is contained in:
@@ -10,6 +10,7 @@ const fmtSize=b=>{const u=['B','KB','MB','GB','TB'];let i=0,x=b;while(x>=1024&&i
|
||||
const asDataURL=f=>new Promise(r=>{const fr=new FileReader();fr.onload=()=>r(String(fr.result||''));fr.readAsDataURL(f)})
|
||||
const imgToWebp=(f,D=128,q=80)=>new Promise((r,j)=>{if(!f)return j();const i=new Image;i.onload=()=>{const c=document.createElement('canvas'),x=c.getContext('2d');let w=i.width,h=i.height;if(D>0&&Math.max(w,h)>D)w>h?(h=D*h/w,w=D):(w=D*w/h,h=D);c.width=w;c.height=h;x.drawImage(i,0,0,w,h);r(c.toDataURL('image/webp',clamp(q,0,100)/100));URL.revokeObjectURL(i.src)};i.onerror=j;i.src=URL.createObjectURL(f)});
|
||||
const b64=x=>x.split(',')[1]||''
|
||||
const utob=s=>btoa(unescape(encodeURIComponent(s))),btou=s=>decodeURIComponent(escape(atob(s.replace(/\s/g,''))))
|
||||
const su={key:'sunes_v1',activeKey:'active_sune_id',load(){try{return JSON.parse(localStorage.getItem(this.key)||'[]')}catch{return[]}},save(list){localStorage.setItem(this.key,JSON.stringify(list||[]))},getActiveId(){return localStorage.getItem(this.activeKey)||null},setActiveId(id){localStorage.setItem(this.activeKey,id||'')}}
|
||||
const defaultSettings={model:DEFAULT_MODEL,temperature:'',top_p:'',top_k:'',frequency_penalty:'',repetition_penalty:'',min_p:'',top_a:'',verbosity:'',reasoning_effort:'default',system_prompt:'',html:'',extension_html:"<sune src='https://raw.githubusercontent.com/sune-org/store/refs/heads/main/sync.sune' private></sune>",hide_composer:false,include_thoughts:false,json_output:false,img_output:false,aspect_ratio:'1:1',image_size:'1K',ignore_master_prompt:false,json_schema:''}
|
||||
const makeSune=(p={})=>({id:p.id||gid(),name:p.name?.trim()||'Default',pinned:!!p.pinned,avatar:p.avatar||'',url:p.url||'',updatedAt:p.updatedAt||Date.now(),settings:Object.assign({},defaultSettings,p.settings||{}),storage:p.storage||{}})
|
||||
@@ -110,11 +111,11 @@ const htmlTabs={index:['htmlTab_index','htmlEditor'],extension:['htmlTab_extensi
|
||||
el.htmlTab_index.textContent='index.html';el.htmlTab_extension.textContent='extension.html';
|
||||
el.htmlTab_index.onclick=()=>showHtmlTab('index');el.htmlTab_extension.onclick=()=>showHtmlTab('extension');
|
||||
const ghApi=async(path,method='GET',body=null)=>{const t=USER.githubToken;if(!t)throw new Error('No GH token');const r=await fetch(`https://api.github.com/repos/${path}`,{method,headers:{'Authorization':`token ${t}`,'Accept':'application/vnd.github.v3+json','Content-Type':'application/json'},body:body?JSON.stringify(body):null});if(!r.ok&&r.status!==404)throw new Error(`GH API ${r.status}`);return r.status===404?null:r.json()};
|
||||
const parseGhUrl=u=>{const p=u.substring(5).split('/'),owner=p[0],repo=p[1],branch=repo?.includes('@')?repo.split('@')[1]:'main',cleanRepo=repo?.split('@')[0],path=p.slice(2).join('/');return{owner,repo:cleanRepo,branch,path,full:`${owner}/${cleanRepo}/contents/${path?path+'/':''}index.json?ref=${branch}`,dir:`${owner}/${cleanRepo}/contents/${path?path+'/':''}`}};
|
||||
const parseGhUrl=u=>{const p=u.substring(5).split('/'),owner=p[0],repo=p[1],branch=repo?.includes('@')?repo.split('@')[1]:'main',cleanRepo=repo?.split('@')[0],path=p.slice(2).join('/');return{owner,repo:cleanRepo,branch,path,full:`${owner}/${cleanRepo}/contents/${path?path+'/':''}index.json?ref=${branch}`,dir:`${owner}/${cleanRepo}/contents/${path?path+'/':' '}`.trim()}};
|
||||
$(el.threadRepoInput).on('change',async()=>{const u=el.threadRepoInput.value.trim();localStorage.setItem('thread_repo_url',u);el.threadBackBtn.classList.toggle('hidden',!u.startsWith('gh://')||u.split('/').length<=3);await THREAD.load();await renderThreads()});
|
||||
$(el.threadBackBtn).on('click',()=>{const u=el.threadRepoInput.value.trim();if(!u.startsWith('gh://'))return;const p=u.split('/');if(p.length>3){p.pop();el.threadRepoInput.value=p.join('/');el.threadRepoInput.dispatchEvent(new Event('change'))}});
|
||||
$(el.threadFolderBtn).on('click',async()=>{const n=prompt('Folder name:');if(!n)return;THREAD.list.unshift({id:n.trim(),title:n.trim(),type:'folder',updatedAt:Date.now()});await THREAD.save();await renderThreads()});
|
||||
$(el.threadSyncBtn).on('click',async()=>{const u=el.threadRepoInput.value.trim();if(!u.startsWith('gh://'))return;const mode=confirm('Sync Threads:\nOK = Upload (Push)\nCancel = Download (Pull)');const info=parseGhUrl(u);try{if(mode){const idxFile=await ghApi(info.full),sha=idxFile?.sha;for(const t of THREAD.list){if(t.type==='thread'&&(t.status==='modified'||t.status==='new')){const msgs=await localforage.getItem('rem_t_'+t.id),fPath=`${info.dir}${t.id}.json`,ex=await ghApi(fPath+'?ref='+info.branch);await ghApi(fPath,'PUT',{message:`Sync thread ${t.id}`,content:btoa(JSON.stringify(msgs)),branch:info.branch,sha:ex?.sha});t.status='synced'}}await ghApi(info.full,'PUT',{message:'Update index.json',content:btoa(JSON.stringify(THREAD.list)),branch:info.branch,sha});alert('Pushed to GitHub.')}else{const idxFile=await ghApi(info.full);if(!idxFile)throw new Error('Remote index.json not found');const remoteList=JSON.parse(atob(idxFile.content));THREAD.list=remoteList.map(t=>({...t,status:'synced'}));await THREAD.save();alert('Pulled from GitHub.')}await renderThreads()}catch(e){alert('Sync failed: '+e.message)}});
|
||||
$(el.threadSyncBtn).on('click',async()=>{const u=el.threadRepoInput.value.trim();if(!u.startsWith('gh://'))return;const mode=confirm('Sync Threads:\nOK = Upload (Push)\nCancel = Download (Pull)');const info=parseGhUrl(u);try{if(mode){const idxFile=await ghApi(info.full),sha=idxFile?.sha;for(const t of THREAD.list){if(t.type==='thread'&&(t.status==='modified'||t.status==='new')){const msgs=await localforage.getItem('rem_t_'+t.id),fPath=`${info.dir}${t.id}.json`,ex=await ghApi(fPath+'?ref='+info.branch);await ghApi(fPath,'PUT',{message:`Sync thread ${t.id}`,content:utob(JSON.stringify(msgs)),branch:info.branch,sha:ex?.sha});t.status='synced'}}await ghApi(info.full,'PUT',{message:'Update index.json',content:utob(JSON.stringify(THREAD.list)),branch:info.branch,sha});alert('Pushed to GitHub.')}else{const idxFile=await ghApi(info.full);if(!idxFile){THREAD.list=[];await THREAD.save();alert('Remote is empty. Local list cleared.')}else{const remoteList=JSON.parse(btou(idxFile.content));THREAD.list=remoteList.map(t=>({...t,status:'synced'}));await THREAD.save();alert('Pulled from GitHub.')}}await renderThreads()}catch(e){alert('Sync failed: '+e.message)}});
|
||||
init()
|
||||
const accountTabs={General:['accountTabGeneral','accountPanelGeneral'],API:['accountTabAPI','accountPanelAPI'],User:['accountTabUser','accountPanelUser']};function showAccountTab(key){Object.entries(accountTabs).forEach(([k,[tb,pn]])=>{el[tb].classList.toggle('border-black',k===key);el[pn].classList.toggle('hidden',k!==key)})}
|
||||
function openAccountSettings(){el.set_provider.value=USER.provider||'openrouter';el.set_api_key_or.value=USER.apiKeyOpenRouter||'';el.set_api_key_oai.value=USER.apiKeyOpenAI||'';el.set_api_key_g.value=USER.apiKeyGoogle||'';el.set_api_key_claude.value=USER.apiKeyClaude||'';el.set_api_key_cf.value=USER.apiKeyCloudflare||'';el.set_master_prompt.value=USER.masterPrompt||'';el.set_title_model.value=USER.titleModel;el.set_gh_token.value=USER.githubToken||'';const sa=USER.gcpSA;el.gcpSAUploadBtn.textContent=sa&&sa.project_id?`Uploaded: ${sa.project_id}`:'Upload .json';el.set_user_name.value=USER.name;el.userAvatarPreview.src=USER.avatar||'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';el.userAvatarPreview.classList.toggle('bg-gray-200',!USER.avatar);el.set_donor.checked=USER.donor;const updateProv=()=>{const d=el.set_donor.checked;Array.from(el.set_provider.options).forEach(o=>{if(o.value!=='openrouter'){o.disabled=!d;if(!d)o.hidden=true;else o.hidden=false}});if(!d&&el.set_provider.value!=='openrouter')el.set_provider.value='openrouter'};updateProv();el.set_donor.onchange=updateProv;showAccountTab('General');el.accountSettingsModal.classList.remove('hidden')}
|
||||
@@ -142,4 +143,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