Commit a62db13c by michaelpastushkov

fix

parent 829e340b
......@@ -155,41 +155,6 @@
filter: drop-shadow(0 6px 16px rgba(0, 0, 0, .15));
opacity: .98;
}
/* Fixed scroll-to-bottom button (ensures visibility above composer/footer) */
#scrollDownBtn {
position: fixed;
right: 1rem;
bottom: calc(var(--footer-h) + var(--composer-h) + 1rem);
z-index: 1031;
display: none;
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;
opacity: .92;
}
#scrollDownBtn:hover {
opacity: 1;
}
#scrollDownBtn svg {
width: 22px;
height: 22px;
pointer-events: none;
}
#scrollDownBtn.show {
display: inline-flex;
align-items: center;
justify-content: center;
}
</style>
</head>
......@@ -212,14 +177,6 @@
<div id="thread"></div>
</main>
<!-- Fixed scroll-to-bottom button -->
<button id="scrollDownBtn" type="button" aria-label="Scroll down">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<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">
......@@ -305,11 +262,6 @@
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 scrollDownBtn = document.getElementById('scrollDownBtn');
if (scrollDownBtn) {
scrollDownBtn.setAttribute('aria-label', t('buttons.scrollDown', 'Scroll down'));
scrollDownBtn.title = t('buttons.scrollDown', 'Scroll down');
}
setWelcomeImage();
}
......@@ -324,12 +276,10 @@
const stopBtn = document.getElementById('stopBtn');
const welcomeLogo = document.getElementById('welcomeLogo');
const mainScroll = document.getElementById('mainScroll');
const scrollDownBtn = document.getElementById('scrollDownBtn');
let firstRequestDone = false;
let controller = null;
// --- helpers ---
function renderMarkdown(targetEl, markdownText) {
const html = marked.parse(markdownText, { breaks: true, gfm: true, headerIds: false });
targetEl.innerHTML = DOMPurify.sanitize(html, { USE_PROFILES: { html: true } });
......@@ -346,28 +296,11 @@
const img = targetEl.querySelector('img.thinking-cat');
if (img) targetEl.innerHTML = '';
}
// --- Scroll indicator ---
function needsScroll() { return mainScroll.scrollHeight - mainScroll.clientHeight > 8; }
function atBottom() { return mainScroll.scrollTop + mainScroll.clientHeight >= mainScroll.scrollHeight - 50; }
function updateScrollIndicator() {
if (needsScroll() && !atBottom()) scrollDownBtn.classList.add('show');
else scrollDownBtn.classList.remove('show');
function scrollToBottom() {
// force scroll to very bottom of the scrollable area
mainScroll.scrollTop = mainScroll.scrollHeight;
}
scrollDownBtn.addEventListener('click', () => {
mainScroll.scrollBy({ top: mainScroll.clientHeight, behavior: 'smooth' });
setTimeout(updateScrollIndicator, 320);
});
mainScroll.addEventListener('scroll', updateScrollIndicator);
window.addEventListener('resize', updateScrollIndicator);
// Observe dynamic changes to keep indicator in sync
const ro = new ResizeObserver(updateScrollIndicator);
ro.observe(mainScroll);
ro.observe(thread);
mainScroll.addEventListener('load', updateScrollIndicator, true);
// --- Composer lock ---
function setComposerLocked(locked) {
isRunning = locked;
inputText.placeholder = locked ? t('status.thinking', 'Thinking…') : t('composer.placeholder', 'What can I help you with?');
......@@ -377,28 +310,23 @@
if (locked) inputText.blur(); else inputText.focus();
}
// --- Flow ---
async function start() {
const prompt = inputText.value.trim();
if (!prompt) return;
if (!firstRequestDone) { welcomeLogo?.remove(); firstRequestDone = true; updateScrollIndicator(); }
const shouldAutoScroll = atBottom();
if (!firstRequestDone) { welcomeLogo?.remove(); firstRequestDone = true; }
const userMsg = document.createElement('div');
userMsg.className = 'message user-message';
userMsg.textContent = prompt;
thread.appendChild(userMsg);
updateScrollIndicator();
scrollToBottom();
const assistantMsg = document.createElement('div');
assistantMsg.className = 'message assistant-message';
showThinkingCat(assistantMsg);
thread.appendChild(assistantMsg);
updateScrollIndicator();
if (shouldAutoScroll) assistantMsg.scrollIntoView({ behavior: 'smooth', block: 'start' });
scrollToBottom();
inputText.value = '';
controller = new AbortController();
......@@ -426,10 +354,7 @@
rafPending = false;
if (!receivedAny) { clearThinkingCatIfPresent(assistantMsg); receivedAny = true; }
renderMarkdown(assistantMsg, rawMarkdown);
if (shouldAutoScroll && atBottom()) {
mainScroll.scrollTop = mainScroll.scrollHeight;
}
updateScrollIndicator();
scrollToBottom();
});
};
......@@ -454,7 +379,7 @@
} finally {
setComposerLocked(false);
controller = null;
updateScrollIndicator();
scrollToBottom();
}
}
......@@ -481,7 +406,7 @@
try { await fetch('/api/clear', { method: 'GET' }); } catch (_) { }
await loadLocale(CURRENT_LOCALE);
document.getElementById('inputText').focus();
updateScrollIndicator();
scrollToBottom();
});
</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