mirror of
https://github.com/multipleof4/sune.git
synced 2026-01-14 16:47:59 +00:00
This build was committed by a bot.
This commit is contained in:
176
dist/index.html
vendored
176
dist/index.html
vendored
@@ -6,6 +6,7 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/github-markdown-css@5.5.1/github-markdown-light.min.css"/>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css"/>
|
||||
<style>
|
||||
|
||||
.markdown-body{font-size:14px;line-height:1.6}.markdown-body pre{overflow:auto}
|
||||
.msg-bubble{overflow-x:auto}
|
||||
.copy-btn{position:absolute;top:.5rem;right:.5rem;background:#0f172a;color:#fff;border-radius:.5rem;padding:.25rem .5rem;font-size:12px;opacity:.85}
|
||||
@@ -151,63 +152,126 @@
|
||||
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/localforage@1.10.0/dist/localforage.min.js"></script>
|
||||
<script>
|
||||
window.doCreateTitle=!0;
|
||||
const DEFAULT_MODEL='openai/gpt-5',DEFAULT_API_KEY='',el=window.el=Object.fromEntries(['topbar','chat','messages','composer','input','sendBtn','suneBtnTop','suneModal','suneURL','settingsForm','closeSettings','cancelSettings','tabModel','tabPrompt','tabScript','panelModel','panelPrompt','panelScript','set_model','set_temperature','set_top_p','set_top_k','set_frequency_penalty','set_presence_penalty','set_repetition_penalty','set_min_p','set_top_a','set_max_tokens','set_verbosity','set_reasoning_effort','set_system_prompt','deleteSuneBtn','sidebarLeft','sidebarOverlayLeft','sidebarBtnLeft','suneList','newSuneBtn','userMenuBtn','userMenu','accountSettingsOption','sunesImportOption','sunesExportOption','threadsImportOption','threadsExportOption','importInput','sidebarBtnRight','sidebarRight','sidebarOverlayRight','threadList','closeThreads','threadPopover','sunePopover','footer','attachBtn','attachBadge','fileInput','htmlEditor','extensionHtmlEditor','htmlTab_index','htmlTab_extension','suneHtml','accountSettingsModal','accountSettingsForm','closeAccountSettings','cancelAccountSettings','set_master_prompt','set_provider','set_api_key_or','set_api_key_oai','set_title_model','copySystemPrompt','pasteSystemPrompt','copyHTML','pasteHTML','accountTabGeneral','accountTabAPI','accountPanelGeneral','accountPanelAPI','set_gh_token','importAccountSettings','exportAccountSettings','importAccountSettingsInput'].map(id=>[id,document.getElementById(id)])),icons=()=>window.lucide?.createIcons(),haptic=()=>/android/i.test(navigator.userAgent)&&navigator.vibrate?.(1),clamp=(v,m,x)=>Math.max(m,Math.min(x,v)),num=(v,d)=>v==null||v===''||isNaN(+v)?d:+v,int=(v,d)=>v==null||v===''||isNaN(parseInt(v))?d:parseInt(v),gid=()=>Math.random().toString(36).slice(2,9),esc=s=>String(s).replace(/[&<>'"`]/g,c=>({"&":"&","<":"<",">":">","\"":""","'":"'","`":"`"}[c])),sid=()=>Date.now().toString(36)+Math.random().toString(36).slice(2,6),fmtSize=b=>{const u=['B','KB','MB','GB','TB'];let i=0;for(x=b;x>=1024&&i<u.length-1;i++)x/=1024;return(x>=10?Math.round(x):Math.round(x*10)/10)+' '+u[i]},asDataURL=f=>new Promise(r=>{const fr=new FileReader;fr.onload=()=>r(String(fr.result||'')),fr.readAsDataURL(f)}),b64=x=>x.split(',')[1]||'';
|
||||
const globalStore={get provider(){return localStorage.getItem('provider')||'openrouter'},set provider(v){localStorage.setItem('provider',v==='openai'?'openai':'openrouter')},get apiKeyOR(){return localStorage.getItem('openrouter_api_key')||DEFAULT_API_KEY||''},set apiKeyOR(v){localStorage.setItem('openrouter_api_key',v||'')},get apiKeyOAI(){return localStorage.getItem('openai_api_key')||''},set apiKeyOAI(v){localStorage.setItem('openai_api_key',v||'')},get masterPrompt(){return localStorage.getItem('master_prompt')||''},set masterPrompt(v){localStorage.setItem('master_prompt',v||'')},get titleModel(){return localStorage.getItem('title_model')??'or:meta-llama/llama-3.2-3b-instruct'},set titleModel(v){localStorage.setItem('title_model',v||'')},get ghToken(){return localStorage.getItem('gh_token')||''},set ghToken(v){localStorage.setItem('gh_token',v||'')}};
|
||||
const su={key:'sunes_v1',activeKey:'active_sune_id',load(){try{return JSON.parse(localStorage.getItem(this.key)||'[]')}catch{return[]}},save(l){localStorage.setItem(this.key,JSON.stringify(l||[]))},getActiveId(){return localStorage.getItem(this.activeKey)||null},setActiveId(id){localStorage.setItem(this.activeKey,id||'')}};
|
||||
const defaultSettings={model:DEFAULT_MODEL,temperature:1,top_p:.96,top_k:0,frequency_penalty:0,presence_penalty:0,repetition_penalty:1,min_p:0,top_a:0,max_tokens:0,verbosity:'',reasoning_effort:'default',system_prompt:'',html:'',extension_html:''};
|
||||
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:{...defaultSettings,...p.settings||{}}});
|
||||
let sunes=(su.load()||[]).map(makeSune);
|
||||
const SUNE=window.SUNE=new Proxy({get list(){return sunes},get id(){return su.getActiveId()},get active(){return sunes.find(a=>a.id===su.getActiveId())||sunes[0]},get:id=>sunes.find(s=>s.id===id),setActive:id=>su.setActiveId(id||''),create(p={}){const s=makeSune(p);sunes.unshift(s);su.save(sunes);return s},delete(id){const curId=this.id;sunes=sunes.filter(s=>s.id!==id);su.save(sunes);if(sunes.length===0){const def=this.create({name:'Default'});this.setActive(def.id)}else if(curId===id)this.setActive(sunes[0].id)},save:()=>su.save(sunes)},{get(t,p){if(p==='fetchDotSune')return async g=>{if(t.get('e1yibwd'))return;try{const[a,b]=g.split('@'),[c,d]=a.split('/'),[e,...f]=b.split('/'),u=`https://raw.githubusercontent.com/${c}/${d}/${e}/${f.join('/')}`,j=await(await fetch(u)).json(),l=Array.isArray(j)?j:(j?.sunes||[]),s=l.find(i=>i.id==='e1yibwd');if(s&&!t.get(s.id)){sunes.unshift(makeSune(s));t.save()}}catch{}};if(p==='getThread')return id=>threads.find(t=>t.id===id)||null;if(p==='setThreadTitle')return async(id,title)=>{const th=threads.find(t=>t.id===id);if(!th||!title)return;th.title=titleFrom(title),th.updatedAt=Date.now(),await tsave(threads),await renderThreads()};if(p==='attach')return async(files,opts={})=>{const arr=[],o=opts===!0?{toAPI:!0,tree:!0}:opts||{};for(const f of files||[])arr.push(await toAttach(f));const clean=arr.filter(Boolean);if(!clean.length)return;await ensureThreadOnFirstUser(clean[0]?.name||'(attachments)');const meta=activeMeta(),toAPI=o.toAPI??o.toapi??!0,tree=o.tree??!0;if(toAPI)addMessage({role:'assistant',content:clean.map(a=>a.part),...meta});if(tree)addAttachmentTree('assistant',clean);await persistThread()};if(p==='log')return async s=>{const t=String(s??'').trim();if(!t)return;await ensureThreadOnFirstUser(t),addMessage({role:'assistant',content:[{type:'text',text:t}],...activeMeta()}),await persistThread()};if(p in t)return t[p];if(p==='provider')return globalStore.provider;if(p==='apiKey')return globalStore.provider==='openai'?globalStore.apiKeyOAI:globalStore.apiKeyOR;if(p==='apiKeyOR')return globalStore.apiKeyOR;if(p==='apiKeyOAI')return globalStore.apiKeyOAI;if(p==='masterPrompt')return globalStore.masterPrompt;if(p==='titleModel')return globalStore.titleModel;const a=t.active;if(a){if(p in a.settings)return a.settings[p];if(p in a)return a[p]}},set(t,p,v){if(p==='provider'){globalStore.provider=v;return!0}if(p==='apiKey'){if(globalStore.provider==='openai')globalStore.apiKeyOAI=v;else globalStore.apiKeyOR=v;return!0}if(p==='apiKeyOR'){globalStore.apiKeyOR=v;return!0}if(p==='apiKeyOAI'){globalStore.apiKeyOAI=v;return!0}if(p==='masterPrompt'){globalStore.masterPrompt=v;return!0}if(p==='titleModel'){globalStore.titleModel=v;return!0}const a=t.active;if(!a)return!1;const i=sunes.findIndex(s=>s.id===a.id);if(i<0)return!1;if(p==='name'||p==='avatar'||p==='url'||p==='pinned')sunes[i][p]=v;else if(p==='model')sunes[i].settings.model=v||DEFAULT_MODEL;else if(p==='system_prompt')sunes[i].settings.system_prompt=v||'';else sunes[i].settings[p]=v;sunes[i].updatedAt=Date.now(),su.save(sunes);return!0}});
|
||||
const DEFAULT_MODEL='openai/gpt-5',DEFAULT_API_KEY=''
|
||||
const el=window.el=Object.fromEntries(['topbar','chat','messages','composer','input','sendBtn','suneBtnTop','suneModal','suneURL','settingsForm','closeSettings','cancelSettings','tabModel','tabPrompt','tabScript','panelModel','panelPrompt','panelScript','set_model','set_temperature','set_top_p','set_top_k','set_frequency_penalty','set_presence_penalty','set_repetition_penalty','set_min_p','set_top_a','set_max_tokens','set_verbosity','set_reasoning_effort','set_system_prompt','deleteSuneBtn','sidebarLeft','sidebarOverlayLeft','sidebarBtnLeft','suneList','newSuneBtn','userMenuBtn','userMenu','accountSettingsOption','sunesImportOption','sunesExportOption','threadsImportOption','threadsExportOption','importInput','sidebarBtnRight','sidebarRight','sidebarOverlayRight','threadList','closeThreads','threadPopover','sunePopover','footer','attachBtn','attachBadge','fileInput','htmlEditor','extensionHtmlEditor','htmlTab_index','htmlTab_extension','suneHtml','accountSettingsModal','accountSettingsForm','closeAccountSettings','cancelAccountSettings','set_master_prompt','set_provider','set_api_key_or','set_api_key_oai','set_title_model','copySystemPrompt','pasteSystemPrompt','copyHTML','pasteHTML','accountTabGeneral','accountTabAPI','accountPanelGeneral','accountPanelAPI','set_gh_token','importAccountSettings','exportAccountSettings','importAccountSettingsInput'].map(id=>[id,document.getElementById(id)]))
|
||||
const icons=()=>window.lucide&&lucide.createIcons()
|
||||
const haptic=()=>/android/i.test(navigator.userAgent)&&navigator.vibrate?.(1)
|
||||
const clamp=(v,min,max)=>Math.max(min,Math.min(max,v)),num=(v,d)=>v==null||v===''||isNaN(+v)?d:+v,int=(v,d)=>v==null||v===''||isNaN(parseInt(v))?d:parseInt(v),gid=()=>Math.random().toString(36).slice(2,9),esc=s=>String(s).replace(/[&<>'"`]/g,c=>({"&":"&","<":"<",">":">","\"":""","'":"'","`":"`"}[c]))
|
||||
const sid=()=>Date.now().toString(36)+Math.random().toString(36).slice(2,6)
|
||||
const fmtSize=b=>{const u=['B','KB','MB','GB','TB'];let i=0,x=b;while(x>=1024&&i<u.length-1){x/=1024;i++}return (x>=10?Math.round(x):Math.round(x*10)/10)+' '+u[i]}
|
||||
const asDataURL=f=>new Promise(r=>{const fr=new FileReader();fr.onload=()=>r(String(fr.result||''));fr.readAsDataURL(f)})
|
||||
const b64=x=>x.split(',')[1]||''
|
||||
const globalStore={get provider(){return localStorage.getItem('provider')||'openrouter'},set provider(v){localStorage.setItem('provider',v==='openai'?'openai':'openrouter')},get apiKeyOR(){return localStorage.getItem('openrouter_api_key')||DEFAULT_API_KEY||''},set apiKeyOR(v){localStorage.setItem('openrouter_api_key',v||'')},get apiKeyOAI(){return localStorage.getItem('openai_api_key')||''},set apiKeyOAI(v){localStorage.setItem('openai_api_key',v||'')},get masterPrompt(){return localStorage.getItem('master_prompt')||''},set masterPrompt(v){localStorage.setItem('master_prompt',v||'')},get titleModel(){return localStorage.getItem('title_model')??'or:meta-llama/llama-3.2-3b-instruct'},set titleModel(v){localStorage.setItem('title_model',v||'')},get ghToken(){return localStorage.getItem('gh_token')||''},set ghToken(v){localStorage.setItem('gh_token',v||'')}}
|
||||
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:1,top_p:0.96,top_k:0,frequency_penalty:0,presence_penalty:0,repetition_penalty:1,min_p:0,top_a:0,max_tokens:0,verbosity:'',reasoning_effort:'default',system_prompt:'',html:'',extension_html:''}
|
||||
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||{})})
|
||||
let sunes=(su.load()||[]).map(makeSune)
|
||||
const SUNE=window.SUNE=new Proxy({get list(){return sunes},get id(){return su.getActiveId()},get active(){return sunes.find(a=>a.id===su.getActiveId())||sunes[0]},get:id=>sunes.find(s=>s.id===id),setActive:id=>su.setActiveId(id||''),create(p={}){const s=makeSune(p);sunes.unshift(s);su.save(sunes);return s},delete(id){const curId=this.id;sunes=sunes.filter(s=>s.id!==id);su.save(sunes);if(sunes.length===0){const def=this.create({name:'Default'});this.setActive(def.id)}else if(curId===id)this.setActive(sunes[0].id)},save:()=>su.save(sunes)},{get(t,p){if(p==='fetchDotSune')return async g=>{if(t.get('e1yibwd'))return;try{const[a,b]=g.split('@'),[c,d]=a.split('/'),[e,...f]=b.split('/'),u=`https://raw.githubusercontent.com/${c}/${d}/${e}/${f.join('/')}`,j=await(await fetch(u)).json(),l=Array.isArray(j)?j:(j?.sunes||[]),s=l.find(i=>i.id==='e1yibwd');if(s&&!t.get(s.id)){sunes.unshift(makeSune(s));t.save()}}catch{}};if(p==='getThread')return id=>threads.find(t=>t.id===id)||null;if(p==='setThreadTitle')return async(id,title)=>{const th=threads.find(t=>t.id===id);if(!th||!title)return;th.title=titleFrom(title);th.updatedAt=Date.now();await tsave(threads);await renderThreads()};if(p==='attach')return async(files,opts={})=>{const arr=[];for(const f of files||[])arr.push(await toAttach(f));const clean=arr.filter(Boolean);if(!clean.length)return;await ensureThreadOnFirstUser(clean[0]?.name||'(attachments)');const meta=activeMeta();const o=typeof opts==='boolean'?{toAPI:opts,tree:true}:(opts||{});const toAPI=('toAPI'in o)?!!o.toAPI:(('toapi'in o)?!!o.toapi:true);const tree=('tree'in o)?!!o.tree:true;if(toAPI){const parts=clean.map(a=>a.part);addMessage({role:'assistant',content:parts,...meta})}if(tree)addAttachmentTree('assistant',clean);await persistThread()};if(p==='log')return async s=>{const t=String(s??'').trim();if(!t)return;await ensureThreadOnFirstUser(t);addMessage({role:'assistant',content:[{type:'text',text:t}],...activeMeta()});await persistThread()};if(p in t)return t[p];if(p==='provider')return globalStore.provider;if(p==='apiKey')return globalStore.provider==='openai'?globalStore.apiKeyOAI:globalStore.apiKeyOR;if(p==='apiKeyOR')return globalStore.apiKeyOR;if(p==='apiKeyOAI')return globalStore.apiKeyOAI;if(p==='masterPrompt')return globalStore.masterPrompt;if(p==='titleModel')return globalStore.titleModel;const a=t.active;if(!a)return;if(p in a.settings)return a.settings[p];if(p in a)return a[p]},set(t,p,v){if(p==='provider'){globalStore.provider=v;return true}if(p==='apiKey'){if(globalStore.provider==='openai')globalStore.apiKeyOAI=v;else globalStore.apiKeyOR=v;return true}if(p==='apiKeyOR'){globalStore.apiKeyOR=v;return true}if(p==='apiKeyOAI'){globalStore.apiKeyOAI=v;return true}if(p==='masterPrompt'){globalStore.masterPrompt=v;return true}if(p==='titleModel'){globalStore.titleModel=v;return true}const a=t.active;if(!a)return false;const i=sunes.findIndex(s=>s.id===a.id);if(i<0)return false;if(p==='name'||p==='avatar'||p==='url'||p==='pinned')sunes[i][p]=v;else{if(p==='model')sunes[i].settings.model=v||DEFAULT_MODEL;else if(p==='system_prompt')sunes[i].settings.system_prompt=v||'';else sunes[i].settings[p]=v}sunes[i].updatedAt=Date.now();su.save(sunes);return true}})
|
||||
if(!sunes.length){const def=SUNE.create({name:'Default'});SUNE.setActive(def.id)}
|
||||
const state=window.state={messages:[],busy:!1,controller:null,currentThreadId:null,abortRequested:!1,attachments:[],stream:{rid:null,bubble:null,meta:null,text:'',done:!1}},getModelShort=m=>(m||SUNE.model||'').split('/').pop(),resolveSuneSrc=src=>{if(!src)return null;if(src.startsWith('gh://')){const[owner,repo,...filePathParts]=src.substring(5).split('/');if(filePathParts.length<1)return null;return`https://raw.githubusercontent.com/${owner}/${repo}/main/${filePathParts.join('/')}`}return src},processSuneIncludes=async(html,depth=0)=>{if(depth>5||!html)return html?'<!-- Sune include depth limit reached -->':'';const c=document.createElement('div');c.innerHTML=html;for(const n of[...c.querySelectorAll('sune')]){if(n.hasAttribute('src')){if(n.hasAttribute('private')&&depth>0){n.remove();continue}const s=n.getAttribute('src'),u=resolveSuneSrc(s);if(!u){n.replaceWith(document.createComment(` Invalid src: ${esc(s)} `));continue}try{const r=await fetch(u);if(!r.ok)throw new Error(`HTTP ${r.status}`);const d=await r.json(),o=Array.isArray(d)?d[0]:d,h=[o?.settings?.html||'',o?.settings?.extension_html||''].join('\n');n.replaceWith(document.createRange().createContextualFragment(await processSuneIncludes(h,depth+1)))}catch(e){n.replaceWith(document.createComment(` Fetch failed: ${esc(u)} `))}}else n.replaceWith(document.createRange().createContextualFragment(n.innerHTML))}return c.innerHTML};
|
||||
const renderSuneHTML=async()=>{const baseHtml=[SUNE.html,SUNE.extension_html].filter(Boolean).map(x=>x.trim()).join('\n'),finalHtml=await processSuneIncludes(baseHtml),container=el.suneHtml;container.innerHTML='',container.classList.toggle('hidden',!finalHtml.trim());if(!finalHtml.trim())return;container.insertAdjacentHTML('afterbegin',finalHtml);const scripts=[...container.querySelectorAll('script')].filter(s=>!s.type||s.type==='text/javascript').map(s=>(s.remove(),s.textContent));if(scripts.length)setTimeout(()=>scripts.forEach(code=>{try{new Function(code)()}catch(e){console.error('Sune script error:',e)}}),0)},reflectActiveSune=async()=>{const a=SUNE.active;el.suneBtnTop.title=`Settings — ${a.name}`,el.suneBtnTop.innerHTML=a.avatar?`<img src="${esc(a.avatar)}" alt="" class="h-8 w-8 rounded-full object-cover"/>`:'✺',await renderSuneHTML(),icons()},suneRow=a=>`<div class="relative flex items-center gap-2 px-3 py-2 ${a.pinned?'bg-yellow-50':''}"><button data-sune-id="${a.id}" class="flex-1 text-left flex items-center gap-2 ${a.id===SUNE.id?'font-medium':''}">${a.avatar?`<img src="${esc(a.avatar)}" alt="" class="h-6 w-6 rounded-full object-cover"/>`:`<span class="h-6 w-6 rounded-full bg-gray-200 flex items-center justify-center">✺</span>`}<span class="truncate">${a.pinned?'📌 ':''}${esc(a.name)}</span></button><button data-sune-menu="${a.id}" class="h-8 w-8 rounded hover:bg-gray-100 flex items-center justify-center" title="More"><i data-lucide="more-horizontal" class="h-4 w-4"></i></button></div>`,renderSidebar=window.renderSidebar=()=>{el.suneList.innerHTML=[...SUNE.list].sort((a,b)=>b.pinned-a.pinned).map(suneRow).join(''),icons()};
|
||||
const enhanceCodeBlocks=root=>{root.querySelectorAll('pre>code').forEach(code=>{if(code.textContent.length>200000)return;const pre=code.parentElement;pre.classList.add('relative','rounded-xl','border','border-gray-200');if(!pre.querySelector('.copy-btn')){const btn=document.createElement('button');btn.className='copy-btn',btn.textContent='Copy',btn.onclick=async e=>{e.stopPropagation();try{await navigator.clipboard.writeText(code.innerText),btn.textContent='Copied',setTimeout(()=>btn.textContent='Copy',1200)}catch{}};pre.appendChild(btn)}window.hljs&&code.textContent.length<100000&&hljs.highlightElement(code)})},md=window.markdownit({html:!1,linkify:!0,typographer:!0,breaks:!0}),getSuneLabel=m=>`${m?.sune_name||SUNE.name} · ${getModelShort(m?.model)}`;
|
||||
const msgRow=m=>{const role=typeof m=='string'?m:m?.role||'assistant',meta=typeof m=='string'?{}:m||{},row=document.createElement('div');row.className='flex flex-col gap-2';const head=document.createElement('div');head.className='flex items-center gap-2 px-4';const avatar=document.createElement('div');if(role==='user')avatar.className='bg-gray-900 text-white msg-avatar shrink-0 h-7 w-7 rounded-full flex items-center justify-center',avatar.textContent='🧑';else if(meta?.avatar){avatar.className='msg-avatar shrink-0 h-7 w-7 rounded-full overflow-hidden';const img=document.createElement('img');img.src=meta.avatar,img.className='h-full w-full object-cover',avatar.appendChild(img)}else avatar.className='bg-gray-200 text-gray-900 msg-avatar shrink-0 h-7 w-7 rounded-full flex items-center justify-center',avatar.textContent='✺';const name=document.createElement('div');name.className='text-xs font-medium text-gray-500',name.textContent=role==='user'?'You':getSuneLabel(meta);const deleteBtn=document.createElement('button');deleteBtn.className='ml-auto p-1.5 rounded-lg hover:bg-gray-200 text-gray-400 hover:text-red-500',deleteBtn.title='Delete message',deleteBtn.innerHTML='<i data-lucide="eraser" class="h-4 w-4"></i>',deleteBtn.onclick=async e=>{e.stopPropagation();if(confirm('Delete message?'))state.messages=state.messages.filter(msg=>msg.id!==m.id),row.remove(),await persistThread()};const copyBtn=document.createElement('button');copyBtn.className='p-1.5 rounded-lg hover:bg-gray-200 text-gray-400 hover:text-gray-600',copyBtn.title='Copy message',copyBtn.innerHTML='<i data-lucide="copy" class="h-4 w-4"></i>',copyBtn.onclick=async function(e){e.stopPropagation();const b=this.parentElement.nextElementSibling;if(b)try{await navigator.clipboard.writeText(b.innerText),this.innerHTML='<i data-lucide="check" class="h-4 w-4 text-green-500"></i>',icons(),setTimeout(()=>{this.innerHTML='<i data-lucide="copy" class="h-4 w-4"></i>',icons()},1200)}catch{}};head.append(avatar,name,deleteBtn,copyBtn);const bubble=document.createElement('div');bubble.className=(role==='user'?'bg-gray-50 border border-gray-200':'bg-gray-100')+' msg-bubble markdown-body rounded-none px-4 py-3 w-full',row.append(head,bubble),el.messages.appendChild(row),queueMicrotask(()=>{el.chat.scrollTo({top:el.chat.scrollHeight,behavior:'smooth'}),icons()});return bubble};
|
||||
const renderMarkdown=window.renderMarkdown=(node,text,opt={highlight:!0})=>{node.innerHTML=md.render(text),enhanceCodeBlocks(node,opt.highlight)},partsToText=parts=>Array.isArray(parts)?parts.map(p=>p?.type==='text'?p.text:(p?.type==='image_url'?``:(p?.type==='file'?`[${p.file?.filename||'file'}]`:(p?.type==='input_audio'?`(audio:${p.input_audio?.format||''})`:'')))).join('\n'):String(parts||''),addMessage=window.addMessage=(m,track=!0)=>{m.id=m.id||gid();if(!Array.isArray(m.content)&&m.content!=null)m.content=[{type:'text',text:String(m.content)}];const bubble=msgRow(m);bubble.dataset.mid=m.id,renderMarkdown(bubble,partsToText(m.content));if(track)state.messages.push(m);return bubble},addSuneBubbleStreaming=meta=>msgRow({role:'assistant',...meta}),clearChat=()=>{state.messages=[],el.messages.innerHTML='',state.attachments=[],updateAttachBadge(),el.fileInput.value=''};
|
||||
const payloadWithSampling=b=>{const o={...b};o.temperature=SUNE.temperature,o.top_p=SUNE.top_p,o.top_k=SUNE.top_k,o.frequency_penalty=SUNE.frequency_penalty,o.presence_penalty=SUNE.presence_penalty,o.repetition_penalty=SUNE.repetition_penalty,o.min_p=SUNE.min_p,o.top_a=SUNE.top_a;const mt=Math.max(0,int(SUNE.max_tokens||0,0));if(mt)o.max_tokens=mt;return o};
|
||||
const setBtnStop=()=>{const b=el.sendBtn;b.dataset.mode='stop',b.type='button',b.setAttribute('aria-label','Stop'),b.innerHTML='<i data-lucide="square" class="h-5 w-5"></i>',icons(),b.onclick=()=>{state.abortRequested=!0,state.controller?.abort?.()}},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},localDemoReply=()=>'Tip: open the sidebar → Account & Backup to set your API key.';
|
||||
let threads=[];const titleFrom=t=>(t||'').replace(/\s+/g,' ').trim().slice(0,60)||'Untitled',TKEY='threads_v1',tload=()=>localforage.getItem(TKEY).then(v=>Array.isArray(v)?v:[]),tsave=v=>localforage.setItem(TKEY,v),cacheStore=localforage.createInstance({name:'master_cache'});
|
||||
const ensureThreadOnFirstUser=async text=>{let needNew=!state.currentThreadId;if(state.messages.length===0)state.currentThreadId=null;if(state.currentThreadId&&!threads.some(x=>x.id===state.currentThreadId))needNew=!0;if(!needNew)return;const id=gid(),now=Date.now(),th={id,title:titleFrom(text),pinned:!1,updatedAt:now,messages:[]};state.currentThreadId=id,threads.unshift(th),await tsave(threads),await renderThreads(),document.dispatchEvent(new CustomEvent('sune:new-thread',{detail:{threadId:id}}))},persistThread=async(full=!0)=>{if(!state.currentThreadId)return;let th=threads.find(x=>x.id===state.currentThreadId);if(!th)return;th.messages=[...state.messages];if(full){th.updatedAt=Date.now(),th.title=titleFrom(partsToText(th.messages.find(m=>m.role==='user')?.content)||th.title)}await tsave(threads),full&&await renderThreads()},threadRow=t=>`<div class="relative flex items-center gap-2 px-3 py-2 ${t.pinned?'bg-yellow-50':''}"><button data-open-thread="${t.id}" class="flex-1 text-left truncate">${t.pinned?'📌 ':''}${esc(t.title)}</button><button data-thread-menu="${t.id}" class="h-8 w-8 rounded hover:bg-gray-100 flex items-center justify-center" title="More"><i data-lucide="more-horizontal" class="h-4 w-4"></i></button></div>`,renderThreads=async()=>{el.threadList.innerHTML=[...threads].sort((a,b)=>(b.pinned-a.pinned)||(b.updatedAt-a.updatedAt)).map(threadRow).join(''),icons()};
|
||||
let menuThreadId=null;const hideThreadPopover=()=>{el.threadPopover.classList.add('hidden'),menuThreadId=null},showThreadPopover=(btn,id)=>{menuThreadId=id;const r=btn.getBoundingClientRect();el.threadPopover.style.top=(r.bottom+4)+'px',el.threadPopover.style.left=Math.min(window.innerWidth-220,r.right-200)+'px',el.threadPopover.classList.remove('hidden'),icons()};
|
||||
let menuSuneId=null;const hideSunePopover=()=>{el.sunePopover.classList.add('hidden'),menuSuneId=null},showSunePopover=(btn,id)=>{menuSuneId=id;const r=btn.getBoundingClientRect();el.sunePopover.style.top=(r.bottom+4)+'px',el.sunePopover.style.left=Math.min(window.innerWidth-220,r.right-200)+'px',el.sunePopover.classList.remove('hidden'),icons()};
|
||||
el.threadList.addEventListener('click',async e=>{const openBtn=e.target.closest('[data-open-thread]'),menuBtn=e.target.closest('[data-thread-menu]');if(openBtn){const id=openBtn.dataset.openThread;if(id!==state.currentThreadId&&state.busy)state.controller?.disconnect?.(),setBtnSend(),state.busy=!1,state.controller=null;const th=threads.find(t=>t.id===id);if(!th)return;if(id===state.currentThreadId)return el.sidebarRight.classList.add('translate-x-full'),el.sidebarOverlayRight.classList.add('hidden'),hideThreadPopover();state.currentThreadId=id,clearChat(),state.messages=th.messages?[...th.messages]:[],state.messages.forEach(m=>{const b=msgRow(m);b.dataset.mid=m.id||'',renderMarkdown(b,partsToText(m.content))}),await renderSuneHTML(),syncWhileBusy(),queueMicrotask(()=>el.chat.scrollTo({top:el.chat.scrollHeight,behavior:'smooth'})),el.sidebarRight.classList.add('translate-x-full'),el.sidebarOverlayRight.classList.add('hidden'),hideThreadPopover()}else if(menuBtn)e.stopPropagation(),showThreadPopover(menuBtn,menuBtn.dataset.threadMenu)});
|
||||
el.threadPopover.addEventListener('click',async e=>{const act=e.target.closest('[data-action]')?.dataset.action;if(!act||!menuThreadId)return;const th=threads.find(t=>t.id===menuThreadId);if(!th)return;if(act==='pin')th.pinned=!th.pinned;else if(act==='rename'){const nv=prompt('Rename to:',th.title);if(nv!=null)th.title=titleFrom(nv),th.updatedAt=Date.now()}else if(act==='delete'){if(confirm('Delete this chat?'))threads=threads.filter(x=>x.id!==th.id),state.currentThreadId===th.id&&(state.currentThreadId=null,clearChat())}else if(act==='count_tokens'){const tokens=Math.max(0,Math.ceil((th.messages||[]).reduce((sum,m)=>sum+(m&&m.role&&m.role!=='system'?String(partsToText(m.content||'')||'').length:0),0)/4)),k=tokens>=1e3?Math.round(tokens/1e3)+'k':String(tokens);alert(tokens+' tokens ('+k+')')}hideThreadPopover(),await tsave(threads),renderThreads()});
|
||||
el.suneList.addEventListener('click',async e=>{const menuBtn=e.target.closest('[data-sune-menu]');if(menuBtn)return e.stopPropagation(),showSunePopover(menuBtn,menuBtn.dataset.suneMenu);const btn=e.target.closest('[data-sune-id]');if(btn){const id=btn.dataset.suneId;if(id){if(state.busy)state.controller?.disconnect?.(),setBtnSend(),state.busy=!1,state.controller=null;SUNE.setActive(id),renderSidebar(),await reflectActiveSune(),state.currentThreadId=null,clearChat(),el.sidebarLeft.classList.add('-translate-x-full'),el.sidebarOverlayLeft.classList.add('hidden')}}});
|
||||
el.sunePopover.addEventListener('click',async e=>{const act=e.target.closest('[data-action]')?.dataset.action;if(!act||!menuSuneId)return;const s=SUNE.get(menuSuneId);if(!s)return;const updateAndRender=async()=>{s.updatedAt=Date.now(),SUNE.save(),renderSidebar(),await reflectActiveSune()};if(act==='pin')s.pinned=!s.pinned,await updateAndRender();else if(act==='rename'){const n=prompt('Rename sune to:',s.name);if(n!=null)s.name=n.trim(),await updateAndRender()}else if(act==='pfp'){const i=document.createElement('input');i.type='file',i.accept='image/*',i.onchange=()=>{const f=i.files?.[0];if(f){const img=new Image;img.onload=async()=>{const c=document.createElement('canvas'),ctx=c.getContext('2d'),D=144;let{width:w,height:h}=img;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,ctx.drawImage(img,0,0,w,h),s.avatar=c.toDataURL('image/webp',.84),await updateAndRender(),URL.revokeObjectURL(img.src)},img.src=URL.createObjectURL(f)}},i.click()}else if(act==='export')dl(`sune-${(s.name||'sune').replace(/\W/g,'_')}-${ts()}.sune`,[s]);hideSunePopover()});
|
||||
const updateAttachBadge=()=>{const n=state.attachments.length;el.attachBadge.textContent=String(n),el.attachBadge.classList.toggle('hidden',n===0)},toAttach=async file=>{if(!file)return null;const pick=(name,bytes,mime,data,mode,part)=>({name,bytes,mime,data,mode,part});if(file instanceof File){const name=file.name||'file',mime=(file.type||'application/octet-stream').toLowerCase(),bytes=file.size||0,data=await asDataURL(file);if(/^image\//.test(mime)||/\.(png|jpe?g|webp|gif)$/i.test(name))return pick(name,bytes,mime,data,'dataURL',{type:'image_url',image_url:{url:data}});const bin=b64(data);if(mime==='application/pdf'||/\.pdf$/i.test(name))return pick(name.endsWith('.pdf')?name:name+'.pdf',bytes,'application/pdf',bin,'base64',{type:'file',file:{filename:name,file_data:bin}});if(/^audio\//.test(mime)||/\.(wav|mp3)$/i.test(name)){const fmt=/mp3/.test(mime)||/\.mp3$/i.test(name)?'mp3':'wav';return pick(name,bytes,mime,bin,'base64',{type:'input_audio',input_audio:{data:bin,format:fmt}})}return pick(name,bytes,mime,bin,'base64',{type:'file',file:{filename:name,file_data:bin}})}if(file&&file.name==null&&file.data){const name=file.name||'file',mime=(file.mime||'application/octet-stream').toLowerCase(),bytes=file.size||0;if(/^image\//.test(mime))return pick(name,bytes,mime,`data:${mime};base64,${file.data}`,'dataURL',{type:'image_url',image_url:{url:`data:${mime};base64,${file.data}`}});const bin=file.data;if(mime==='application/pdf')return pick(name,bytes,mime,bin,'base64',{type:'file',file:{filename:name,file_data:bin}});if(/^audio\//.test(mime))return pick(name,bytes,mime,bin,'base64',{type:'input_audio',input_audio:{data:bin,format:/mp3/.test(mime)?'mp3':'wav'}});return pick(name,bytes,mime,bin,'base64',{type:'file',file:{filename:name,file_data:bin}})}return null},attachmentsText=(id,arr)=>`**Attachments**\n${arr.map((a,i)=>`- [${esc(a.name)} • ${fmtSize(a.bytes)}](#dl-${id}-${i})`).join('\n')}`,addAttachmentTree=(role,arr)=>{if(!arr?.length)return;const id=gid(),text=attachmentsText(id,arr),meta={role,content:[{type:'text',text}],id,kind:'attachments',attachmentsMeta:arr.map(a=>({name:a.name,bytes:a.bytes,mime:a.mime,mode:a.mode,data:a.data}))},b=addMessage(meta,!0);b.dataset.mid=id};
|
||||
el.attachBtn.addEventListener('click',()=>{if(!state.busy)state.attachments.length?(state.attachments=[],updateAttachBadge(),el.fileInput.value=''):el.fileInput.click()});
|
||||
el.fileInput.addEventListener('change',async()=>{const files=el.fileInput.files||[];if(files.length)await Promise.all([...files].map(async f=>{const at=await toAttach(f).catch(()=>null);if(at)state.attachments.push(at)})),updateAttachBadge()});
|
||||
el.messages.addEventListener('click',async e=>{const a=e.target.closest('a[href^="#dl-"]');if(!a)return;e.preventDefault();const m=a.getAttribute('href').match(/^#dl-([^-]+)-(\d+)$/);if(!m)return;const msg=state.messages.find(x=>x.id===m[1]),meta=msg?.attachmentsMeta?.[+m[2]];if(!meta)return;const blob=meta.mode==='dataURL'?await(await fetch(meta.data)).blob():new Blob([Uint8Array.from(atob(meta.data),c=>c.charCodeAt(0))],{type:meta.mime||'application/octet-stream'}),url=URL.createObjectURL(blob),dl=document.createElement('a');dl.href=url,dl.download=meta.name||'download',document.body.appendChild(dl),dl.click(),dl.remove(),URL.revokeObjectURL(url)});
|
||||
el.composer.addEventListener('submit',async e=>{e.preventDefault();if(state.busy)return;const text=el.input.value.trim();if(!text&&!state.attachments.length)return;state.messages.length===0&&(state.currentThreadId=null),await ensureThreadOnFirstUser(text||'(attachments)'),el.input.value='';const parts=[];if(text)parts.push({type:'text',text});state.attachments.forEach(a=>parts.push(a.part)),addMessage({role:'user',content:parts.length?parts:[{type:'text',text:text||'(sent attachments)'}]}),state.attachments.length&&addAttachmentTree('user',state.attachments),state.busy=!0,setBtnStop();const a=SUNE.active,suneMeta={sune_name:a.name,model:SUNE.model,avatar:a.avatar||''},suneBubble=addSuneBubbleStreaming(suneMeta),streamId=sid();suneBubble.dataset.mid=streamId;const assistantMsg={id:streamId,role:'assistant',content:[{type:'text',text:''}],...suneMeta};state.messages.push(assistantMsg),persistThread(!1),state.stream={rid:streamId,bubble:suneBubble,meta:suneMeta,text:'',done:!1};let buf='',completed=!1;const onDelta=(delta,done)=>{buf+=delta,state.stream.text=buf,renderMarkdown(suneBubble,buf,{highlight:!1}),assistantMsg.content[0].text=buf;if(done&&!completed)completed=!0,setBtnSend(),state.busy=!1,enhanceCodeBlocks(suneBubble),persistThread(),state.stream={rid:null,bubble:null,meta:null,text:'',done:!1};else if(!done)persistThread(!1)};await askOpenRouterStreaming(onDelta,streamId),state.attachments=[],updateAttachBadge()});
|
||||
let jars={html:null,extension:null};const ensureJars=async()=>{if(jars.html&&jars.extension)return jars;const{CodeJar:C}=await import('https://medv.io/codejar/codejar.js'),hl=e=>e.innerHTML=hljs.highlight(e.textContent,{language:'xml'}).value;return jars.html=C(el.htmlEditor,hl,{tab:' '}),jars.extension=C(el.extensionHtmlEditor,hl,{tab:' '}),jars};
|
||||
let openedHTML=!1;const openSettings=()=>{const a=SUNE.active,s=a.settings;openedHTML=!1,el.suneURL.value=a.url||'',el.set_model.value=s.model,el.set_temperature.value=s.temperature,el.set_top_p.value=s.top_p,el.set_top_k.value=s.top_k,el.set_frequency_penalty.value=s.frequency_penalty,el.set_presence_penalty.value=s.presence_penalty,el.set_repetition_penalty.value=s.repetition_penalty,el.set_min_p.value=s.min_p,el.set_top_a.value=s.top_a,el.set_max_tokens.value=s.max_tokens||'',el.set_verbosity.value=s.verbosity||'',el.set_reasoning_effort.value=s.reasoning_effort||'default',el.set_system_prompt.value=s.system_prompt,showTab('Model'),el.suneModal.classList.remove('hidden')},closeSettings=()=>el.suneModal.classList.add('hidden'),tabs={Model:['tabModel','panelModel'],Prompt:['tabPrompt','panelPrompt'],Script:['tabScript','panelScript']},showTab=key=>{Object.entries(tabs).forEach(([k,[tb,pn]])=>{el[tb].classList.toggle('border-black',k===key),el[pn].classList.toggle('hidden',k!==key)});if(key==='Script'){openedHTML=!0,showHtmlTab('index'),ensureJars().then(({html,extension})=>{const s=SUNE.settings;html.updateCode(s.html||''),extension.updateCode(s.extension_html||'')})}};
|
||||
el.suneBtnTop.addEventListener('click',openSettings),el.cancelSettings.addEventListener('click',closeSettings),el.suneModal.addEventListener('click',e=>(e.target===el.suneModal||e.target.classList.contains('bg-black/30'))&&closeSettings()),el.tabModel.addEventListener('click',()=>showTab('Model')),el.tabPrompt.addEventListener('click',()=>showTab('Prompt')),el.tabScript.addEventListener('click',()=>showTab('Script'));
|
||||
el.settingsForm.addEventListener('submit',async e=>{e.preventDefault(),SUNE.url=(el.suneURL.value||'').trim(),SUNE.model=(el.set_model.value||DEFAULT_MODEL).trim(),SUNE.temperature=clamp(num(el.set_temperature.value,1),0,2),SUNE.top_p=clamp(num(el.set_top_p.value,1),0,1),SUNE.top_k=Math.max(0,int(el.set_top_k.value,0)),SUNE.frequency_penalty=clamp(num(el.set_frequency_penalty.value,0),-2,2),SUNE.presence_penalty=clamp(num(el.set_presence_penalty.value,0),-2,2),SUNE.repetition_penalty=clamp(num(el.set_repetition_penalty.value,1),0,2),SUNE.min_p=clamp(num(el.set_min_p.value,0),0,1),SUNE.top_a=clamp(num(el.set_top_a.value,0),0,1),SUNE.max_tokens=Math.max(0,int(el.set_max_tokens.value,0)),SUNE.verbosity=el.set_verbosity.value||'',SUNE.reasoning_effort=el.set_reasoning_effort.value||'default',SUNE.system_prompt=el.set_system_prompt.value.trim(),openedHTML&&(SUNE.html=el.htmlEditor.textContent,SUNE.extension_html=el.extensionHtmlEditor.textContent),closeSettings(),await reflectActiveSune()});
|
||||
el.deleteSuneBtn.addEventListener('click',async()=>{const activeId=SUNE.id,name=SUNE.name||'this sune';if(confirm(`Delete "${name}"?`))SUNE.delete(activeId),renderSidebar(),await reflectActiveSune(),state.currentThreadId=null,clearChat(),closeSettings()}),el.newSuneBtn.addEventListener('click',async()=>{const name=prompt('Name your sune:');if(name){const sune=SUNE.create({name:name.trim()});SUNE.setActive(sune.id),renderSidebar(),await reflectActiveSune(),state.currentThreadId=null,clearChat(),el.sidebarLeft.classList.add('-translate-x-full'),el.sidebarOverlayLeft.classList.add('hidden')}});
|
||||
const dl=(name,obj)=>{const blob=new Blob([JSON.stringify(obj,null,2)],{type:name.endsWith('.sune')?'application/octet-stream':'application/json'}),url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url,a.download=name,document.body.appendChild(a),a.click(),a.remove(),URL.revokeObjectURL(url)},ts=()=>{const d=new Date,p=n=>String(n).padStart(2,'0');return`${d.getFullYear()}${p(d.getMonth()+1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`};
|
||||
let importMode=null;el.sunesExportOption.onclick=()=>dl(`sunes-${ts()}.sune`,{version:1,sunes:SUNE.list,activeId:SUNE.id}),el.sunesImportOption.onclick=()=>(importMode='sunes',el.importInput.value='',el.importInput.click()),el.threadsExportOption.onclick=()=>dl(`threads-${ts()}.json`,{version:1,threads}),el.threadsImportOption.onclick=()=>(importMode='threads',el.importInput.value='',el.importInput.click());
|
||||
el.importInput.addEventListener('change',async()=>{const file=el.importInput.files?.[0];if(!file)return;try{const data=JSON.parse(await file.text());if(importMode==='sunes'){const list=(Array.isArray(data)?data:data.sunes)||[];if(!list.length)throw new Error('No sunes');const map={},incoming=list.map(a=>makeSune(a||{}));incoming.forEach(s=>{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(),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)||[];if(!arr.length)throw new Error('No threads');const norm=t=>({id:t.id||gid(),title:titleFrom(t.title||t.messages?.find?.(m=>m.role==='user')?.content),pinned:!!t.pinned,updatedAt:t.updatedAt||Date.now(),messages:(t.messages||[]).filter(m=>m&&m.role&&m.content)}),best={};arr.forEach(t=>{const n=norm(t),k=n.id;(!best[k]||+n.updatedAt>+best[k].updatedAt)&&(best[k]=n)});let kept=0,skipped=0;const idx=Object.fromEntries(threads.map(t=>[t.id,t]));Object.values(best).forEach(th=>{const ex=idx[th.id];if(ex&&+ex.updatedAt>=+th.updatedAt)skipped++;else !ex?threads.push(th):Object.assign(ex,th),kept++;});await tsave(threads),await renderThreads(),alert(`${kept} imported, ${skipped} skipped (older).`)}el.userMenu.classList.add('hidden')}catch{alert('Import failed')}finally{importMode=null}});
|
||||
const kbUpdate=()=>{const v=window.visualViewport,overlap=v?Math.max(0,window.innerHeight-(v.height+v.offsetTop)):0,fh=el.footer.getBoundingClientRect().height;document.documentElement.style.setProperty('--kb',overlap+'px'),document.documentElement.style.setProperty('--footer-h',fh+'px'),el.footer.style.transform=`translateY(${-overlap}px)`,el.chat.style.scrollPaddingBottom=`${fh+overlap+16}px`},kbBind=()=>{window.visualViewport&&['resize','scroll'].forEach(ev=>visualViewport.addEventListener(ev,kbUpdate,{passive:!0}));['resize','orientationchange'].forEach(ev=>window.addEventListener(ev,()=>setTimeout(kbUpdate,50),{passive:!0}));['focus','click'].forEach(ev=>el.input.addEventListener(ev,()=>{setTimeout(()=>{kbUpdate(),el.input.scrollIntoView({block:'nearest',behavior:'smooth'})},0)}))},activeMeta=()=>({sune_name:SUNE.name,model:SUNE.model,avatar:SUNE.avatar});
|
||||
window.USER={log:async s=>{const t=String(s??'').trim();if(t)await ensureThreadOnFirstUser(t),addMessage({role:'user',content:[{type:'text',text:t}]}),await persistThread()},get PAT(){return globalStore.ghToken}};
|
||||
const init=async()=>{await SUNE.fetchDotSune('sune-org/store@main/marketplace.sune'),threads=await tload(),await renderThreads(),renderSidebar(),await reflectActiveSune(),clearChat(),icons(),kbBind(),kbUpdate()};
|
||||
window.addEventListener('resize',()=>{hideThreadPopover(),hideSunePopover()});
|
||||
const htmlTabs={index:['htmlTab_index','htmlEditor'],extension:['htmlTab_extension','extensionHtmlEditor']},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',el.htmlTab_index.onclick=()=>showHtmlTab('index'),el.htmlTab_extension.onclick=()=>showHtmlTab('extension');
|
||||
const HTTP_BASE='https://orp.awww.workers.dev/ws',buildBody=()=>{const msgs=[];if(SUNE.masterPrompt)msgs.push({role:'system',content:[{type:'text',text:SUNE.masterPrompt}]});if(SUNE.system_prompt)msgs.push({role:'system',content:[{type:'text',text:SUNE.system_prompt}]});msgs.push(...state.messages.filter(m=>m.role!=='system').map(m=>({role:m.role,content:m.content})));const b=payloadWithSampling({model:SUNE.model.replace(/^(or:|oai:)/,''),messages:msgs,stream:!0});if(SUNE.reasoning_effort&&SUNE.reasoning_effort!=='default')b.reasoning={effort:SUNE.reasoning_effort};if(SUNE.verbosity)b.verbosity=SUNE.verbosity;return b};
|
||||
const askOpenRouterStreaming=async(onDelta,streamId)=>{const model=SUNE.model,provider=model.startsWith('oai:')?'openai':model.startsWith('or:')?'openrouter':SUNE.provider,apiKey=provider==='openai'?SUNE.apiKeyOAI:SUNE.apiKeyOR;if(!apiKey)return onDelta(localDemoReply(),!0),{ok:!0,rid:streamId||null};const r={rid:streamId||gid(),seq:-1,done:!1,signaled:!1,ws:null};await cacheStore.setItem(r.rid,'busy');const signal=t=>{if(!r.signaled)r.signaled=!0,onDelta(t||'',!0)};const ws=new WebSocket(`${HTTP_BASE.replace('https','wss')}?uid=${encodeURIComponent(r.rid)}`);r.ws=ws,ws.onopen=()=>ws.send(JSON.stringify({type:'begin',rid:r.rid,provider,apiKey,or_body:buildBody()})),ws.onmessage=e=>{let m;try{m=JSON.parse(e.data)}catch{return}if(m.type==='delta'&&typeof m.seq=='number'&&m.seq>r.seq)r.seq=m.seq,onDelta(m.text||'',!1);else if(m.type==='done'||m.type==='err')r.done=!0,cacheStore.setItem(r.rid,'done'),signal(m.type==='err'?'\n\n'+(m.message||'error'):''),ws.close()},ws.onclose=()=>{},ws.onerror=()=>{},state.controller={abort:()=>{r.done=!0,cacheStore.setItem(r.rid,'done');try{ws.readyState===1&&ws.send(JSON.stringify({type:'stop',rid:r.rid}))}catch{}signal('')},disconnect:()=>ws.close()};return{ok:!0,rid:r.rid}};
|
||||
const accountTabs={General:['accountTabGeneral','accountPanelGeneral'],API:['accountTabAPI','accountPanelAPI']},showAccountTab=key=>{Object.entries(accountTabs).forEach(([k,[tb,pn]])=>{el[tb].classList.toggle('border-black',k===key),el[pn].classList.toggle('hidden',k!==key)})},openAccountSettings=()=>{el.set_provider.value=SUNE.provider||'openrouter',el.set_api_key_or.value=SUNE.apiKeyOR||'',el.set_api_key_oai.value=SUNE.apiKeyOAI||'',el.set_master_prompt.value=SUNE.masterPrompt||'',el.set_title_model.value=SUNE.titleModel,el.set_gh_token.value=globalStore.ghToken||'',showAccountTab('General'),el.accountSettingsModal.classList.remove('hidden')},closeAccountSettings=()=>el.accountSettingsModal.classList.add('hidden');
|
||||
el.accountSettingsOption.addEventListener('click',()=>{el.userMenu.classList.add('hidden'),openAccountSettings()}),el.closeAccountSettings.addEventListener('click',closeAccountSettings),el.cancelAccountSettings.addEventListener('click',closeAccountSettings),el.accountSettingsModal.addEventListener('click',e=>(e.target===el.accountSettingsModal||e.target.classList.contains('bg-black/30'))&&closeAccountSettings());
|
||||
el.accountSettingsForm.addEventListener('submit',e=>{e.preventDefault(),SUNE.provider=el.set_provider.value||'openrouter',SUNE.apiKeyOR=String(el.set_api_key_or.value||'').trim(),SUNE.apiKeyOAI=String(el.set_api_key_oai.value||'').trim(),SUNE.masterPrompt=String(el.set_master_prompt.value||'').trim(),SUNE.titleModel=String(el.set_title_model.value||'').trim(),globalStore.ghToken=String(el.set_gh_token.value||'').trim(),closeAccountSettings()});
|
||||
el.accountTabGeneral.onclick=()=>showAccountTab('General'),el.accountTabAPI.onclick=()=>showAccountTab('API'),el.exportAccountSettings.onclick=()=>dl(`sune-account-${ts()}.json`,{v:1,provider:globalStore.provider,apiKeyOR:globalStore.apiKeyOR,apiKeyOAI:globalStore.apiKeyOAI,masterPrompt:globalStore.masterPrompt,titleModel:globalStore.titleModel,ghToken:globalStore.ghToken}),el.importAccountSettings.onclick=()=>{el.importAccountSettingsInput.value='',el.importAccountSettingsInput.click()},el.importAccountSettingsInput.onchange=async e=>{const f=e.target.files?.[0];if(f)try{const d=JSON.parse(await f.text());if(d&&typeof d=='object')Object.entries({provider:'string',apiKeyOR:'string',apiKeyOAI:'string',masterPrompt:'string',titleModel:'string',ghToken:'string'}).forEach(([k,t])=>{if(typeof d[k]===t)k==='ghToken'?globalStore[k]=d[k]:SUNE[k]=d[k]}),openAccountSettings(),alert('Imported.')}catch{alert('Import failed')}};
|
||||
const lastAssistantId=()=>{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&&!/^\s*You\b/.test(h.textContent||''))return b.dataset.mid||null}return null},getBubbleById=id=>el.messages.querySelector(`.msg-bubble[data-mid="${CSS.escape(id)}"]`);
|
||||
const syncActiveThread=async()=>{const id=lastAssistantId();if(!id||await cacheStore.getItem(id)==='done'){if(state.busy)setBtnSend(),state.busy=!1,state.controller=null;return!1}if(!state.busy)state.busy=!0,state.controller={abort:()=>{const ws=new WebSocket(HTTP_BASE.replace('https','wss'));ws.onopen=function(){this.send(JSON.stringify({type:'stop',rid:id})),this.close()}}},setBtnStop();const bubble=getBubbleById(id);if(!bubble)return!1;const prevText=bubble.textContent||'',j=await fetch(`${HTTP_BASE}?uid=${encodeURIComponent(id)}`).then(r=>r.ok?r.json():null).catch(()=>null);const finalise=(t,c)=>{renderMarkdown(bubble,t,{highlight:!1}),enhanceCodeBlocks(bubble),state.messages.find(x=>x.id===id).content=c,persistThread(),setBtnSend(),state.busy=!1,cacheStore.setItem(id,'done'),state.controller=null};if(!j||j.rid!==id){if(j?.error)finalise(prevText+'\n\n'+j.error,[{type:'text',text:prevText+'\n\n'+j.error}]);return!1}const text=j.text||'',isDone=j.error||j.done||j.phase==='done';if(text)renderMarkdown(bubble,text,{highlight:!1});if(isDone)return finalise(text||prevText,[{type:'text',text:text||prevText}]),!1;return await cacheStore.setItem(id,'busy'),!0};
|
||||
let syncLoopRunning=!1;const syncWhileBusy=async()=>{if(syncLoopRunning||document.visibilityState==='hidden')return;syncLoopRunning=!0;try{while(await syncActiveThread())await new Promise(r=>setTimeout(r,1200))}finally{syncLoopRunning=!1}},onForeground=()=>{if(document.visibilityState==='visible')state.controller?.disconnect?.(),state.busy&&syncWhileBusy()};
|
||||
document.addEventListener('visibilitychange',onForeground);
|
||||
el.copySystemPrompt.onclick=async()=>await navigator.clipboard.writeText(el.set_system_prompt.value||'').catch(()=>{}),el.pasteSystemPrompt.onclick=async()=>el.set_system_prompt.value=await navigator.clipboard.readText().catch(()=>'');
|
||||
const getActiveHtmlParts=()=>el.htmlEditor.classList.contains('hidden')?[el.extensionHtmlEditor,jars.extension]:[el.htmlEditor,jars.html];
|
||||
el.copyHTML.onclick=async()=>await navigator.clipboard.writeText(getActiveHtmlParts()[0].textContent||'').catch(()=>{}),el.pasteHTML.onclick=async()=>{try{const t=await navigator.clipboard.readText(),[editor,jar]=getActiveHtmlParts();jar?.updateCode?jar.updateCode(t):editor&&(editor.textContent=t)}catch{}};
|
||||
init();
|
||||
const state=window.state={messages:[],busy:false,controller:null,currentThreadId:null,abortRequested:false,attachments:[],stream:{rid:null,bubble:null,meta:null,text:'',done:false}}
|
||||
const getModelShort=m=>{const mm=m||SUNE.model||'';return mm.includes('/')?mm.split('/').pop():mm}
|
||||
const resolveSuneSrc=src=>{if(!src)return null;if(src.startsWith('gh://')){const path=src.substring(5),parts=path.split('/');if(parts.length<3)return null;const[owner,repo,...filePathParts]=parts;return`https://raw.githubusercontent.com/${owner}/${repo}/main/${filePathParts.join('/')}`}return src}
|
||||
const processSuneIncludes=async(html,depth=0)=>{if(depth>5)return'<!-- Sune include depth limit reached -->';if(!html)return'';const c=document.createElement('div');c.innerHTML=html;for(const n of[...c.querySelectorAll('sune')]){if(n.hasAttribute('src')){if(n.hasAttribute('private')&&depth>0){n.remove();continue}const s=n.getAttribute('src'),u=resolveSuneSrc(s);if(!u){n.replaceWith(document.createComment(` Invalid src: ${esc(s)} `));continue}try{const r=await fetch(u);if(!r.ok)throw new Error(`HTTP ${r.status}`);const d=await r.json(),o=Array.isArray(d)?d[0]:d,h=[o?.settings?.html||'',o?.settings?.extension_html||''].join('\n');n.replaceWith(document.createRange().createContextualFragment(await processSuneIncludes(h,depth+1)))}catch(e){n.replaceWith(document.createComment(` Fetch failed: ${esc(u)} `))}}else{n.replaceWith(document.createRange().createContextualFragment(n.innerHTML))}}return c.innerHTML}
|
||||
const renderSuneHTML=async()=>{const baseHtml=[SUNE.html,SUNE.extension_html].map(x=>(x||'').trim()).join('\n');const finalHtml=await processSuneIncludes(baseHtml,0);const container=el.suneHtml;container.innerHTML='';container.classList.toggle('hidden',!finalHtml.trim());if(!finalHtml.trim())return;container.insertAdjacentHTML('afterbegin',finalHtml);const scripts=[];container.querySelectorAll('script').forEach(s=>{if(!s.type||s.type==='text/javascript'){scripts.push(s.textContent);s.remove()}});if(scripts.length)setTimeout(()=>scripts.forEach(code=>{try{new Function(code)()}catch(e){console.error('Sune script error:',e)}}),0)}
|
||||
const reflectActiveSune=async()=>{const a=SUNE.active;el.suneBtnTop.title=`Settings — ${a.name}`;el.suneBtnTop.innerHTML=a.avatar?`<img src="${esc(a.avatar)}" alt="" class="h-8 w-8 rounded-full object-cover"/>`:'✺';await renderSuneHTML();icons()}
|
||||
const suneRow=a=>`<div class="relative flex items-center gap-2 px-3 py-2 ${a.pinned?'bg-yellow-50':''}"><button data-sune-id="${a.id}" class="flex-1 text-left flex items-center gap-2 ${a.id===SUNE.id?'font-medium':''}">${a.avatar?`<img src="${esc(a.avatar)}" alt="" class="h-6 w-6 rounded-full object-cover"/>`:`<span class="h-6 w-6 rounded-full bg-gray-200 flex items-center justify-center">✺</span>`}<span class="truncate">${a.pinned?'📌 ':''}${esc(a.name)}</span></button><button data-sune-menu="${a.id}" class="h-8 w-8 rounded hover:bg-gray-100 flex items-center justify-center" title="More"><i data-lucide="more-horizontal" class="h-4 w-4"></i></button></div>`
|
||||
const renderSidebar=window.renderSidebar=()=>{const list=[...SUNE.list].sort((a,b)=>(b.pinned-a.pinned));el.suneList.innerHTML=list.map(suneRow).join('');icons()}
|
||||
function enhanceCodeBlocks(root,doHL=true){root.querySelectorAll('pre>code').forEach(code=>{if(code.textContent.length>200000)return;const pre=code.parentElement;pre.classList.add('relative','rounded-xl','border','border-gray-200');if(!pre.querySelector('.copy-btn')){const btn=document.createElement('button');btn.className='copy-btn';btn.textContent='Copy';btn.addEventListener('click',async e=>{e.stopPropagation();try{await navigator.clipboard.writeText(code.innerText);btn.textContent='Copied';setTimeout(()=>btn.textContent='Copy',1200)}catch{}});pre.appendChild(btn)}if(doHL&&window.hljs&&code.textContent.length<100000)hljs.highlightElement(code)})}
|
||||
const md=window.markdownit({html:false,linkify:true,typographer:true,breaks:true})
|
||||
const getSuneLabel=m=>{const name=(m&&m.sune_name)||SUNE.name,modelShort=getModelShort(m&&m.model);return `${name} · ${modelShort}`}
|
||||
function msgRow(m){const role=typeof m==='string'?m:(m&&m.role)||'assistant';const meta=typeof m==='string'?{}:m||{};const row=document.createElement('div');row.className='flex flex-col gap-2';const head=document.createElement('div');head.className='flex items-center gap-2 px-4';const avatar=document.createElement('div');if(role==='user'){avatar.className='bg-gray-900 text-white msg-avatar shrink-0 h-7 w-7 rounded-full flex items-center justify-center';avatar.textContent='🧑'}else{if(meta&&meta.avatar){avatar.className='msg-avatar shrink-0 h-7 w-7 rounded-full overflow-hidden';const img=document.createElement('img');img.src=meta.avatar;img.className='h-full w-full object-cover';avatar.appendChild(img)}else{avatar.className='bg-gray-200 text-gray-900 msg-avatar shrink-0 h-7 w-7 rounded-full flex items-center justify-center';avatar.textContent='✺'}}const name=document.createElement('div');name.className='text-xs font-medium text-gray-500';name.textContent=role==='user'?'You':getSuneLabel(meta);const deleteBtn=document.createElement('button');deleteBtn.className='ml-auto p-1.5 rounded-lg hover:bg-gray-200 text-gray-400 hover:text-red-500';deleteBtn.title='Delete message';deleteBtn.innerHTML='<i data-lucide="eraser" class="h-4 w-4"></i>';deleteBtn.onclick=async e=>{e.stopPropagation();if(!confirm('Delete message?'))return;state.messages=state.messages.filter(msg=>msg.id!==m.id);row.remove();await persistThread()};const copyBtn=document.createElement('button');copyBtn.className='p-1.5 rounded-lg hover:bg-gray-200 text-gray-400 hover:text-gray-600';copyBtn.title='Copy message';copyBtn.innerHTML='<i data-lucide="copy" class="h-4 w-4"></i>';copyBtn.onclick=async function(e){e.stopPropagation();const b=this.parentElement.nextElementSibling;if(!b)return;try{await navigator.clipboard.writeText(b.innerText);this.innerHTML='<i data-lucide="check" class="h-4 w-4 text-green-500"></i>';icons();setTimeout(()=>{this.innerHTML='<i data-lucide="copy" class="h-4 w-4"></i>';icons()},1200)}catch{}};head.appendChild(avatar);head.appendChild(name);head.appendChild(deleteBtn);head.appendChild(copyBtn);const bubble=document.createElement('div');bubble.className=(role==='user'?'bg-gray-50 border border-gray-200':'bg-gray-100')+' msg-bubble markdown-body rounded-none px-4 py-3 w-full';row.appendChild(head);row.appendChild(bubble);el.messages.appendChild(row);queueMicrotask(()=>{el.chat.scrollTo({top:el.chat.scrollHeight,behavior:'smooth'});icons()});return bubble}
|
||||
const renderMarkdown=window.renderMarkdown=function(node,text,opt={enhance:true,highlight:true}){node.innerHTML=md.render(text);if(opt.enhance)enhanceCodeBlocks(node,opt.highlight)}
|
||||
function partsToText(parts){if(!parts)return'';if(Array.isArray(parts))return parts.map(p=>p?.type==='text'?p.text:(p?.type==='image_url'?``:(p?.type==='file'?`[${p.file?.filename||'file'}]`:(p?.type==='input_audio'?`(audio:${p.input_audio?.format||''})`:'')))).join('\n');return String(parts)}
|
||||
const addMessage=window.addMessage=function(m,track=true){m.id=m.id||gid();if(!Array.isArray(m.content)&&m.content!=null){m.content=[{type:'text',text:String(m.content)}]}const bubble=msgRow(m);bubble.dataset.mid=m.id;renderMarkdown(bubble,partsToText(m.content));if(track)state.messages.push(m);return bubble}
|
||||
const addSuneBubbleStreaming=meta=>msgRow(Object.assign({role:'assistant'},meta))
|
||||
const clearChat=()=>{state.messages=[];el.messages.innerHTML='';state.attachments=[];updateAttachBadge();el.fileInput.value=''}
|
||||
const payloadWithSampling=b=>{const o=Object.assign({},b);o.temperature=SUNE.temperature;o.top_p=SUNE.top_p;o.top_k=SUNE.top_k;o.frequency_penalty=SUNE.frequency_penalty;o.presence_penalty=SUNE.presence_penalty;o.repetition_penalty=SUNE.repetition_penalty;o.min_p=SUNE.min_p;o.top_a=SUNE.top_a;const mt=Math.max(0,int(SUNE.max_tokens||0,0));if(mt)o.max_tokens=mt;return o}
|
||||
function setBtnStop(){const b=el.sendBtn;b.dataset.mode='stop';b.type='button';b.setAttribute('aria-label','Stop');b.innerHTML='<i data-lucide="square" class="h-5 w-5"></i>';icons();b.onclick=()=>{state.abortRequested=true;state.controller?.abort?.()}}
|
||||
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.'}
|
||||
let threads=[];const titleFrom=t=>(t||'').replace(/\s+/g,' ').trim().slice(0,60)||'Untitled'
|
||||
const TKEY='threads_v1',tload=()=>localforage.getItem(TKEY).then(v=>Array.isArray(v)?v:[]),tsave=v=>localforage.setItem(TKEY,v)
|
||||
const cacheStore=localforage.createInstance({name:'master_cache'});
|
||||
async function ensureThreadOnFirstUser(text){let needNew=!state.currentThreadId;if(state.messages.length===0)state.currentThreadId=null;if(state.currentThreadId&&!threads.some(x=>x.id===state.currentThreadId))needNew=true;if(!needNew)return;const id=gid(),now=Date.now(),th={id,title:window.doCreateTitle?titleFrom(text):'',pinned:false,updatedAt:now,messages:[]};state.currentThreadId=id;threads.unshift(th);await tsave(threads);await renderThreads();document.dispatchEvent(new CustomEvent('sune:new-thread',{detail:{threadId:id}}))}
|
||||
async function persistThread(full=true){if(!state.currentThreadId)return;let th=threads.find(x=>x.id===state.currentThreadId);if(!th)return;th.messages=[...state.messages];if(full){th.updatedAt=Date.now();if(window.doCreateTitle)th.title=titleFrom(partsToText(th.messages.find(m=>m.role==='user')?.content)||th.title)}await tsave(threads);if(full)await renderThreads()}
|
||||
const threadRow=t=>`<div class=\"relative flex items-center gap-2 px-3 py-2 ${t.pinned?'bg-yellow-50':''}\"><button data-open-thread=\"${t.id}\" class=\"flex-1 text-left truncate\">${t.pinned?'📌 ':''}${esc(t.title)}</button><button data-thread-menu=\"${t.id}\" class=\"h-8 w-8 rounded hover:bg-gray-100 flex items-center justify-center\" title=\"More\"><i data-lucide=\"more-horizontal\" class=\"h-4 w-4\"></i></button></div>`
|
||||
async function renderThreads(){const list=[...threads].sort((a,b)=>(b.pinned-a.pinned)||(b.updatedAt-a.updatedAt));el.threadList.innerHTML=list.map(threadRow).join('');icons()}
|
||||
let menuThreadId=null;const hideThreadPopover=()=>{el.threadPopover.classList.add('hidden');menuThreadId=null}
|
||||
function showThreadPopover(btn,id){menuThreadId=id;const r=btn.getBoundingClientRect();el.threadPopover.style.top=(r.bottom+4)+'px';el.threadPopover.style.left=Math.min(window.innerWidth-220,r.right-200)+'px';el.threadPopover.classList.remove('hidden');icons()}
|
||||
let menuSuneId=null;const hideSunePopover=()=>{el.sunePopover.classList.add('hidden');menuSuneId=null}
|
||||
function showSunePopover(btn,id){menuSuneId=id;const r=btn.getBoundingClientRect();el.sunePopover.style.top=(r.bottom+4)+'px';el.sunePopover.style.left=Math.min(window.innerWidth-220,r.right-200)+'px';el.sunePopover.classList.remove('hidden');icons()}
|
||||
el.threadList.addEventListener('click',async e=>{const openBtn=e.target.closest('[data-open-thread]'),menuBtn=e.target.closest('[data-thread-menu]');if(openBtn){const id=openBtn.getAttribute('data-open-thread');if(id!==state.currentThreadId&&state.busy){state.controller?.disconnect?.();setBtnSend();state.busy=false;state.controller=null}const th=threads.find(t=>t.id===id);if(!th)return;if(id===state.currentThreadId){el.sidebarRight.classList.add('translate-x-full');el.sidebarOverlayRight.classList.add('hidden');hideThreadPopover();return}state.currentThreadId=id;clearChat();state.messages=Array.isArray(th.messages)?[...th.messages]:[];for(const m of state.messages){const b=msgRow(m);b.dataset.mid=m.id||'';renderMarkdown(b,partsToText(m.content))}await renderSuneHTML();syncWhileBusy();queueMicrotask(()=>el.chat.scrollTo({top:el.chat.scrollHeight,behavior:'smooth'}));el.sidebarRight.classList.add('translate-x-full');el.sidebarOverlayRight.classList.add('hidden');hideThreadPopover();return}if(menuBtn){e.stopPropagation();showThreadPopover(menuBtn,menuBtn.getAttribute('[data-thread-menu]')?menuBtn.getAttribute('[data-thread-menu]'):menuBtn.getAttribute('data-thread-menu'))}})
|
||||
el.threadPopover.addEventListener('click',async e=>{const act=e.target.closest('[data-action]')?.getAttribute('data-action');if(!act||!menuThreadId)return;const th=threads.find(t=>t.id===menuThreadId);if(!th)return;if(act==='pin'){th.pinned=!th.pinned}else if(act==='rename'){const nv=prompt('Rename to:',th.title);if(nv!=null){th.title=titleFrom(nv);th.updatedAt=Date.now()}}else if(act==='delete'){if(confirm('Delete this chat?')){threads=threads.filter(x=>x.id!==th.id);if(state.currentThreadId===th.id){state.currentThreadId=null;clearChat()}}}else if(act==='count_tokens'){const msgs=Array.isArray(th.messages)?th.messages:[];let totalChars=0;for(const m of msgs){if(!m||!m.role||m.role==='system')continue;totalChars+=String(partsToText(m.content||'')||'').length}const tokens=Math.max(0,Math.ceil(totalChars/4));const k=tokens>=1000?Math.round(tokens/1000)+'k':String(tokens);alert(tokens+' tokens ('+k+')')}hideThreadPopover();await tsave(threads);renderThreads()})
|
||||
el.suneList.addEventListener('click',async e=>{const menuBtn=e.target.closest('[data-sune-menu]');if(menuBtn){e.stopPropagation();showSunePopover(menuBtn,menuBtn.getAttribute('[data-sune-menu]')?menuBtn.getAttribute('[data-sune-menu]'):menuBtn.getAttribute('data-sune-menu'));return}const btn=e.target.closest('[data-sune-id]');if(!btn)return;const id=btn.getAttribute('data-sune-id');if(id){if(state.busy){state.controller?.disconnect?.();setBtnSend();state.busy=false;state.controller=null};SUNE.setActive(id);renderSidebar();await reflectActiveSune();state.currentThreadId=null;clearChat();document.getElementById('sidebarLeft').classList.add('-translate-x-full');document.getElementById('sidebarOverlayLeft').classList.add('hidden')}})
|
||||
el.sunePopover.addEventListener('click',async e=>{const act=e.target.closest('[data-action]')?.getAttribute('data-action');if(!act||!menuSuneId)return;const s=SUNE.get(menuSuneId);if(!s)return;const updateAndRender=async()=>{s.updatedAt=Date.now();SUNE.save();renderSidebar();await reflectActiveSune()};if(act==='pin'){s.pinned=!s.pinned;await updateAndRender()}else if(act==='rename'){const n=prompt('Rename sune to:',s.name);if(n!=null){s.name=n.trim();await updateAndRender()}}else if(act==='pfp'){const i=document.createElement('input');i.type='file';i.accept='image/*';i.onchange=()=>{const f=i.files?.[0];if(!f)return;const img=new Image;img.onload=async()=>{const c=document.createElement('canvas'),ctx=c.getContext('2d'),D=144;let w=img.width,h=img.height;if(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;ctx.drawImage(img,0,0,w,h);s.avatar=c.toDataURL('image/webp',.84);await updateAndRender();URL.revokeObjectURL(img.src)};img.src=URL.createObjectURL(f)};i.click()}else if(act==='export')dl(`sune-${(s.name||'sune').replace(/\W/g,'_')}-${ts()}.sune`,[s]);hideSunePopover()})
|
||||
function updateAttachBadge(){const n=state.attachments.length;el.attachBadge.textContent=String(n);el.attachBadge.classList.toggle('hidden',n===0)}
|
||||
async function toAttach(file){if(!file)return null;const pick=(name,bytes,mime,data,mode,part)=>({name,bytes,mime,data,mode,part});if(file instanceof File){const name=file.name||'file',mime=(file.type||'application/octet-stream').toLowerCase(),bytes=file.size||0;if(/^image\//.test(mime)||/\.(png|jpe?g|webp|gif)$/i.test(name)){const data=await asDataURL(file);return pick(name,bytes,mime,data,'dataURL',{type:'image_url',image_url:{url:data}})}if(mime==='application/pdf'||/\.pdf$/i.test(name)){const data=await asDataURL(file),bin=b64(data);return pick(name.endsWith('.pdf')?name:name+'.pdf',bytes,'application/pdf',bin,'base64',{type:'file',file:{filename:name,file_data:bin}})}if(/^audio\//.test(mime)||/\.(wav|mp3)$/i.test(name)){const data=await asDataURL(file),bin=b64(data),fmt=/mp3/.test(mime)||/\.mp3$/i.test(name)?'mp3':'wav';return pick(name,bytes,mime,bin,'base64',{type:'input_audio',input_audio:{data:bin,format:fmt}})}const data=await asDataURL(file),bin=b64(data);return pick(name,bytes,mime,bin,'base64',{type:'file',file:{filename:name,file_data:bin}})}if(file&&file.name==null&&file.data){const name=file.name||'file',mime=(file.mime||'application/octet-stream').toLowerCase(),bytes=file.size||0;if(/^image\//.test(mime)){const url=`data:${mime};base64,${file.data}`;return pick(name,bytes,mime,url,'dataURL',{type:'image_url',image_url:{url}})}if(mime==='application/pdf'){return pick(name,bytes,mime,file.data,'base64',{type:'file',file:{filename:name,file_data:file.data}})}if(/^audio\//.test(mime)){const fmt=/mp3/.test(mime)?'mp3':'wav';return pick(name,bytes,mime,file.data,'base64',{type:'input_audio',input_audio:{data:file.data,format:fmt}})}return pick(name,bytes,mime,file.data,'base64',{type:'file',file:{filename:name,file_data:file.data}})}return null}
|
||||
function attachmentsText(id,arr){const head='**Attachments**',list=arr.map((a,i)=>`- [${esc(a.name)} • ${fmtSize(a.bytes)}](#dl-${id}-${i})`).join('\n');return head+'\n'+list}
|
||||
function addAttachmentTree(role,arr){if(!arr?.length)return;const id=gid(),text=attachmentsText(id,arr),meta={role,content:[{type:'text',text}],id,kind:'attachments',attachmentsMeta:arr.map(a=>({name:a.name,bytes:a.bytes,mime:a.mime,mode:a.mode,data:a.mode==='dataURL'?a.data:a.data}))};const b=addMessage(meta,true);b.dataset.mid=id}
|
||||
el.attachBtn.addEventListener('click',()=>{if(state.busy)return;if(state.attachments.length){state.attachments=[];updateAttachBadge();el.fileInput.value=''};el.fileInput.click()})
|
||||
el.fileInput.addEventListener('change',async()=>{const files=[...(el.fileInput.files||[])];if(!files.length)return;for(const f of files){const at=await toAttach(f).catch(()=>null);if(at)state.attachments.push(at)}updateAttachBadge()})
|
||||
el.messages.addEventListener('click',async e=>{const a=e.target.closest('a[href^="#dl-"]');if(!a)return; e.preventDefault();const m=a.getAttribute('href').match(/^#dl-([^-]+)-(\d+)$/);if(!m)return;const id=m[1],i=+m[2];const msg=state.messages.find(x=>x.id===id),meta=msg?.attachmentsMeta?.[i];if(!meta)return;let blob;if(meta.mode==='dataURL'){blob=await (await fetch(meta.data)).blob()}else{const bin=Uint8Array.from(atob(meta.data),c=>c.charCodeAt(0));blob=new Blob([bin],{type:meta.mime||'application/octet-stream'})}const url=URL.createObjectURL(blob),dl=document.createElement('a');dl.href=url;dl.download=meta.name||'download';document.body.appendChild(dl);dl.click();dl.remove();URL.revokeObjectURL(url)})
|
||||
el.composer.addEventListener('submit',async e=>{e.preventDefault();if(state.busy)return;const text=el.input.value.trim();if(!text&&!state.attachments.length)return;if(state.messages.length===0)state.currentThreadId=null;await ensureThreadOnFirstUser(text||'(attachments)');el.input.value='';const parts=[];if(text)parts.push({type:'text',text});state.attachments.forEach(a=>parts.push(a.part));addMessage({role:'user',content:parts.length?parts:[{type:'text',text:text||'(sent attachments)'}]});if(state.attachments.length)addAttachmentTree('user',state.attachments);state.busy=true;setBtnStop();const a=SUNE.active;const suneMeta={sune_name:a.name,model:SUNE.model,avatar:a.avatar||''};const suneBubble=addSuneBubbleStreaming(suneMeta);const streamId=sid();suneBubble.dataset.mid=streamId;const assistantMsg=Object.assign({id:streamId,role:'assistant',content:[{type:'text',text:''}]},suneMeta);state.messages.push(assistantMsg);persistThread(false);state.stream={rid:streamId,bubble:suneBubble,meta:suneMeta,text:'',done:false};let buf='',completed=false;const onDelta=(delta,done)=>{buf+=delta;state.stream.text=buf;renderMarkdown(suneBubble,buf,{enhance:false});assistantMsg.content[0].text=buf;if(done&&!completed){completed=true;setBtnSend();state.busy=false;enhanceCodeBlocks(suneBubble,true);persistThread(true);state.stream={rid:null,bubble:null,meta:null,text:'',done:false}}else if(!done)persistThread(false)};await askOpenRouterStreaming(onDelta,streamId);state.attachments=[];updateAttachBadge()})
|
||||
let jars={html:null,extension:null};const ensureJars=async()=>{if(jars.html&&jars.extension)return jars;const mod=await import('https://medv.io/codejar/codejar.js'),CodeJar=mod.CodeJar||mod.default,hl=e=>e.innerHTML=hljs.highlight(e.textContent,{language:'xml'}).value;if(!jars.html)jars.html=CodeJar(el.htmlEditor,hl,{tab:' '});if(!jars.extension)jars.extension=CodeJar(el.extensionHtmlEditor,hl,{tab:' '});return jars}
|
||||
let openedHTML=false
|
||||
function openSettings(){const a=SUNE.active,s=a.settings;openedHTML=false;el.suneURL.value=a.url||'';el.set_model.value=s.model;el.set_temperature.value=s.temperature;el.set_top_p.value=s.top_p;el.set_top_k.value=s.top_k;el.set_frequency_penalty.value=s.frequency_penalty;el.set_presence_penalty.value=s.presence_penalty;el.set_repetition_penalty.value=s.repetition_penalty;el.set_min_p.value=s.min_p;el.set_top_a.value=s.top_a;el.set_max_tokens.value=s.max_tokens||'';el.set_verbosity.value=s.verbosity||'';el.set_reasoning_effort.value=s.reasoning_effort||'default';el.set_system_prompt.value=s.system_prompt;showTab('Model');el.suneModal.classList.remove('hidden')}
|
||||
const closeSettings=()=>{el.suneModal.classList.add('hidden')}
|
||||
const tabs={Model:['tabModel','panelModel'],Prompt:['tabPrompt','panelPrompt'],Script:['tabScript','panelScript']}
|
||||
function showTab(key){Object.entries(tabs).forEach(([k,[tb,pn]])=>{el[tb].classList.toggle('border-black',k===key);el[pn].classList.toggle('hidden',k!==key)});if(key==='Script'){openedHTML=true;showHtmlTab('index');ensureJars().then(({html,extension})=>{const s=SUNE.settings;html.updateCode(s.html||'');extension.updateCode(s.extension_html||'')})}}
|
||||
el.suneBtnTop.addEventListener('click',openSettings)
|
||||
el.cancelSettings.addEventListener('click',closeSettings)
|
||||
el.suneModal.addEventListener('click',e=>{if(e.target===el.suneModal||e.target.classList.contains('bg-black/30'))closeSettings()})
|
||||
el.tabModel.addEventListener('click',()=>showTab('Model'))
|
||||
el.tabPrompt.addEventListener('click',()=>showTab('Prompt'))
|
||||
el.tabScript.addEventListener('click',()=>showTab('Script'))
|
||||
el.settingsForm.addEventListener('submit',async e=>{e.preventDefault();SUNE.url=(el.suneURL.value||'').trim();SUNE.model=(el.set_model.value||DEFAULT_MODEL).trim();SUNE.temperature=clamp(num(el.set_temperature.value,1.0),0,2);SUNE.top_p=clamp(num(el.set_top_p.value,1.0),0,1);SUNE.top_k=Math.max(0,int(el.set_top_k.value,0));SUNE.frequency_penalty=clamp(num(el.set_frequency_penalty.value,0.0),-2,2);SUNE.presence_penalty=clamp(num(el.set_presence_penalty.value,0.0),-2,2);SUNE.repetition_penalty=clamp(num(el.set_repetition_penalty.value,1.0),0,2);SUNE.min_p=clamp(num(el.set_min_p.value,0.0),0,1);SUNE.top_a=clamp(num(el.set_top_a.value,0.0),0,1);SUNE.max_tokens=Math.max(0,int(el.set_max_tokens.value,0));SUNE.verbosity=(el.set_verbosity.value||'');SUNE.reasoning_effort=(el.set_reasoning_effort.value||'default');SUNE.system_prompt=el.set_system_prompt.value.trim();if(openedHTML){SUNE.html=el.htmlEditor.textContent;SUNE.extension_html=el.extensionHtmlEditor.textContent}closeSettings();await reflectActiveSune()})
|
||||
el.deleteSuneBtn.addEventListener('click',async()=>{const activeId=SUNE.id,name=SUNE.name||'this sune';if(!confirm(`Delete "${name}"?`))return;SUNE.delete(activeId);renderSidebar();await reflectActiveSune();state.currentThreadId=null;clearChat();closeSettings()})
|
||||
el.newSuneBtn.addEventListener('click',async()=>{const name=prompt('Name your sune:');if(!name)return;const sune=SUNE.create({name:name.trim()});SUNE.setActive(sune.id);renderSidebar();await reflectActiveSune();state.currentThreadId=null;clearChat();document.getElementById('sidebarLeft').classList.add('-translate-x-full');document.getElementById('sidebarOverlayLeft').classList.add('hidden')})
|
||||
function dl(name,obj){const blob=new Blob([JSON.stringify(obj,null,2)],{type:name.endsWith('.sune')?'application/octet-stream':'application/json'}),url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=name;document.body.appendChild(a);a.click();a.remove();URL.revokeObjectURL(url)}
|
||||
const ts=()=>{const d=new Date(),p=n=>String(n).padStart(2,'0');return `${d.getFullYear()}${p(d.getMonth()+1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`}
|
||||
let importMode=null
|
||||
el.sunesExportOption.addEventListener('click',()=>{dl(`sunes-${ts()}.sune`,{version:1,sunes:SUNE.list,activeId:SUNE.id});el.userMenu.classList.add('hidden')})
|
||||
el.sunesImportOption.addEventListener('click',()=>{importMode='sunes';el.importInput.value='';el.importInput.click()})
|
||||
el.threadsExportOption.addEventListener('click',()=>{dl(`threads-${ts()}.json`,{version:1,threads});el.userMenu.classList.add('hidden')})
|
||||
el.threadsImportOption.addEventListener('click',()=>{importMode='threads';el.importInput.value='';el.importInput.click()})
|
||||
el.importInput.addEventListener('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:(Array.isArray(data.threads)?data.threads:[]);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(threads.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}if(!ex)threads.push(th);else Object.assign(ex,th);kept++}await tsave(threads);await renderThreads();alert(`${kept} imported, ${skipped} skipped (older).`)}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}))}['resize','orientationchange'].forEach(ev=>window.addEventListener(ev,()=>setTimeout(kbUpdate,50),{passive:true}));['focus','click'].forEach(ev=>el.input.addEventListener(ev,()=>{setTimeout(()=>{kbUpdate();el.input.scrollIntoView({block:'nearest',behavior:'smooth'})},0)}))}
|
||||
function activeMeta(){return {sune_name:SUNE.name,model:SUNE.model,avatar:SUNE.avatar}}
|
||||
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 persistThread()},get PAT(){return globalStore.ghToken}}
|
||||
async function init(){await SUNE.fetchDotSune('sune-org/store@main/marketplace.sune');threads=await tload();await renderThreads();renderSidebar();await reflectActiveSune();clearChat();icons();kbBind();kbUpdate()}
|
||||
window.addEventListener('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';
|
||||
el.htmlTab_index.onclick=()=>showHtmlTab('index');el.htmlTab_extension.onclick=()=>showHtmlTab('extension');
|
||||
init()
|
||||
const HTTP_BASE='https://orp.awww.workers.dev/ws'
|
||||
const buildBody=()=>{const msgs=[];if(SUNE.masterPrompt)msgs.push({role:'system',content:[{type:'text',text:SUNE.masterPrompt}]});if(SUNE.system_prompt)msgs.push({role:'system',content:[{type:'text',text:SUNE.system_prompt}]});msgs.push(...state.messages.filter(m=>m.role!=='system').map(m=>({role:m.role,content:m.content})));const b=payloadWithSampling({model:SUNE.model.replace(/^(or:|oai:)/,''),messages:msgs,stream:true});if(SUNE.reasoning_effort&&SUNE.reasoning_effort!=='default')b.reasoning={effort:SUNE.reasoning_effort};if(SUNE.verbosity)b.verbosity=SUNE.verbosity;return b}
|
||||
async function askOpenRouterStreaming(onDelta,streamId){const model=SUNE.model,provider=model.startsWith('oai:')?'openai':model.startsWith('or:')?'openrouter':SUNE.provider,apiKey=provider==='openai'?SUNE.apiKeyOAI:SUNE.apiKeyOR;if(!apiKey){onDelta(localDemoReply(),true);return {ok:true,rid:streamId||null}}const r={rid:streamId||gid(),seq:-1,done:false,signaled:false,ws:null};await cacheStore.setItem(r.rid,'busy');const signal=t=>{if(!r.signaled){r.signaled=true;onDelta(t||'',true)}};const ws=new WebSocket(HTTP_BASE.replace('https','wss')+'?uid='+encodeURIComponent(r.rid));r.ws=ws;ws.onopen=()=>ws.send(JSON.stringify({type:'begin',rid:r.rid,provider,apiKey,or_body:buildBody()}));ws.onmessage=e=>{let m;try{m=JSON.parse(e.data)}catch{return}if(m.type==='delta'&&typeof m.seq==='number'&&m.seq>r.seq){r.seq=m.seq;onDelta(m.text||'',false)}else if(m.type==='done'||m.type==='err'){r.done=true;cacheStore.setItem(r.rid,'done');signal(m.type==='err'?'\n\n'+(m.message||'error'):'');ws.close()}};ws.onclose=()=>{};ws.onerror=()=>{};state.controller={abort:()=>{r.done=true;cacheStore.setItem(r.rid,'done');try{if(ws.readyState===1)ws.send(JSON.stringify({type:'stop',rid:r.rid}))}catch{};signal('')},disconnect:()=>ws.close()};return {ok:true,rid:r.rid}}
|
||||
const accountTabs={General:['accountTabGeneral','accountPanelGeneral'],API:['accountTabAPI','accountPanelAPI']};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=SUNE.provider||'openrouter';el.set_api_key_or.value=SUNE.apiKeyOR||'';el.set_api_key_oai.value=SUNE.apiKeyOAI||'';el.set_master_prompt.value=SUNE.masterPrompt||'';el.set_title_model.value=SUNE.titleModel;el.set_gh_token.value=globalStore.ghToken||'';showAccountTab('General');el.accountSettingsModal.classList.remove('hidden')}
|
||||
function closeAccountSettings(){el.accountSettingsModal.classList.add('hidden')}
|
||||
el.accountSettingsOption.addEventListener('click',()=>{el.userMenu.classList.add('hidden');openAccountSettings()})
|
||||
el.closeAccountSettings.addEventListener('click',closeAccountSettings)
|
||||
el.cancelAccountSettings.addEventListener('click',closeAccountSettings)
|
||||
el.accountSettingsModal.addEventListener('click',e=>{if(e.target===el.accountSettingsModal||e.target.classList.contains('bg-black/30'))closeAccountSettings()})
|
||||
el.accountSettingsForm.addEventListener('submit',e=>{e.preventDefault();SUNE.provider=el.set_provider.value||'openrouter';SUNE.apiKeyOR=String(el.set_api_key_or.value||'').trim();SUNE.apiKeyOAI=String(el.set_api_key_oai.value||'').trim();SUNE.masterPrompt=String(el.set_master_prompt.value||'').trim();SUNE.titleModel=String(el.set_title_model.value||'').trim();globalStore.ghToken=String(el.set_gh_token.value||'').trim();closeAccountSettings()})
|
||||
el.accountTabGeneral.onclick=()=>showAccountTab('General');el.accountTabAPI.onclick=()=>showAccountTab('API');
|
||||
el.exportAccountSettings.onclick=()=>dl(`sune-account-${ts()}.json`,{v:1,provider:globalStore.provider,apiKeyOR:globalStore.apiKeyOR,apiKeyOAI:globalStore.apiKeyOAI,masterPrompt:globalStore.masterPrompt,titleModel:globalStore.titleModel,ghToken:globalStore.ghToken});
|
||||
el.importAccountSettings.onclick=()=>{el.importAccountSettingsInput.value='';el.importAccountSettingsInput.click()};
|
||||
el.importAccountSettingsInput.onchange=async e=>{const f=e.target.files?.[0];if(!f)return;try{const d=JSON.parse(await f.text());if(!d||typeof d!=='object')throw new Error('Invalid');Object.entries({provider:'string',apiKeyOR:'string',apiKeyOAI:'string',masterPrompt:'string',titleModel:'string',ghToken:'string'}).forEach(([k,t])=>{if(typeof d[k]===t)k==='ghToken'?globalStore[k]=d[k]:SUNE[k]=d[k]});openAccountSettings();alert('Imported.')}catch{alert('Import failed')}};
|
||||
const lastAssistantId=()=>{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;const you=/^\s*You\b/.test(h.textContent||'');if(!you)return b.dataset.mid||null}return null}
|
||||
const getBubbleById=id=>el.messages.querySelector(`.msg-bubble[data-mid="${CSS.escape(id)}"]`)
|
||||
async function syncActiveThread(){const id=lastAssistantId();if(!id)return false;if(await cacheStore.getItem(id)==='done'){if(state.busy){setBtnSend();state.busy=false;state.controller=null}return false}if(!state.busy){state.busy=true;state.controller={abort:()=>{const ws=new WebSocket(HTTP_BASE.replace('https','wss'));ws.onopen=function(){this.send(JSON.stringify({type:'stop',rid:id}));this.close()}}};setBtnStop()}const bubble=getBubbleById(id);if(!bubble)return false;const prevText=bubble.textContent||'';const j=await(fetch(HTTP_BASE+'?uid='+encodeURIComponent(id)).then(r=>r.ok?r.json():null).catch(()=>null));const finalise=(t,c)=>{renderMarkdown(bubble,t,{enhance:false});enhanceCodeBlocks(bubble,true);const i=state.messages.findIndex(x=>x.id===id);if(i>=0)state.messages[i].content=c;else state.messages.push({id,role:'assistant',content:c,...activeMeta()});persistThread();setBtnSend();state.busy=false;cacheStore.setItem(id,'done');state.controller=null};if(!j||j.rid!==id){if(j&&j.error){const t=prevText+'\n\n'+j.error;finalise(t,[{type:'text',text:t}])}return false}const text=j.text||'',isDone=j.error||j.done||j.phase==='done';if(text)renderMarkdown(bubble,text,{enhance:false});if(isDone){const finalText=text||prevText;finalise(finalText,[{type:'text',text:finalText}]);return false}await cacheStore.setItem(id,'busy');return true}
|
||||
let syncLoopRunning=false
|
||||
async function syncWhileBusy(){if(syncLoopRunning||document.visibilityState==='hidden')return;syncLoopRunning=true;try{while(await syncActiveThread())await new Promise(r=>setTimeout(r,1200))}finally{syncLoopRunning=false}}
|
||||
const onForeground=()=>{if(document.visibilityState!=='visible')return;state.controller?.disconnect?.();if(state.busy)syncWhileBusy()}
|
||||
document.addEventListener('visibilitychange',onForeground)
|
||||
el.copySystemPrompt.addEventListener('click',async()=>{try{await navigator.clipboard.writeText(el.set_system_prompt.value||'')}catch{}})
|
||||
el.pasteSystemPrompt.addEventListener('click',async()=>{try{el.set_system_prompt.value=await navigator.clipboard.readText()}catch{}})
|
||||
const getActiveHtmlParts=()=>!el.htmlEditor.classList.contains('hidden')?[el.htmlEditor,jars.html]:[el.extensionHtmlEditor,jars.extension]
|
||||
el.copyHTML.addEventListener('click',async()=>{try{await navigator.clipboard.writeText(getActiveHtmlParts()[0].textContent||'')}catch{}})
|
||||
el.pasteHTML.addEventListener('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{}})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
2
dist/sw.js
vendored
2
dist/sw.js
vendored
@@ -1 +1 @@
|
||||
if(!self.define){let e,i={};const t=(t,n)=>(t=new URL(t+".js",n).href,i[t]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=t,e.onload=i,document.head.appendChild(e)}else e=t,importScripts(t),i()}).then(()=>{let e=i[t];if(!e)throw new Error(`Module ${t} didn’t register its module`);return e}));self.define=(n,r)=>{const s=e||("document"in self?document.currentScript.src:"")||location.href;if(i[s])return;let o={};const c=e=>t(e,s),d={module:{uri:s},exports:o,require:c};i[s]=Promise.all(n.map(e=>d[e]||c(e))).then(e=>(r(...e),o))}}define(["./workbox-5ffe50d4"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"index.html",revision:"462ced777b947ec3b50192c8ba4cf9a8"},{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"manifest.webmanifest",revision:"7a6c5c6ab9cb5d3605d21df44c6b17a2"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});
|
||||
if(!self.define){let e,i={};const t=(t,n)=>(t=new URL(t+".js",n).href,i[t]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=t,e.onload=i,document.head.appendChild(e)}else e=t,importScripts(t),i()}).then(()=>{let e=i[t];if(!e)throw new Error(`Module ${t} didn’t register its module`);return e}));self.define=(n,r)=>{const s=e||("document"in self?document.currentScript.src:"")||location.href;if(i[s])return;let o={};const d=e=>t(e,s),c={module:{uri:s},exports:o,require:d};i[s]=Promise.all(n.map(e=>c[e]||d(e))).then(e=>(r(...e),o))}}define(["./workbox-5ffe50d4"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"index.html",revision:"1dcda6498a84a8a882b32fe2299b8e4b"},{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"manifest.webmanifest",revision:"7a6c5c6ab9cb5d3605d21df44c6b17a2"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});
|
||||
|
||||
Reference in New Issue
Block a user