Fix scroll-to-bottom on page load/refresh in builder page
The scroll-to-bottom functionality wasn't working properly on page load because messages take time to load and render. This fix adds: - Enhanced scrollChatToBottom() with image load detection - ResizeObserver to detect content height changes - Multiple scroll attempts with increasing delays (up to 2s) - Additional scroll calls in selectSessionById with proper delays - Scroll calls at end of initBuilder initialization This ensures the chat area properly scrolls to the bottom even when content loads asynchronously or images take time to render.
This commit is contained in:
@@ -1520,7 +1520,7 @@ function closeHistoryModal() {
|
|||||||
if (el.historyModal) el.historyModal.style.display = 'none';
|
if (el.historyModal) el.historyModal.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollChatToBottom() {
|
function scrollChatToBottom(force = false) {
|
||||||
if (!el.chatArea) return;
|
if (!el.chatArea) return;
|
||||||
const target = el.chatArea;
|
const target = el.chatArea;
|
||||||
|
|
||||||
@@ -1533,14 +1533,18 @@ function scrollChatToBottom() {
|
|||||||
// 1. Immediate scroll attempt
|
// 1. Immediate scroll attempt
|
||||||
doScroll();
|
doScroll();
|
||||||
|
|
||||||
// 2. After a short delay to allow DOM updates
|
// 2. After short delays to allow DOM updates
|
||||||
setTimeout(() => { doScroll(); }, 10);
|
setTimeout(() => { doScroll(); }, 10);
|
||||||
setTimeout(() => { doScroll(); }, 50);
|
setTimeout(() => { doScroll(); }, 50);
|
||||||
setTimeout(() => { doScroll(); }, 100);
|
setTimeout(() => { doScroll(); }, 100);
|
||||||
setTimeout(() => { doScroll(); }, 250);
|
setTimeout(() => { doScroll(); }, 250);
|
||||||
setTimeout(() => { doScroll(); }, 500);
|
setTimeout(() => { doScroll(); }, 500);
|
||||||
|
|
||||||
// 3. Use requestAnimationFrame for smooth scrolling
|
// 3. Longer delay for content to fully settle (especially on page load)
|
||||||
|
setTimeout(() => { doScroll(); }, 1000);
|
||||||
|
setTimeout(() => { doScroll(); }, 2000);
|
||||||
|
|
||||||
|
// 4. Use requestAnimationFrame for smooth scrolling
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
doScroll();
|
doScroll();
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@@ -1551,22 +1555,65 @@ function scrollChatToBottom() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4. Scroll when images load (if any)
|
// 5. Wait for ALL images to load before scrolling
|
||||||
const images = target.querySelectorAll('img');
|
const images = target.querySelectorAll('img');
|
||||||
|
let imagesToLoad = 0;
|
||||||
|
|
||||||
images.forEach((img) => {
|
images.forEach((img) => {
|
||||||
if (!img.complete) {
|
if (!img.complete) {
|
||||||
img.addEventListener('load', () => doScroll(), { once: true });
|
imagesToLoad++;
|
||||||
|
img.addEventListener('load', () => {
|
||||||
|
imagesToLoad--;
|
||||||
|
doScroll();
|
||||||
|
// Scroll again after a short delay to ensure layout is updated
|
||||||
|
setTimeout(() => doScroll(), 100);
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
|
img.addEventListener('error', () => {
|
||||||
|
imagesToLoad--;
|
||||||
|
doScroll();
|
||||||
|
}, { once: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5. Also scroll when any content finishes rendering (using MutationObserver)
|
// If there are images loading, scroll again after they all complete
|
||||||
|
if (imagesToLoad > 0) {
|
||||||
|
const checkImagesLoaded = setInterval(() => {
|
||||||
|
if (imagesToLoad === 0) {
|
||||||
|
clearInterval(checkImagesLoaded);
|
||||||
|
doScroll();
|
||||||
|
setTimeout(() => doScroll(), 100);
|
||||||
|
setTimeout(() => doScroll(), 500);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// Stop checking after 5 seconds to avoid infinite interval
|
||||||
|
setTimeout(() => clearInterval(checkImagesLoaded), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Use ResizeObserver to detect when content height changes
|
||||||
|
if (typeof ResizeObserver !== 'undefined') {
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.target === target) {
|
||||||
|
doScroll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resizeObserver.observe(target);
|
||||||
|
|
||||||
|
// Stop observing after a while
|
||||||
|
setTimeout(() => resizeObserver.disconnect(), 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Also scroll when any content finishes rendering (using MutationObserver)
|
||||||
if (typeof MutationObserver !== 'undefined') {
|
if (typeof MutationObserver !== 'undefined') {
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
doScroll();
|
doScroll();
|
||||||
});
|
});
|
||||||
observer.observe(target, { childList: true, subtree: true });
|
observer.observe(target, { childList: true, subtree: true });
|
||||||
// Stop observing after a while to avoid memory leaks
|
// Stop observing after a while to avoid memory leaks
|
||||||
setTimeout(() => observer.disconnect(), 2000);
|
setTimeout(() => observer.disconnect(), 3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2775,15 +2822,20 @@ window.selectSessionById = async function(id) {
|
|||||||
console.log('[BUILDER] Rendering UI with fresh session data');
|
console.log('[BUILDER] Rendering UI with fresh session data');
|
||||||
renderSessionMeta(freshSession);
|
renderSessionMeta(freshSession);
|
||||||
renderMessages(freshSession);
|
renderMessages(freshSession);
|
||||||
// Add a small delay to ensure DOM is updated before scrolling
|
// Wait for content to fully render before scrolling
|
||||||
setTimeout(() => scrollChatToBottom(), 50);
|
// Use multiple delays to catch content that loads asynchronously
|
||||||
|
setTimeout(() => scrollChatToBottom(), 100);
|
||||||
|
setTimeout(() => scrollChatToBottom(), 500);
|
||||||
|
setTimeout(() => scrollChatToBottom(), 1000);
|
||||||
} else {
|
} else {
|
||||||
// If refresh failed, use the local session data instead
|
// If refresh failed, use the local session data instead
|
||||||
console.warn('[BUILDER] Using local session data for UI render');
|
console.warn('[BUILDER] Using local session data for UI render');
|
||||||
renderSessionMeta(session);
|
renderSessionMeta(session);
|
||||||
renderMessages(session);
|
renderMessages(session);
|
||||||
// Add a small delay to ensure DOM is updated before scrolling
|
// Wait for content to fully render before scrolling
|
||||||
setTimeout(() => scrollChatToBottom(), 50);
|
setTimeout(() => scrollChatToBottom(), 100);
|
||||||
|
setTimeout(() => scrollChatToBottom(), 500);
|
||||||
|
setTimeout(() => scrollChatToBottom(), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update URL to reflect the new session (but don't reload page)
|
// Update URL to reflect the new session (but don't reload page)
|
||||||
@@ -4557,6 +4609,12 @@ window.addEventListener('focus', () => {
|
|||||||
|
|
||||||
// Load provider limits for fallback model selection
|
// Load provider limits for fallback model selection
|
||||||
loadProviderLimits();
|
loadProviderLimits();
|
||||||
|
|
||||||
|
// Scroll to bottom after initialization completes - use multiple delays
|
||||||
|
// to catch content that renders asynchronously
|
||||||
|
setTimeout(() => scrollChatToBottom(), 500);
|
||||||
|
setTimeout(() => scrollChatToBottom(), 1000);
|
||||||
|
setTimeout(() => scrollChatToBottom(), 2000);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
async function loadProviderLimits() {
|
async function loadProviderLimits() {
|
||||||
|
|||||||
Reference in New Issue
Block a user