Commit 3f1bf550 by Michael Pastushkov

fix

parent ecbc30ca
......@@ -7,6 +7,7 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<style>
:root{
/* tune these if footer/composer sizes change */
--footer-h: 2.75rem; /* fixed footer height (approx) */
--composer-h: 6.5rem; /* composer block height (approx) */
--bottom-pad: calc(var(--footer-h) + var(--composer-h) + 1rem);
......@@ -24,7 +25,7 @@
main{
flex:1 1 auto;
min-height:0; /* IMPORTANT: allow flex child to actually scroll */
min-height:0; /* allow flex child to actually scroll */
overflow-y:auto; /* scrollable area */
position:relative;
padding-bottom:var(--bottom-pad); /* room for composer + fixed footer */
......@@ -70,30 +71,6 @@
max-width:520px; width:100%; height:auto;
filter:drop-shadow(0 6px 16px rgba(0,0,0,.15)); opacity:.98;
}
/* Scroll button: FIXED, toggled by .show */
#scrollBtn{
position:fixed;
right:1rem;
bottom:calc(var(--footer-h) + var(--composer-h) + 1rem); /* sits above composer + footer */
z-index:1100;
display:none; /* hidden by default; JS toggles .show */
align-items:center;
justify-content:center;
border:none;
border-radius:999px;
width:40px;
height:40px;
background:rgba(13,110,253,.95);
color:#fff;
box-shadow:0 4px 14px rgba(0,0,0,.18);
cursor:pointer;
transition:opacity .18s ease-in-out, transform .12s ease-in-out;
opacity:.96;
}
#scrollBtn.show{ display:inline-flex; }
#scrollBtn:hover{ opacity:1; transform:translateY(-1px); }
#scrollBtn svg{ width:22px; height:22px; pointer-events:none; }
</style>
</head>
<body>
......@@ -115,14 +92,6 @@
<div id="thread"></div>
</main>
<!-- Scroll button: visibility toggled by JS -->
<button id="scrollBtn" type="button" aria-label="Scroll down" title="Scroll down">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<!-- down chevron -->
<path d="M12 16a1 1 0 0 1-.7-.29l-6-6a1 1 0 1 1 1.4-1.42L12 13.59l5.3-5.3a1 1 0 0 1 1.4 1.42l-6 6A1 1 0 0 1 12 16z"/>
</svg>
</button>
<!-- Composer (sticks above fixed footer) -->
<div class="composer py-3">
<div class="container">
......@@ -142,7 +111,7 @@
<script src="https://cdn.jsdelivr.net/npm/marked@12.0.2/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.6/dist/purify.min.js"></script>
<script>
// --- i18n setup (unchanged) ---
// --- i18n setup ---
const DEFAULT_LOCALE='en'; const SUPPORTED=['en','ru'];
let I18N={}; let CURRENT_LOCALE=detectLocale(); let isRunning=false;
......@@ -159,7 +128,6 @@
const goBtn=document.getElementById('goBtn'); if(goBtn) goBtn.textContent=t('buttons.go','Go');
const stopBtn=document.getElementById('stopBtn'); if(stopBtn) stopBtn.textContent=t('buttons.stop','Stop');
const disclaimer=document.getElementById('disclaimer'); if(disclaimer) disclaimer.textContent=t('footer.disclaimer','Beware: Cat can make mistakes.');
const sb=document.getElementById('scrollBtn'); if(sb){ sb.setAttribute('aria-label', t('buttons.scrollDown','Scroll down')); sb.title=t('buttons.scrollDown','Scroll down'); }
setWelcomeImage();
}
......@@ -171,8 +139,7 @@
const goBtn=document.getElementById('goBtn');
const stopBtn=document.getElementById('stopBtn');
const welcomeLogo=document.getElementById('welcomeLogo');
const mainScroll=document.getElementById('mainScroll'); // intended scroll container
const scrollBtn=document.getElementById('scrollBtn'); // the button we toggle & click
const mainScroll=document.getElementById('mainScroll');
let firstRequestDone=false;
let controller=null;
......@@ -195,43 +162,11 @@
if(img) targetEl.innerHTML='';
}
// --- figure out what is actually scrolling right now ---
function isOverflowing(el){ return (el.scrollHeight - el.clientHeight) > 4; }
function currentScrollEl(){
// Prefer main if it overflows, otherwise fall back to the page scroller
if (mainScroll && isOverflowing(mainScroll)) return mainScroll;
return document.scrollingElement || document.documentElement;
}
function atBottom(el){
return (el.scrollTop + el.clientHeight) >= (el.scrollHeight - 4);
}
function updateScrollIndicator(){
if (!scrollBtn) return;
const el = currentScrollEl();
if (isOverflowing(el) && !atBottom(el)) scrollBtn.classList.add('show');
else scrollBtn.classList.remove('show');
// --- bottom helpers ---
function atBottom(){
return (mainScroll.scrollTop + mainScroll.clientHeight) >= (mainScroll.scrollHeight - 4);
}
// Click: scroll the **actual** scroll container to bottom
if (scrollBtn) {
scrollBtn.addEventListener('click', () => {
const el = currentScrollEl();
const top = el.scrollHeight;
if (el === document.scrollingElement || el === document.documentElement || el === document.body) {
window.scrollTo({ top, behavior: 'smooth' });
} else {
el.scrollTo({ top, behavior: 'smooth' });
}
setTimeout(updateScrollIndicator, 400);
});
}
// Keep indicator state in sync (listen to both container + window)
if (mainScroll) mainScroll.addEventListener('scroll', updateScrollIndicator, { passive:true });
window.addEventListener('scroll', updateScrollIndicator, { passive:true });
window.addEventListener('resize', updateScrollIndicator);
// --- composer lock ---
function setComposerLocked(locked){
isRunning=locked;
......@@ -249,8 +184,7 @@
if(!firstRequestDone){ welcomeLogo?.remove(); firstRequestDone=true; }
const elBefore = currentScrollEl();
const auto = atBottom(elBefore);
const auto = atBottom();
const userMsg=document.createElement('div');
userMsg.className='message user-message';
......@@ -263,7 +197,6 @@
thread.appendChild(assistantMsg);
if (auto) assistantMsg.scrollIntoView({ behavior:'smooth', block:'start' });
updateScrollIndicator();
inputText.value='';
controller=new AbortController();
......@@ -289,14 +222,9 @@
rafPending=false;
if(!receivedAny){ clearThinkingCatIfPresent(assistantMsg); receivedAny=true; }
renderMarkdown(assistantMsg, rawMarkdown);
// If user was at bottom before streaming, keep pinned to bottom of the active scroller
if (auto) {
const el = currentScrollEl();
el.scrollTop = el.scrollHeight;
if (auto && atBottom()){
mainScroll.scrollTop = mainScroll.scrollHeight;
}
updateScrollIndicator();
});
};
......@@ -321,7 +249,6 @@
}finally{
setComposerLocked(false);
controller=null;
updateScrollIndicator();
}
}
......@@ -331,18 +258,17 @@
}
// events
document.getElementById('promptForm').addEventListener('submit', e => { e.preventDefault(); if (goBtn.style.display!=='none') start(); });
document.getElementById('inputText').addEventListener('keydown', e => {
promptForm.addEventListener('submit', e => { e.preventDefault(); if (goBtn.style.display!=='none') start(); });
inputText.addEventListener('keydown', e => {
if (e.key==='Enter' && !e.shiftKey && goBtn.style.display!=='none'){ e.preventDefault(); start(); }
});
document.getElementById('stopBtn').addEventListener('click', stop);
stopBtn.addEventListener('click', stop);
// init
window.addEventListener('DOMContentLoaded', async () => {
try{ await fetch('/api/clear',{method:'GET'}); }catch(_){}
await loadLocale(CURRENT_LOCALE);
inputText.focus();
updateScrollIndicator(); // initial state
});
</script>
</body>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment