Commit a62db13c by michaelpastushkov

fix

parent 829e340b
...@@ -155,41 +155,6 @@ ...@@ -155,41 +155,6 @@
filter: drop-shadow(0 6px 16px rgba(0, 0, 0, .15)); filter: drop-shadow(0 6px 16px rgba(0, 0, 0, .15));
opacity: .98; 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> </style>
</head> </head>
...@@ -212,14 +177,6 @@ ...@@ -212,14 +177,6 @@
<div id="thread"></div> <div id="thread"></div>
</main> </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) --> <!-- Composer (sticks above fixed footer) -->
<div class="composer py-3"> <div class="composer py-3">
<div class="container"> <div class="container">
...@@ -305,11 +262,6 @@ ...@@ -305,11 +262,6 @@
if (stopBtn) stopBtn.textContent = t('buttons.stop', 'Stop'); if (stopBtn) stopBtn.textContent = t('buttons.stop', 'Stop');
const disclaimer = document.getElementById('disclaimer'); const disclaimer = document.getElementById('disclaimer');
if (disclaimer) disclaimer.textContent = t('footer.disclaimer', 'Beware: Cat can make mistakes.'); 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(); setWelcomeImage();
} }
...@@ -324,12 +276,10 @@ ...@@ -324,12 +276,10 @@
const stopBtn = document.getElementById('stopBtn'); const stopBtn = document.getElementById('stopBtn');
const welcomeLogo = document.getElementById('welcomeLogo'); const welcomeLogo = document.getElementById('welcomeLogo');
const mainScroll = document.getElementById('mainScroll'); const mainScroll = document.getElementById('mainScroll');
const scrollDownBtn = document.getElementById('scrollDownBtn');
let firstRequestDone = false; let firstRequestDone = false;
let controller = null; let controller = null;
// --- helpers ---
function renderMarkdown(targetEl, markdownText) { function renderMarkdown(targetEl, markdownText) {
const html = marked.parse(markdownText, { breaks: true, gfm: true, headerIds: false }); const html = marked.parse(markdownText, { breaks: true, gfm: true, headerIds: false });
targetEl.innerHTML = DOMPurify.sanitize(html, { USE_PROFILES: { html: true } }); targetEl.innerHTML = DOMPurify.sanitize(html, { USE_PROFILES: { html: true } });
...@@ -346,28 +296,11 @@ ...@@ -346,28 +296,11 @@
const img = targetEl.querySelector('img.thinking-cat'); const img = targetEl.querySelector('img.thinking-cat');
if (img) targetEl.innerHTML = ''; if (img) targetEl.innerHTML = '';
} }
function scrollToBottom() {
// --- Scroll indicator --- // force scroll to very bottom of the scrollable area
function needsScroll() { return mainScroll.scrollHeight - mainScroll.clientHeight > 8; } mainScroll.scrollTop = mainScroll.scrollHeight;
function atBottom() { return mainScroll.scrollTop + mainScroll.clientHeight >= mainScroll.scrollHeight - 50; }
function updateScrollIndicator() {
if (needsScroll() && !atBottom()) scrollDownBtn.classList.add('show');
else scrollDownBtn.classList.remove('show');
} }
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) { function setComposerLocked(locked) {
isRunning = locked; isRunning = locked;
inputText.placeholder = locked ? t('status.thinking', 'Thinking…') : t('composer.placeholder', 'What can I help you with?'); inputText.placeholder = locked ? t('status.thinking', 'Thinking…') : t('composer.placeholder', 'What can I help you with?');
...@@ -377,28 +310,23 @@ ...@@ -377,28 +310,23 @@
if (locked) inputText.blur(); else inputText.focus(); if (locked) inputText.blur(); else inputText.focus();
} }
// --- Flow ---
async function start() { async function start() {
const prompt = inputText.value.trim(); const prompt = inputText.value.trim();
if (!prompt) return; if (!prompt) return;
if (!firstRequestDone) { welcomeLogo?.remove(); firstRequestDone = true; updateScrollIndicator(); } if (!firstRequestDone) { welcomeLogo?.remove(); firstRequestDone = true; }
const shouldAutoScroll = atBottom();
const userMsg = document.createElement('div'); const userMsg = document.createElement('div');
userMsg.className = 'message user-message'; userMsg.className = 'message user-message';
userMsg.textContent = prompt; userMsg.textContent = prompt;
thread.appendChild(userMsg); thread.appendChild(userMsg);
updateScrollIndicator(); scrollToBottom();
const assistantMsg = document.createElement('div'); const assistantMsg = document.createElement('div');
assistantMsg.className = 'message assistant-message'; assistantMsg.className = 'message assistant-message';
showThinkingCat(assistantMsg); showThinkingCat(assistantMsg);
thread.appendChild(assistantMsg); thread.appendChild(assistantMsg);
updateScrollIndicator(); scrollToBottom();
if (shouldAutoScroll) assistantMsg.scrollIntoView({ behavior: 'smooth', block: 'start' });
inputText.value = ''; inputText.value = '';
controller = new AbortController(); controller = new AbortController();
...@@ -426,10 +354,7 @@ ...@@ -426,10 +354,7 @@
rafPending = false; rafPending = false;
if (!receivedAny) { clearThinkingCatIfPresent(assistantMsg); receivedAny = true; } if (!receivedAny) { clearThinkingCatIfPresent(assistantMsg); receivedAny = true; }
renderMarkdown(assistantMsg, rawMarkdown); renderMarkdown(assistantMsg, rawMarkdown);
if (shouldAutoScroll && atBottom()) { scrollToBottom();
mainScroll.scrollTop = mainScroll.scrollHeight;
}
updateScrollIndicator();
}); });
}; };
...@@ -454,7 +379,7 @@ ...@@ -454,7 +379,7 @@
} finally { } finally {
setComposerLocked(false); setComposerLocked(false);
controller = null; controller = null;
updateScrollIndicator(); scrollToBottom();
} }
} }
...@@ -481,7 +406,7 @@ ...@@ -481,7 +406,7 @@
try { await fetch('/api/clear', { method: 'GET' }); } catch (_) { } try { await fetch('/api/clear', { method: 'GET' }); } catch (_) { }
await loadLocale(CURRENT_LOCALE); await loadLocale(CURRENT_LOCALE);
document.getElementById('inputText').focus(); document.getElementById('inputText').focus();
updateScrollIndicator(); scrollToBottom();
}); });
</script> </script>
</body> </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