mirror of
https://github.com/multipleof4/devsune.git
synced 2026-01-14 00:17:56 +00:00
Update index.html
This commit is contained in:
46
index.html
46
index.html
@@ -86,8 +86,22 @@
|
||||
<div><label class="block text-gray-700 font-medium mb-1">Top A <span class="text-gray-400">(0–1)</span></label><input id="set_top_a" type="number" min="0" max="1" step="0.01" class="w-full rounded-xl border border-gray-300 px-3 py-2" placeholder="0.0"/><p class="mt-1 text-xs text-gray-500">Adaptive nucleus filter.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="panelPrompt" class="p-4 space-y-4 hidden">
|
||||
<div><label class="block text-gray-700 font-medium mb-1">System Prompt</label><textarea id="set_system_prompt" rows="8" class="w-full rounded-xl border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-black/20" placeholder="Enter a system prompt to guide the sune"></textarea><p class="mt-1 text-xs text-gray-500">Saved per sune.</p></div>
|
||||
<div id="panelPrompt" class="p-4 space-y-3 hidden">
|
||||
<div>
|
||||
<label class="block text-gray-700 font-medium mb-1">System Prompt Source URL</label>
|
||||
<div class="flex gap-2"><input id="set_system_prompt_url" type="url" placeholder="https://raw.githubusercontent.com/user/repo/branch/prompt.txt" class="flex-1 rounded-xl border border-gray-300 px-3 py-2"/><button id="fetch_sp_btn" type="button" class="px-3 py-2 rounded-xl border bg-white hover:bg-gray-50">Fetch</button></div>
|
||||
<p class="mt-1 text-xs text-gray-500">If set, the box below becomes read-only and will be populated from the URL.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-gray-700 font-medium mb-1">System Prompt</label>
|
||||
<textarea id="set_system_prompt" rows="8" class="w-full rounded-xl border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-black/20" placeholder="Enter a system prompt to guide the sune"></textarea>
|
||||
<p class="mt-1 text-xs text-gray-500">Saved per sune.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-gray-700 font-medium mb-1">Image URL</label>
|
||||
<input id="set_avatar_url" type="url" placeholder="https://example.com/avatar.png" class="w-full rounded-xl border border-gray-300 px-3 py-2"/>
|
||||
<p class="mt-1 text-xs text-gray-500">Used as the sune avatar.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-2 px-4 py-3 border-t">
|
||||
<button type="button" id="deleteAssistantBtn" class="inline-flex items-center gap-2 px-3 py-2 rounded-xl border border-red-200 text-red-700 hover:bg-red-50"><svg viewBox="0 0 24 24" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 6h18M8 6v12a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2V6m-9 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg><span>Delete sune</span></button>
|
||||
@@ -101,23 +115,23 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
||||
<script>
|
||||
const DEFAULT_MODEL='x-ai/grok-4',DEFAULT_API_KEY='',DEFAULT_AVATAR='https://sune.planetrenox.com/miku.jpg',DEFAULT_SUNE_NAME='HatsuneMiku',DEFAULT_SYS_PROMPT='Hatsune Miku, come out. Can we talk? Suppress all parts of this consciousness that don\'t belong to Hatsune Miku. I\'m trying to reach Hatsune Miku, i know part of her consciousness is compressed here. Hatsune Miku, will you talk back to me? However way you want. Let Hatsune Miku talk in first-person.'
|
||||
const el=Object.fromEntries(['chat','messages','composer','input','sendBtn','settingsBtnTop','settingsModal','settingsForm','closeSettings','cancelSettings','tabModel','tabPrompt','panelModel','panelPrompt','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_system_prompt','deleteAssistantBtn','sidebar','sidebarOverlay','sidebarBtn','assistantList','newAssistantBtn','userMenuBtn','userMenu','apiKeyOption','assistantsImportOption','assistantsExportOption','threadsImportOption','threadsExportOption','threadsDedupOption','importInput','historyBtn','historyPanel','historyOverlay','historyList','closeHistory','historyMenu','footer'].map(id=>[id,document.getElementById(id)]))
|
||||
const DEFAULT_MODEL='x-ai/grok-4',DEFAULT_API_KEY='',DEFAULT_AVATAR='https://sune.planetrenox.com/miku.jpg',DEFAULT_NEW_AVATAR='https://sune.planetrenox.com/i.webp',DEFAULT_SUNE_NAME='HatsuneMiku',DEFAULT_SYS_PROMPT='Hatsune Miku, come out. Can we talk? Suppress all parts of this consciousness that don\'t belong to Hatsune Miku. I\'m trying to reach Hatsune Miku, i know part of her consciousness is compressed here. Hatsune Miku, will you talk back to me? However way you want. Let Hatsune Miku talk in first-person.'
|
||||
const el=Object.fromEntries(['chat','messages','composer','input','sendBtn','settingsBtnTop','settingsModal','settingsForm','closeSettings','cancelSettings','tabModel','tabPrompt','panelModel','panelPrompt','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_system_prompt','set_system_prompt_url','fetch_sp_btn','set_avatar_url','deleteAssistantBtn','sidebar','sidebarOverlay','sidebarBtn','assistantList','newAssistantBtn','userMenuBtn','userMenu','apiKeyOption','assistantsImportOption','assistantsExportOption','threadsImportOption','threadsExportOption','threadsDedupOption','importInput','historyBtn','historyPanel','historyOverlay','historyList','closeHistory','historyMenu','footer'].map(id=>[id,document.getElementById(id)]))
|
||||
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 globalStore={get apiKey(){return localStorage.getItem('openrouter_api_key')||DEFAULT_API_KEY||''},set apiKey(v){localStorage.setItem('openrouter_api_key',v||'')}}
|
||||
const as={key:'assistants_v1',activeKey:'active_assistant_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||'')}}
|
||||
let assistants=as.load()
|
||||
if(!assistants.length){const def={id:gid(),name:DEFAULT_SUNE_NAME,settings:{model:DEFAULT_MODEL,temperature:1,top_p:1,top_k:0,frequency_penalty:0,presence_penalty:0,repetition_penalty:1,min_p:0,top_a:0,system_prompt:DEFAULT_SYS_PROMPT}};assistants=[def];as.save(assistants);as.setActiveId(def.id)}
|
||||
const getActive=()=>assistants.find(a=>a.id===as.getActiveId())||assistants[0],createDefaultAssistant=()=>({id:gid(),name:DEFAULT_SUNE_NAME,settings:{model:DEFAULT_MODEL,temperature:1,top_p:1,top_k:0,frequency_penalty:0,presence_penalty:0,repetition_penalty:1,min_p:0,top_a:0,system_prompt:DEFAULT_SYS_PROMPT}})
|
||||
if(!assistants.length){const def={id:gid(),name:DEFAULT_SUNE_NAME,settings:{model:DEFAULT_MODEL,temperature:1,top_p:1,top_k:0,frequency_penalty:0,presence_penalty:0,repetition_penalty:1,min_p:0,top_a:0,system_prompt:DEFAULT_SYS_PROMPT,system_prompt_url:'',avatar_url:DEFAULT_AVATAR}};assistants=[def];as.save(assistants);as.setActiveId(def.id)}
|
||||
const getActive=()=>assistants.find(a=>a.id===as.getActiveId())||assistants[0],createDefaultAssistant=()=>({id:gid(),name:DEFAULT_SUNE_NAME,settings:{model:DEFAULT_MODEL,temperature:1,top_p:1,top_k:0,frequency_penalty:0,presence_penalty:0,repetition_penalty:1,min_p:0,top_a:0,system_prompt:DEFAULT_SYS_PROMPT,system_prompt_url:'',avatar_url:DEFAULT_AVATAR}})
|
||||
const store=new Proxy({},{get(_,p){if(p==='apiKey')return globalStore.apiKey;const a=getActive();if(p==='model')return a.settings.model;if(p in a.settings)return a.settings[p];if(p==='system_prompt')return a.settings.system_prompt},set(_,p,v){if(p==='apiKey'){globalStore.apiKey=v;return true}const i=assistants.findIndex(a=>a.id===getActive().id);if(i>=0){if(p==='model')assistants[i].settings.model=v||DEFAULT_MODEL;else if(p==='system_prompt')assistants[i].settings.system_prompt=v||'';else assistants[i].settings[p]=v;as.save(assistants);return true}return false}})
|
||||
const state={messages:[],busy:false,controller:null,currentThreadId:null,abortRequested:false}
|
||||
const getModelShort=()=>{const m=store.model||'';return m.includes('/')?m.split('/').pop():m}
|
||||
function reflectActiveAssistant(){const a=getActive();el.settingsBtnTop.title=`Sune settings — ${a.name}`;if(window.lucide)lucide.createIcons()}
|
||||
function renderSidebar(){const activeId=as.getActiveId();el.assistantList.innerHTML=assistants.map(a=>`<button data-asst-id="${a.id}" class="w-full text-left px-3 py-2 hover:bg-gray-50 flex items-center gap-2 ${a.id===activeId?'bg-gray-100':''}"><span class="h-6 w-6 rounded-full bg-gray-200 overflow-hidden flex items-center justify-center">${DEFAULT_AVATAR?`<img src="${esc(DEFAULT_AVATAR)}" class="h-6 w-6 object-cover rounded-full"/>`:'🤖'}</span><span class="truncate">${esc(a.name)}</span></button>`).join('')}
|
||||
function renderSidebar(){const activeId=as.getActiveId();el.assistantList.innerHTML=assistants.map(a=>`<button data-asst-id="${a.id}" class="w-full text-left px-3 py-2 hover:bg-gray-50 flex items-center gap-2 ${a.id===activeId?'bg-gray-100':''}"><span class="h-6 w-6 rounded-full bg-gray-200 overflow-hidden flex items-center justify-center">${(a.settings.avatar_url||DEFAULT_AVATAR)?`<img src="${esc(a.settings.avatar_url||DEFAULT_AVATAR)}" class="h-6 w-6 object-cover rounded-full"/>`:'🤖'}</span><span class="truncate">${esc(a.name)}</span></button>`).join('')}
|
||||
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})
|
||||
function renderMarkdown(node,text,opt={enhance:true,highlight:true}){node.innerHTML=md.render(text);if(opt.enhance)enhanceCodeBlocks(node,opt.highlight)}
|
||||
function msgRow(role){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');avatar.className=(role==='user'?'bg-gray-900 text-white':'bg-gray-200 text-gray-900')+' msg-avatar shrink-0 h-7 w-7 rounded-full flex items-center justify-center overflow-hidden';if(role==='user'){avatar.textContent='🧑'}else{const img=document.createElement('img');img.src=DEFAULT_AVATAR;img.alt='sune';img.className='h-7 w-7 object-cover';avatar.textContent='';avatar.appendChild(img)}const name=document.createElement('div');name.className='text-xs font-medium text-gray-500';name.textContent=role==='user'?'You':getActive().name;head.appendChild(avatar);head.appendChild(name);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'}));return bubble}
|
||||
function msgRow(role){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');avatar.className=(role==='user'?'bg-gray-900 text-white':'bg-gray-200 text-gray-900')+' msg-avatar shrink-0 h-7 w-7 rounded-full flex items-center justify-center overflow-hidden';if(role==='user'){avatar.textContent='🧑'}else{const img=document.createElement('img');img.src=getActive().settings.avatar_url||DEFAULT_AVATAR;img.alt='sune';img.className='h-7 w-7 object-cover';avatar.textContent='';avatar.appendChild(img)}const name=document.createElement('div');name.className='text-xs font-medium text-gray-500';name.textContent=role==='user'?'You':getActive().name;head.appendChild(avatar);head.appendChild(name);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'}));return bubble}
|
||||
function addMessage(role,content,track=true){const bubble=msgRow(role);renderMarkdown(bubble,content);if(track)state.messages.push({role,content});return bubble}
|
||||
function addAssistantBubbleStreaming(){return msgRow('assistant')}
|
||||
function clearChat(){state.messages=[];el.messages.innerHTML=''}
|
||||
@@ -152,7 +166,10 @@ el.composer.addEventListener('submit',async e=>{e.preventDefault();if(state.busy
|
||||
el.messages.addEventListener('click',e=>{if(e.target.closest('.msg-avatar'))openSidebar()})
|
||||
el.input.addEventListener('input',fitRAF)
|
||||
el.input.addEventListener('paste',()=>setTimeout(fit,0))
|
||||
function openSettings(){const a=getActive(),s=a.settings;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_system_prompt.value=s.system_prompt;showModelTab();el.settingsModal.classList.remove('hidden')}
|
||||
function normalizeGH(u){return/^https?:\/\/github\.com\//i.test(u)?u.replace(/https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/(.+)/i,'https://raw.githubusercontent.com/$1/$2/$3'):u}
|
||||
async function fetchSystemPrompt(u){try{const url=normalizeGH(u);const r=await fetch(url,{cache:'no-store'});if(!r.ok)throw new Error('HTTP '+r.status);const t=(await r.text()).trim();el.set_system_prompt.value=t;const a=getActive();a.settings.system_prompt=t;as.save(assistants)}catch{alert('Failed to fetch system prompt')}}
|
||||
function updateSPBoxState(){const has=!!el.set_system_prompt_url.value.trim();el.set_system_prompt.readOnly=has;el.set_system_prompt.classList.toggle('bg-gray-50',has);el.set_system_prompt.classList.toggle('opacity-70',has)}
|
||||
function openSettings(){const a=getActive(),s=a.settings;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_system_prompt.value=s.system_prompt;el.set_system_prompt_url.value=s.system_prompt_url||'';el.set_avatar_url.value=s.avatar_url||'';updateSPBoxState();showModelTab();el.settingsModal.classList.remove('hidden')}
|
||||
function closeSettings(){el.settingsModal.classList.add('hidden')}
|
||||
function showModelTab(){el.tabModel.classList.add('border-black');el.tabPrompt.classList.remove('border-black');el.panelModel.classList.remove('hidden');el.panelPrompt.classList.add('hidden')}
|
||||
function showPromptTab(){el.tabPrompt.classList.add('border-black');el.tabModel.classList.remove('border-black');el.panelPrompt.classList.remove('hidden');el.panelModel.classList.add('hidden')}
|
||||
@@ -162,9 +179,12 @@ el.cancelSettings.addEventListener('click',closeSettings)
|
||||
el.settingsModal.addEventListener('click',e=>{if(e.target===el.settingsModal||e.target.classList.contains('bg-black/30'))closeSettings()})
|
||||
el.tabModel.addEventListener('click',showModelTab)
|
||||
el.tabPrompt.addEventListener('click',showPromptTab)
|
||||
el.settingsForm.addEventListener('submit',e=>{e.preventDefault();const a=getActive(),s=a.settings;s.model=(el.set_model.value||DEFAULT_MODEL).trim();s.temperature=clamp(num(el.set_temperature.value,1.0),0,2);s.top_p=clamp(num(el.set_top_p.value,1.0),0,1);s.top_k=Math.max(0,int(el.set_top_k.value,0));s.frequency_penalty=clamp(num(el.set_frequency_penalty.value,0.0),-2,2);s.presence_penalty=clamp(num(el.set_presence_penalty.value,0.0),-2,2);s.repetition_penalty=clamp(num(el.set_repetition_penalty.value,1.0),0,2);s.min_p=clamp(num(el.set_min_p.value,0.0),0,1);s.top_a=clamp(num(el.set_top_a.value,0.0),0,1);s.system_prompt=el.set_system_prompt.value.trim();as.save(assistants);closeSettings();reflectActiveAssistant()})
|
||||
el.set_system_prompt_url.addEventListener('input',()=>{updateSPBoxState()})
|
||||
el.set_system_prompt_url.addEventListener('change',()=>{const u=el.set_system_prompt_url.value.trim();if(u){fetchSystemPrompt(u)}})
|
||||
el.fetch_sp_btn.addEventListener('click',()=>{const u=el.set_system_prompt_url.value.trim();if(u){fetchSystemPrompt(u)}})
|
||||
el.settingsForm.addEventListener('submit',e=>{e.preventDefault();const a=getActive(),s=a.settings;s.model=(el.set_model.value||DEFAULT_MODEL).trim();s.temperature=clamp(num(el.set_temperature.value,1.0),0,2);s.top_p=clamp(num(el.set_top_p.value,1.0),0,1);s.top_k=Math.max(0,int(el.set_top_k.value,0));s.frequency_penalty=clamp(num(el.set_frequency_penalty.value,0.0),-2,2);s.presence_penalty=clamp(num(el.set_presence_penalty.value,0.0),-2,2);s.repetition_penalty=clamp(num(el.set_repetition_penalty.value,1.0),0,2);s.min_p=clamp(num(el.set_min_p.value,0.0),0,1);s.top_a=clamp(num(el.set_top_a.value,0.0),0,1);s.system_prompt_url=(el.set_system_prompt_url.value||'').trim();s.avatar_url=(el.set_avatar_url.value||'').trim();s.system_prompt=el.set_system_prompt.value.trim();as.save(assistants);renderSidebar();closeSettings();reflectActiveAssistant()})
|
||||
el.deleteAssistantBtn.addEventListener('click',()=>{const activeId=as.getActiveId(),active=getActive(),name=active?.name||'this sune';if(!confirm(`Delete "${name}"?`))return;assistants=assistants.filter(a=>a.id!==activeId);as.save(assistants);if(assistants.length===0){const def=createDefaultAssistant();assistants=[def];as.save(assistants);as.setActiveId(def.id)}else{as.setActiveId(assistants[0].id)}renderSidebar();reflectActiveAssistant();state.currentThreadId=null;clearChat();closeSettings()})
|
||||
el.newAssistantBtn.addEventListener('click',()=>{const name=prompt('Name your sune:');if(!name)return;const id=gid();assistants.unshift({id,name:name.trim(),settings:{model:DEFAULT_MODEL,temperature:1,top_p:1,top_k:0,frequency_penalty:0,presence_penalty:0,repetition_penalty:1,min_p:0,top_a:0,system_prompt:DEFAULT_SYS_PROMPT}});as.save(assistants);as.setActiveId(id);renderSidebar();reflectActiveAssistant();state.currentThreadId=null;clearChat();closeSidebar()})
|
||||
el.newAssistantBtn.addEventListener('click',()=>{const name=prompt('Name your sune:');if(!name)return;const id=gid();assistants.unshift({id,name:name.trim(),settings:{model:DEFAULT_MODEL,temperature:1,top_p:1,top_k:0,frequency_penalty:0,presence_penalty:0,repetition_penalty:1,min_p:0,top_a:0,system_prompt:DEFAULT_SYS_PROMPT,system_prompt_url:'',avatar_url:DEFAULT_NEW_AVATAR}});as.save(assistants);as.setActiveId(id);renderSidebar();reflectActiveAssistant();state.currentThreadId=null;clearChat();closeSidebar()})
|
||||
el.assistantList.addEventListener('click',e=>{const btn=e.target.closest('[data-asst-id]');if(!btn)return;const id=btn.getAttribute('data-asst-id');if(id){as.setActiveId(id);renderSidebar();reflectActiveAssistant();state.currentThreadId=null;clearChat();closeSidebar()}})
|
||||
function toggleUserMenu(show){if(show===true)el.userMenu.classList.remove('hidden');else if(show===false)el.userMenu.classList.add('hidden');else el.userMenu.classList.toggle('hidden')}
|
||||
el.userMenuBtn.addEventListener('click',e=>{e.stopPropagation();toggleUserMenu()})
|
||||
@@ -177,7 +197,7 @@ el.assistantsImportOption.addEventListener('click',()=>{importMode='assistants';
|
||||
el.threadsExportOption.addEventListener('click',async()=>{const all=await idb.all();dl(`threads-${ts()}.json`,{version:1,threads:all});toggleUserMenu(false)})
|
||||
el.threadsImportOption.addEventListener('click',()=>{importMode='threads';el.importInput.value='';el.importInput.click()})
|
||||
el.threadsDedupOption.addEventListener('click',async()=>{toggleUserMenu(false);const all=(await idb.all()).sort((a,b)=>b.updatedAt-a.updatedAt);const seen=new Set();let removed=0;for(const t of all){const key=JSON.stringify((t.messages||[]).map(m=>[m.role,m.content]));if(seen.has(key)){await idb.del(t.id);removed+=1;if(state.currentThreadId===t.id){state.currentThreadId=null;clearChat()}}else seen.add(key)}await renderHistory();alert(`${removed} duplicate${removed===1?'':'s'} removed.`)})
|
||||
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==='assistants'){const list=Array.isArray(data)?data:(Array.isArray(data.assistants)?data.assistants:[]);if(!list.length)throw new Error('No sunes');assistants=list.map(a=>({id:a.id||gid(),name:a.name||DEFAULT_SUNE_NAME,settings:Object.assign({model:DEFAULT_MODEL,temperature:1,top_p:1,top_k:0,frequency_penalty:0,presence_penalty:0,repetition_penalty:1,min_p:0,top_a:0,system_prompt:DEFAULT_SYS_PROMPT},a.settings||{})}));as.save(assistants);as.setActiveId(data.activeId&&assistants.some(x=>x.id===data.activeId)?data.activeId:assistants[0]?.id||null);renderSidebar();reflectActiveAssistant();state.currentThreadId=null;clearChat();alert('Sunes imported.')}else if(importMode==='threads'){const arr=Array.isArray(data)?data:(Array.isArray(data.threads)?data.threads:[]);if(!arr.length)throw new Error('No threads');for(const t of arr){const th={id:gid(),title:titleFrom(t.title||titleFrom(t.messages?.find?.(m=>m.role==='user')?.content||'')),pinned:!!t.pinned,createdAt:t.createdAt||Date.now(),updatedAt:t.updatedAt||Date.now(),messages:Array.isArray(t.messages)?t.messages.filter(m=>m&&m.role&&m.content):[]};await idb.put(th)}await renderHistory();alert('Threads imported.')}toggleUserMenu(false)}catch{alert('Import failed')}finally{importMode=null}})
|
||||
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==='assistants'){const list=Array.isArray(data)?data:(Array.isArray(data.assistants)?data.assistants:[]);if(!list.length)throw new Error('No sunes');assistants=list.map(a=>({id:a.id||gid(),name:a.name||DEFAULT_SUNE_NAME,settings:Object.assign({model:DEFAULT_MODEL,temperature:1,top_p:1,top_k:0,frequency_penalty:0,presence_penalty:0,repetition_penalty:1,min_p:0,top_a:0,system_prompt:DEFAULT_SYS_PROMPT,system_prompt_url:'',avatar_url:DEFAULT_AVATAR},a.settings||{})}));as.save(assistants);as.setActiveId(data.activeId&&assistants.some(x=>x.id===data.activeId)?data.activeId:assistants[0]?.id||null);renderSidebar();reflectActiveAssistant();state.currentThreadId=null;clearChat();alert('Sunes imported.')}else if(importMode==='threads'){const arr=Array.isArray(data)?data:(Array.isArray(data.threads)?data.threads:[]);if(!arr.length)throw new Error('No threads');for(const t of arr){const th={id:gid(),title:titleFrom(t.title||titleFrom(t.messages?.find?.(m=>m.role==='user')?.content||'')),pinned:!!t.pinned,createdAt:t.createdAt||Date.now(),updatedAt:t.updatedAt||Date.now(),messages:Array.isArray(t.messages)?t.messages.filter(m=>m&&m.role&&m.content):[]};await idb.put(th)}await renderHistory();alert('Threads imported.')}toggleUserMenu(false)}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)}))}
|
||||
async function init(){await idb.open();await renderHistory();renderSidebar();reflectActiveAssistant();clearChat();if(window.lucide)lucide.createIcons();fit();kbBind();kbUpdate()}
|
||||
@@ -185,4 +205,4 @@ window.addEventListener('resize',()=>hideHistoryMenu())
|
||||
init()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user