mirror of
https://github.com/multipleof4/devsune.git
synced 2026-01-13 16:07:55 +00:00
Update index.html
This commit is contained in:
@@ -114,10 +114,9 @@ const state={messages:[],busy:false,controller:null,currentThreadId:null}
|
||||
const getModelShort=()=>{const m=store.model||'';return m.includes('/')?m.split('/').pop():m}
|
||||
function reflectActiveAssistant(){const a=getActive();el.settingsBtnTop.title=`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 flex items-center justify-center">🤖</span><span class="truncate">${esc(a.name)}</span></button>`).join('')}
|
||||
const HL_CHAR_MAX=8000,HL_LINE_MAX=300,HL_ABS_MAX=300000
|
||||
function enhanceCodeBlocks(root){root.querySelectorAll('pre>code').forEach(code=>{const t=code.textContent||'';if(t.length>HL_ABS_MAX){return}const pre=code.parentElement;const lines=(t.match(/\n/g)||[]).length+1;const tooBig=t.length>HL_CHAR_MAX||lines>HL_LINE_MAX;if(tooBig){code.dataset.noHl='1'}if(!pre.classList.contains('relative')){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(window.hljs&&!code.dataset.noHl&&!code.dataset.hl){hljs.highlightElement(code);code.dataset.hl='1'}})}
|
||||
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){node.innerHTML=md.render(text);enhanceCodeBlocks(node)}
|
||||
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';avatar.textContent=role==='user'?'🧑':'🤖';const name=document.createElement('div');name.className='text-xs font-medium text-gray-500';name.textContent=role==='user'?'You':getModelShort();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')}
|
||||
@@ -142,14 +141,14 @@ function closeHistory(){el.historyPanel.classList.add('translate-x-full');el.his
|
||||
el.historyBtn.addEventListener('click',openHistory);el.historyOverlay.addEventListener('click',closeHistory);el.closeHistory.addEventListener('click',closeHistory)
|
||||
let menuThreadId=null;function hideHistoryMenu(){el.historyMenu.classList.add('hidden');menuThreadId=null}
|
||||
function showHistoryMenu(btn,id){menuThreadId=id;const r=btn.getBoundingClientRect();el.historyMenu.style.top=(r.bottom+4)+'px';el.historyMenu.style.left=Math.min(window.innerWidth-220,r.right-200)+'px';el.historyMenu.classList.remove('hidden');if(window.lucide)lucide.createIcons()}
|
||||
el.historyList.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'),th=threads.find(t=>t.id===id)||await idb.get(id);if(!th)return;state.currentThreadId=id;clearChat();state.messages=Array.isArray(th.messages)?[...th.messages]:[];for(const m of state.messages){const b=msgRow(m.role);renderMarkdown(b,m.content)}queueMicrotask(()=>el.chat.scrollTo({top:el.chat.scrollHeight,behavior:'smooth'}));closeHistory();hideHistoryMenu();return}if(menuBtn){e.stopPropagation();showHistoryMenu(menuBtn,menuBtn.getAttribute('data-thread-menu'))}})
|
||||
el.historyList.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'),th=threads.find(t=>t.id===id)||await idb.get(id);if(!th)return;state.currentThreadId=id;clearChat();state.messages=Array.isArray(th.messages)?[...th.messages]:[];for(const m of state.messages){const b=msgRow(m.role);renderMarkdown(b,m.content)}queueMicrotask(()=>el.chat.scrollTo({top:el.chat.scrollHeight,behavior:'smooth'}));closeHistory();hideHistoryMenu();return}if(menuBtn){e.stopPropagation();showHistoryMenu(menuBtn,menuBtn.getAttribute('[data-thread-menu]'))}})
|
||||
document.addEventListener('click',e=>{if(!el.historyMenu.contains(e.target)&&!e.target.closest('[data-thread-menu]'))hideHistoryMenu();if(!el.userMenu.contains(e.target)&&!el.userMenuBtn.contains(e.target))el.userMenu.classList.add('hidden')})
|
||||
el.historyMenu.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)||await idb.get(menuThreadId);if(!th)return;if(act==='pin'){th.pinned=!th.pinned;await idb.put(th)}else if(act==='rename'){const nv=prompt('Rename to:',th.title);if(nv!=null){th.title=titleFrom(nv);await idb.put(th)}}else if(act==='delete'){if(confirm('Delete this chat?')){await idb.del(th.id);if(state.currentThreadId===th.id){state.currentThreadId=null;clearChat()}}}hideHistoryMenu();renderHistory()})
|
||||
const raf=(fn=>{let id=null;return()=>{if(id)cancelAnimationFrame(id);id=requestAnimationFrame(()=>{id=null;fn()})}})
|
||||
const big=()=>el.input.value.length>5000||el.input.value.split('\n').length>200
|
||||
const fit=()=>{const large=big();el.input.style.overflowY=large?'auto':'hidden';el.input.style.height='auto';el.input.style.height=(large?160:Math.min(el.input.scrollHeight,160))+'px'}
|
||||
const fitRAF=raf(fit)
|
||||
el.composer.addEventListener('submit',async e=>{e.preventDefault();if(state.busy)return;const text=el.input.value.trim();if(!text)return;if(state.messages.length===0)state.currentThreadId=null;await ensureThreadOnFirstUser(text);el.input.value='';fit();addMessage('user',text);state.busy=true;setBtnStop();const assistantBubble=addAssistantBubbleStreaming();let buf='',completed=false;await askOpenRouterStreaming((delta,done)=>{buf+=delta;renderMarkdown(assistantBubble,buf);if(done&&!completed){completed=true;setBtnSend();state.busy=false;state.messages.push({role:'assistant',content:buf});persistThread();queueMicrotask(()=>el.chat.scrollTo({top:el.chat.scrollHeight,behavior:'smooth'}))}})})
|
||||
el.composer.addEventListener('submit',async e=>{e.preventDefault();if(state.busy)return;const text=el.input.value.trim();if(!text)return;if(state.messages.length===0)state.currentThreadId=null;await ensureThreadOnFirstUser(text);el.input.value='';fit();addMessage('user',text);state.busy=true;setBtnStop();const assistantBubble=addAssistantBubbleStreaming();let buf='',completed=false;await askOpenRouterStreaming((delta,done)=>{buf+=delta;renderMarkdown(assistantBubble,buf,{enhance:false});if(done&&!completed){completed=true;setBtnSend();state.busy=false;enhanceCodeBlocks(assistantBubble,true);state.messages.push({role:'assistant',content:buf});persistThread();queueMicrotask(()=>el.chat.scrollTo({top:el.chat.scrollHeight,behavior:'smooth'}))}})})
|
||||
el.messages.addEventListener('click',e=>{if(e.target.closest('.msg-avatar'))openSidebar()})
|
||||
el.input.addEventListener('input',fitRAF)
|
||||
el.input.addEventListener('paste',()=>setTimeout(fit,0))
|
||||
|
||||
Reference in New Issue
Block a user