diff --git a/rag/bin/rag-web.py b/rag/bin/rag-web.py index 1059125..1167c57 100644 --- a/rag/bin/rag-web.py +++ b/rag/bin/rag-web.py @@ -82,7 +82,7 @@ #ks{color:var(--mut);white-space:nowrap}

CPTS notes assistant

-

Answers from your note chunks — retrieval first, +

Answers from your note chunks. Retrieval first, then the local model.

@@ -110,6 +110,13 @@ document.getElementById('n').textContent=d.chunks; if(d.auth_required){kb.hidden=false; if(!apiKey) ki.focus();} }); +// Sticky autoscroll: follow the stream only while the reader is at the bottom. +// If they scroll up to re-read, stop yanking them back down — that fight is the +// jank. One listener for the whole page, not one per question. +let sticky=true; +const atBottom=()=>window.innerHeight+window.scrollY>=document.documentElement.scrollHeight-60; +const stick=()=>{if(sticky) window.scrollTo(0,document.documentElement.scrollHeight)}; +addEventListener('scroll',()=>{sticky=atBottom()},{passive:true}); function esc(s){return s.replace(/[&<>]/g,c=>({'&':'&','<':'<','>':'>'}[c]))} function render(s){return esc(s) .replace(/```([\\s\\S]*?)```/g,(m,c)=>'
'+c.replace(/^\\w*\\n/,'')+'
') @@ -121,7 +128,8 @@ const qd=document.createElement('div'); qd.className='msg q'; qd.textContent=question; const ad=document.createElement('div'); ad.className='msg'; const body=document.createElement('div'); body.className='a'; ad.appendChild(body); - log.append(qd,ad); ad.scrollIntoView({block:'end'}); + log.append(qd,ad); + sticky=true; stick(); // a fresh question always jumps to the newest message let text='', t0=Date.now(); try{ const res=await fetch('ask',{method:'POST', @@ -139,9 +147,8 @@ const line=p.split('\\n').find(l=>l.startsWith('data:')); if(!line) continue; const data=line.slice(5).trim(); if(!data||data==='[DONE]') continue; const ev=JSON.parse(data); - if(ev.token){text+=ev.token; body.innerHTML=render(text); - ad.scrollIntoView({block:'end'})} - if(ev.error){body.innerHTML+='
'+esc(ev.error)+'
'} + if(ev.token){text+=ev.token; body.innerHTML=render(text); stick()} + if(ev.error){body.innerHTML+='
'+esc(ev.error)+'
'; stick()} if(ev.sources){ const s=document.createElement('div'); s.className='src'; s.innerHTML='sources'+ev.sources.map(x=>'
'+esc(x)+'
').join(''); @@ -150,7 +157,7 @@ m.textContent=`${ev.chunks} chunks searched · retrieval ${ev.retrieve_ms}ms ` +`· first token ${(ev.ttft_ms/1000).toFixed(1)}s · ${ev.tok_per_s} tok/s ` +`· ${ev.prompt_chars} prompt chars · total ${((Date.now()-t0)/1000).toFixed(1)}s`; - ad.appendChild(m); + ad.appendChild(m); stick(); } } }