mirror of
https://github.com/multipleof4/sune.git
synced 2026-01-13 16:17:55 +00:00
Refactor: Improve account settings UI/UX
This commit is contained in:
35
index.html
35
index.html
@@ -127,23 +127,32 @@
|
||||
<form id="accountSettingsForm" class="text-sm">
|
||||
<div class="border-b flex text-xs font-medium"><button type="button" id="accountTabGeneral" class="flex-1 py-2 px-3 text-center border-b-2 border-black">General</button><button type="button" id="accountTabAPI" class="flex-1 py-2 px-3 text-center border-b-2 border-transparent hover:border-gray-300">API</button><button type="button" id="accountTabUser" class="flex-1 py-2 px-3 text-center border-b-2 border-transparent hover:border-gray-300">User</button></div>
|
||||
<div id="accountPanelGeneral" class="p-4 space-y-4">
|
||||
<div><label class="block text-gray-700 font-medium mb-1">Provider</label><select id="set_provider" class="w-full rounded-xl border border-gray-300 px-3 py-2"><option value="openrouter">OpenRouter</option><option value="openai">OpenAI</option><option value="google">Google</option><option value="cloudflare">Cloudflare</option></select><p class="mt-1 text-xs text-gray-500">Or you can prefix model names with or:, oai:, g:, or cf: to override.</p></div>
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="flex-1"><label class="block text-gray-700 font-medium mb-1">Provider</label><select id="set_provider" class="w-full rounded-xl border border-gray-300 px-3 py-2"><option value="openrouter">OpenRouter</option><option value="openai">OpenAI</option><option value="google">Google</option><option value="cloudflare">Cloudflare</option></select><p class="mt-1 text-xs text-gray-500">Or you can prefix model names with or:, oai:, g:, or cf: to override.</p></div>
|
||||
<div class="flex-shrink-0"><div class="relative"><img id="userAvatarPreview" class="h-16 w-16 rounded-full object-cover bg-gray-200"><button type="button" id="setUserAvatarBtn" class="absolute bottom-0 right-0 h-6 w-6 rounded-full bg-white border border-gray-300 flex items-center justify-center hover:bg-gray-100" aria-label="Edit photo"><i data-lucide="edit-3" class="h-3 w-3"></i></button></div><input id="userAvatarInput" type="file" accept="image/*" class="hidden"></div>
|
||||
</div>
|
||||
<div><label class="block text-gray-700 font-medium mb-1">Master Prompt</label><textarea id="set_master_prompt" rows="6" class="w-full rounded-xl border border-gray-300 px-3 py-2" placeholder="Applies to all sunes on this device"></textarea><p class="mt-1 text-xs text-gray-500">Stored locally.</p></div>
|
||||
<div><label class="block text-gray-700 font-medium mb-1">Model preference for titles</label><input id="set_title_model" type="text" class="w-full rounded-xl border border-gray-300 px-3 py-2" placeholder="or:google/gemma-3-12b-it"/><p class="mt-1 text-xs text-gray-500">Used for auto-generating thread titles.</p></div>
|
||||
</div>
|
||||
<div id="accountPanelAPI" class="p-4 hidden"><div class="grid grid-cols-2 gap-x-4 gap-y-4"><div><label class="block text-gray-700 font-medium mb-1">OpenRouter Key</label><div class="relative"><input id="set_api_key_or" type="password" class="w-full rounded-xl border border-gray-300 px-3 py-2 pr-10" placeholder="sk-or-..."><button type="button" data-reveal-for="set_api_key_or" class="absolute inset-y-0 right-0 px-3 flex items-center text-gray-400 hover:text-gray-600"><i data-lucide="eye" class="h-4 w-4"></i></button></div><p class="mt-1 text-xs text-gray-500">Use: <code>USER.apiKeyOpenRouter</code></p></div><div><label class="block text-gray-700 font-medium mb-1">OpenAI Key</label><div class="relative"><input id="set_api_key_oai" type="password" class="w-full rounded-xl border border-gray-300 px-3 py-2 pr-10" placeholder="sk-..."><button type="button" data-reveal-for="set_api_key_oai" class="absolute inset-y-0 right-0 px-3 flex items-center text-gray-400 hover:text-gray-600"><i data-lucide="eye" class="h-4 w-4"></i></button></div><p class="mt-1 text-xs text-gray-500">Use: <code>USER.apiKeyOpenAI</code></p></div><div><label class="block text-gray-700 font-medium mb-1">Google Key</label><div class="relative"><input id="set_api_key_g" type="password" class="w-full rounded-xl border border-gray-300 px-3 py-2 pr-10" placeholder="AIza..."><button type="button" data-reveal-for="set_api_key_g" class="absolute inset-y-0 right-0 px-3 flex items-center text-gray-400 hover:text-gray-600"><i data-lucide="eye" class="h-4 w-4"></i></button></div><p class="mt-1 text-xs text-gray-500">Gemini/Studio. Use: <code>USER.apiKeyGoogle</code></p></div><div><label class="block text-gray-700 font-medium mb-1">Cloudflare Token</label><div class="relative"><input id="set_api_key_cf" type="password" class="w-full rounded-xl border border-gray-300 px-3 py-2 pr-10" placeholder="..."><button type="button" data-reveal-for="set_api_key_cf" class="absolute inset-y-0 right-0 px-3 flex items-center text-gray-400 hover:text-gray-600"><i data-lucide="eye" class="h-4 w-4"></i></button></div><p class="mt-1 text-xs text-gray-500">Not used. Use: <code>USER.apiKeyCloudflare</code></p></div><div><label class="block text-gray-700 font-medium mb-1">Github Token</label><div class="relative"><input id="set_gh_token" type="password" class="w-full rounded-xl border border-gray-300 px-3 py-2 pr-10" placeholder="ghp_..."><button type="button" data-reveal-for="set_gh_token" class="absolute inset-y-0 right-0 px-3 flex items-center text-gray-400 hover:text-gray-600"><i data-lucide="eye" class="h-4 w-4"></i></button></div><p class="mt-1 text-xs text-gray-500">Use: <code>USER.githubToken</code></p></div><div><label class="block text-gray-700 font-medium mb-1">GCP Service Acct</label><input id="gcpSAInput" type="file" class="hidden" accept="application/json,.json"><button type="button" id="gcpSAUploadBtn" class="w-full text-left rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm hover:bg-gray-50 truncate">Upload .json</button><p class="mt-1 text-xs text-gray-500">Use: <code>USER.gcpSA</code></p></div></div></div>
|
||||
<div id="accountPanelUser" class="p-4 space-y-4 hidden">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="relative"><img id="userAvatarPreview" class="h-16 w-16 rounded-full object-cover bg-gray-200"><button type="button" id="setUserAvatarBtn" class="absolute bottom-0 right-0 h-6 w-6 rounded-full bg-white border border-gray-300 flex items-center justify-center hover:bg-gray-100" aria-label="Edit photo"><i data-lucide="edit-3" class="h-3 w-3"></i></button></div>
|
||||
<input id="userAvatarInput" type="file" accept="image/*" class="hidden">
|
||||
<div class="flex-1"><label for="set_user_name" class="block text-gray-700 font-medium mb-1">Username</label><input id="set_user_name" type="text" class="w-full rounded-xl border border-gray-300 px-3 py-2" placeholder="Master"/></div>
|
||||
</div>
|
||||
<div id="passwordInputContainer"><label class="block text-gray-700 font-medium mb-1">Password</label><input id="set_user_password" type="password" class="w-full rounded-xl border border-gray-300 px-3 py-2"/></div>
|
||||
<div><label for="set_user_name" class="block text-gray-700 font-medium mb-1">Username</label><input id="set_user_name" type="text" class="w-full rounded-xl border border-gray-300 px-3 py-2" placeholder="Master"/></div>
|
||||
<div id="loginStatus" class="hidden items-center justify-between pt-2">
|
||||
<div class="flex items-center gap-2 text-sm text-green-700"><span class="h-2 w-2 rounded-full bg-green-500 animate-pulse"></span><span>Logged in</span></div>
|
||||
<button type="button" id="logoutBtn" class="px-3 py-2 rounded-xl border bg-white hover:bg-gray-50 text-sm">Logout</button>
|
||||
</div>
|
||||
<div id="authButtons" class="flex items-center justify-end gap-2 pt-2"><button type="button" id="loginBtn" class="px-3 py-2 rounded-xl border bg-white hover:bg-gray-50">Login</button><button type="button" id="signupBtn" class="px-3 py-2 rounded-xl bg-black text-white hover:bg-black/90">Sign-up</button></div>
|
||||
<div id="userAuthContainer">
|
||||
<div class="border-b flex text-xs font-medium"><button type="button" id="userSubTabLogin" class="flex-1 py-2 px-3 text-center border-b-2">Login</button><button type="button" id="userSubTabSignup" class="flex-1 py-2 px-3 text-center border-b-2">Sign-up</button></div>
|
||||
<div id="userPanelLogin" class="pt-4 space-y-4">
|
||||
<div><label class="block text-gray-700 font-medium mb-1">Password</label><input id="set_user_password_login" type="password" class="w-full rounded-xl border border-gray-300 px-3 py-2"/></div>
|
||||
<div class="flex items-center justify-end"><button type="button" id="loginBtn" class="px-3 py-2 rounded-xl border bg-white hover:bg-gray-50">Login</button></div>
|
||||
</div>
|
||||
<div id="userPanelSignup" class="pt-4 space-y-4 hidden">
|
||||
<div><label class="block text-gray-700 font-medium mb-1">Password</label><input id="set_user_password_signup" type="password" class="w-full rounded-xl border border-gray-300 px-3 py-2"/></div>
|
||||
<div><label class="block text-gray-700 font-medium mb-1">Confirm Password</label><input id="set_user_password_confirm" type="password" class="w-full rounded-xl border border-gray-300 px-3 py-2"/></div>
|
||||
<div class="flex items-center justify-end"><button type="button" id="signupBtn" class="px-3 py-2 rounded-xl bg-black text-white hover:bg-black/90">Sign-up</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-2 px-4 py-3 border-t">
|
||||
<div class="flex items-center gap-2"><button type="button" id="importAccountSettings" class="text-xs px-2.5 py-1.5 rounded-lg border bg-white hover:bg-gray-50">Import</button><button type="button" id="exportAccountSettings" class="text-xs px-2.5 py-1.5 rounded-lg border bg-white hover:bg-gray-50">Export</button></div>
|
||||
@@ -161,7 +170,7 @@
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded',()=>{
|
||||
const DEFAULT_MODEL='google/gemini-2.5-pro',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_repetition_penalty','set_min_p','set_top_a','set_verbosity','set_reasoning_effort','set_system_prompt','set_hide_composer','set_include_thoughts','set_json_output','set_ignore_master_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','jsonSchemaEditor','htmlTab_index','htmlTab_extension','suneHtml','accountSettingsModal','accountSettingsForm','closeAccountSettings','cancelAccountSettings','set_master_prompt','set_provider','set_api_key_or','set_api_key_oai','set_api_key_g','set_api_key_cf','set_title_model','copySystemPrompt','pasteSystemPrompt','copyHTML','pasteHTML','accountTabGeneral','accountTabAPI','accountPanelGeneral','accountPanelAPI','set_gh_token','gcpSAInput','gcpSAUploadBtn','importAccountSettings','exportAccountSettings','importAccountSettingsInput','accountTabUser','accountPanelUser','set_user_name','userAvatarPreview','setUserAvatarBtn','userAvatarInput','set_user_password','loginBtn','signupBtn','passwordInputContainer','loginStatus','logoutBtn','authButtons'].map(id=>[id,$('#'+id)[0]]))
|
||||
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_repetition_penalty','set_min_p','set_top_a','set_verbosity','set_reasoning_effort','set_system_prompt','set_hide_composer','set_include_thoughts','set_json_output','set_ignore_master_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','jsonSchemaEditor','htmlTab_index','htmlTab_extension','suneHtml','accountSettingsModal','accountSettingsForm','closeAccountSettings','cancelAccountSettings','set_master_prompt','set_provider','set_api_key_or','set_api_key_oai','set_api_key_g','set_api_key_cf','set_title_model','copySystemPrompt','pasteSystemPrompt','copyHTML','pasteHTML','accountTabGeneral','accountTabAPI','accountPanelGeneral','accountPanelAPI','set_gh_token','gcpSAInput','gcpSAUploadBtn','importAccountSettings','exportAccountSettings','importAccountSettingsInput','accountTabUser','accountPanelUser','set_user_name','userAvatarPreview','setUserAvatarBtn','userAvatarInput','loginStatus','logoutBtn','userAuthContainer','userSubTabLogin','userSubTabSignup','userPanelLogin','userPanelSignup','set_user_password_login','set_user_password_signup','set_user_password_confirm','loginBtn','signupBtn'].map(id=>[id,$('#'+id)[0]]))
|
||||
let adminConfig={};
|
||||
const icons=()=>window.lucide&&lucide.createIcons()
|
||||
const haptic=()=>/android/i.test(navigator.userAgent)&&navigator.vibrate?.(1)
|
||||
@@ -263,7 +272,8 @@ const HTTP_BASE='https://orp.aww.4ev.link/ws'
|
||||
const buildBody=()=>{const msgs=[];if(USER.masterPrompt&&!SUNE.ignore_master_prompt)msgs.push({role:'system',content:[{type:'text',text:USER.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:|g:|cf:)/,''),messages:msgs,stream:true});if(SUNE.json_output){let s;try{s=JSON.parse(SUNE.json_schema||'null')}catch{s=null}if(s&&typeof s==='object'&&Object.keys(s).length>0){b.response_format={type:'json_schema',json_schema:s}}else{b.response_format={type:'json_object'}}}b.reasoning={...(SUNE.reasoning_effort&&SUNE.reasoning_effort!=='default'?{effort:SUNE.reasoning_effort}:{}),exclude:!SUNE.include_thoughts};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('g:')?'google':model.startsWith('cf:')?'cloudflare':model.startsWith('or:')?'openrouter':USER.provider,apiKey=provider==='openai'?USER.apiKeyOpenAI:provider==='google'?USER.apiKeyGoogle:provider==='cloudflare'?USER.apiKeyCloudflare:USER.apiKeyOpenRouter;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'],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_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);const loggedIn=!!USER.passHash;el.passwordInputContainer.classList.toggle('hidden',loggedIn);el.authButtons.classList.toggle('hidden',loggedIn);el.loginStatus.classList.toggle('hidden',!loggedIn);showAccountTab(Object.keys(accountTabs).find(k=>!el[accountTabs[k][0]].classList.contains('hidden'))||'General');el.accountSettingsModal.classList.remove('hidden')}
|
||||
const userSubTabs={Login:['userSubTabLogin','userPanelLogin'],Signup:['userSubTabSignup','userPanelSignup']};function showUserSubTab(key){Object.entries(userSubTabs).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)})}
|
||||
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_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);const loggedIn=!!USER.passHash;el.userAuthContainer.classList.toggle('hidden',loggedIn);el.loginStatus.classList.toggle('hidden',!loggedIn);if(!loggedIn)showUserSubTab('Login');showAccountTab(Object.keys(accountTabs).find(k=>!el[accountTabs[k][0]].classList.contains('hidden'))||'General');el.accountSettingsModal.classList.remove('hidden')}
|
||||
function closeAccountSettings(){el.accountSettingsModal.classList.add('hidden')}
|
||||
$(el.accountSettingsOption).on('click',()=>{el.userMenu.classList.add('hidden');openAccountSettings()})
|
||||
$(el.closeAccountSettings).on('click',closeAccountSettings)
|
||||
@@ -272,12 +282,13 @@ $(el.accountSettingsModal).on('click',e=>{if(e.target===el.accountSettingsModal|
|
||||
$(el.accountSettingsForm).on('submit',e=>{e.preventDefault();USER.provider=el.set_provider.value||'openrouter';USER.apiKeyOpenRouter=String(el.set_api_key_or.value||'').trim();USER.apiKeyOpenAI=String(el.set_api_key_oai.value||'').trim();USER.apiKeyGoogle=String(el.set_api_key_g.value||'').trim();USER.apiKeyCloudflare=String(el.set_api_key_cf.value||'').trim();USER.masterPrompt=String(el.set_master_prompt.value||'').trim();USER.titleModel=String(el.set_title_model.value||'').trim();USER.githubToken=String(el.set_gh_token.value||'').trim();USER.name=String(el.set_user_name.value||'').trim();closeAccountSettings()})
|
||||
el.gcpSAUploadBtn.onclick=()=>el.gcpSAInput.click();el.gcpSAInput.onchange=async e=>{const f=e.target.files?.[0];if(!f)return;try{const t=await f.text(),d=JSON.parse(t);if(!d.project_id)throw new Error('Invalid');USER.gcpSA=d;el.gcpSAUploadBtn.textContent=`Uploaded: ${d.project_id}`;alert('GCP SA loaded.')}catch{alert('Failed to load GCP SA.')}};$(el.accountPanelAPI).on('click',e=>{const b=e.target.closest('[data-reveal-for]');if(!b)return;const i=document.getElementById(b.dataset.revealFor);if(!i)return;const p=i.type==='password';i.type=p?'text':'password';b.querySelector('i').setAttribute('data-lucide',p?'eye-off':'eye');lucide.createIcons()});
|
||||
el.accountTabGeneral.onclick=()=>showAccountTab('General');el.accountTabAPI.onclick=()=>showAccountTab('API');el.accountTabUser.onclick=()=>showAccountTab('User');
|
||||
el.userSubTabLogin.onclick=()=>showUserSubTab('Login');el.userSubTabSignup.onclick=()=>showUserSubTab('Signup');
|
||||
el.setUserAvatarBtn.onclick=()=>el.userAvatarInput.click();el.userAvatarInput.onchange=async e=>{const f=e.target.files?.[0];if(!f)return;try{const dataUrl=await imgToWebp(f);USER.avatar=dataUrl;el.userAvatarPreview.src=dataUrl;el.userAvatarPreview.classList.remove('bg-gray-200')}catch{alert('Failed to process image.')}}
|
||||
el.exportAccountSettings.onclick=()=>dl(`sune-account-${ts()}.json`,{v:1,provider:USER.provider,apiKeyOpenRouter:USER.apiKeyOpenRouter,apiKeyOpenAI:USER.apiKeyOpenAI,apiKeyGoogle:USER.apiKeyGoogle,apiKeyCloudflare:USER.apiKeyCloudflare,masterPrompt:USER.masterPrompt,titleModel:USER.titleModel,githubToken:USER.githubToken,gcpSA:USER.gcpSA,userName:USER.name,userAvatar:USER.avatar,passHash:USER.passHash});
|
||||
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');const m={provider:'provider',apiKeyOpenRouter:'apiKeyOR',apiKeyOpenAI:'apiKeyOAI',apiKeyGoogle:'apiKeyG',apiKeyCloudflare:'apiKeyCF',masterPrompt:'masterPrompt',titleModel:'titleModel',githubToken:'ghToken',name:'userName',avatar:'userAvatar',gcpSA:'gcpSA',passHash:'passHash'};Object.entries(m).forEach(([p,k])=>{const v=d[p]??d[k];if(typeof v==='string'||(p==='gcpSA'&&typeof v==='object'&&v))USER[p]=v});openAccountSettings();alert('Imported.')}catch{alert('Import failed')}};
|
||||
const hash=s=>crypto.subtle.digest('SHA-256',new TextEncoder().encode(s)).then(b=>Array.from(new Uint8Array(b)).map(x=>x.toString(16).padStart(2,'0')).join(''));
|
||||
const authAction=async endpoint=>{const u=(el.set_user_name.value||'').trim(),p=el.set_user_password.value;if(!u||!p)return alert('Username and password required.');const spinner='<i data-lucide="loader-2" class="h-5 w-5 animate-spin"></i>';[el.loginBtn,el.signupBtn].forEach(b=>{b.dataset.og=b.innerHTML;b.innerHTML=spinner;b.disabled=true});icons();el.set_user_password.value='';try{const r=await fetch(`https://spa.awww.workers.dev/${endpoint}`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:u,password_hash:await hash(p)})}),t=await r.text();alert(`${endpoint[0].toUpperCase()+endpoint.slice(1)}: ${r.status} ${t}`);if(r.ok){USER.name=u;if(endpoint==='login')USER.passHash=await hash(p);closeAccountSettings()}}catch(e){alert(`Error: ${e.message}`)}finally{[el.loginBtn,el.signupBtn].forEach(b=>{b.innerHTML=b.dataset.og;b.disabled=false});icons()}};
|
||||
const authAction=async endpoint=>{const u=(el.set_user_name.value||'').trim();let p;if(endpoint==='signup'){p=el.set_user_password_signup.value;if(p!==el.set_user_password_confirm.value)return alert('Passwords do not match.')}else{p=el.set_user_password_login.value}if(!u||!p)return alert('Username and password required.');const spinner='<i data-lucide="loader-2" class="h-5 w-5 animate-spin"></i>';[el.loginBtn,el.signupBtn].forEach(b=>{b.dataset.og=b.innerHTML;b.innerHTML=spinner;b.disabled=true});icons();[el.set_user_password_login,el.set_user_password_signup,el.set_user_password_confirm].forEach(i=>i.value='');try{const r=await fetch(`https://spa.awww.workers.dev/${endpoint}`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:u,password_hash:await hash(p)})}),t=await r.text();alert(`${endpoint[0].toUpperCase()+endpoint.slice(1)}: ${r.status} ${t}`);if(r.ok){USER.name=u;USER.passHash=await hash(p);closeAccountSettings()}}catch(e){alert(`Error: ${e.message}`)}finally{[el.loginBtn,el.signupBtn].forEach(b=>{b.innerHTML=b.dataset.og;b.disabled=false});icons()}};
|
||||
el.loginBtn.onclick=()=>authAction('login');el.signupBtn.onclick=()=>authAction('signup');
|
||||
el.logoutBtn.onclick=()=>{USER.passHash='';openAccountSettings()};
|
||||
const getBubbleById=id=>el.messages.querySelector(`.msg-bubble[data-mid="${CSS.escape(id)}"]`)
|
||||
@@ -291,7 +302,7 @@ $(el.pasteSystemPrompt).on('click',async()=>{try{el.set_system_prompt.value=awai
|
||||
const getActiveHtmlParts=()=>!el.htmlEditor.classList.contains('hidden')?[el.htmlEditor,jars.html]:[el.extensionHtmlEditor,jars.extension]
|
||||
$(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,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,buildBody,askOpenRouterStreaming,showAccountTab,openAccountSettings,closeAccountSettings,getBubbleById,syncActiveThread,syncWhileBusy,onForeground,getActiveHtmlParts,imgToWebp});
|
||||
Object.assign(window,{icons,haptic,clamp,num,int,gid,esc,positionPopover,sid,fmtSize,asDataURL,b64,makeSune,getModelShort,resolveSuneSrc,processSuneIncludes,renderSuneHTML,reflectActiveSune,suneRow,enhanceCodeBlocks,getSuneLabel,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,buildBody,askOpenRouterStreaming,showAccountTab,openAccountSettings,closeAccountSettings,getBubbleById,syncActiveThread,syncWhileBusy,onForeground,getActiveHtmlParts,imgToWebp,showUserSubTab});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user