smooth scroller
smooth option for scroller
3clyp50 committed
Jan 29, 2026 at 15:58 UTC
a15069acc60340796a2ad43197c75cee9d0fc408
2 files changed
+26
-19
webui/index.js
+1
-8
@@ -200,14 +200,7 @@ updateUserTime();
200
setInterval(updateUserTime, 1000);
201
202
function setMessages(...params) {
203
- const chatHistoryEl = document.getElementById("chat-history");
204
- let scroller;
205
-
206
- if (preferencesStore.autoScroll && chatHistoryEl) scroller = new msgs.Scroller(chatHistoryEl)
207
- const result = msgs.setMessages(...params);
208
- if (scroller) scroller.reApplyScroll();
209
-
210
- return result;
203
+ return msgs.setMessages(...params);
204
}
205
206
globalThis.loadKnowledge = async function () {
webui/js/messages.js
+25
-11
@@ -69,6 +69,12 @@ export function setMessages(messages) {
69
const historyEmpty = !history || history.childElementCount === 0;
70
const isLargeAppend = !historyEmpty && messages.length > 10;
71
const cutoff = isLargeAppend ? Math.max(0, messages.length - 2) : 0;
72
+ const massRender = historyEmpty || isLargeAppend;
73
+
74
+ let mainScroller;
75
+ if (preferencesStore.autoScroll && history) {
76
+ mainScroller = new Scroller(history, { smooth: !massRender });
77
+ }
78
79
// process messages
80
for (let i = 0; i < messages.length; i++) {
@@ -78,6 +84,8 @@ export function setMessages(messages) {
84
85
// reset _massRender flag
86
_massRender = false;
87
+
88
+ if (mainScroller) mainScroller.reApplyScroll();
89
}
90
91
// entrypoint called from poll/WS communication, this is how all messages are rendered and updated
@@ -350,7 +358,9 @@ function drawProcessStep({
358
titleEl.textContent = title;
359
360
// auto-scroller of the step detail
353
- const detailScroller = new Scroller(stepDetailScroll); // scroller for step detail content
361
+ const detailScroller = new Scroller(stepDetailScroll, {
362
+ smooth: !isMassRender(),
363
+ }); // scroller for step detail content
364
365
// update KVPs of the step detail
366
const kvpsTable = drawKvpsIncremental(stepDetailScroll, kvps);
@@ -366,7 +376,7 @@ function drawProcessStep({
376
stepDetailContent.textContent = content;
377
378
// reapply scroll position (autoscroll if bottom) - only when expanded already and not mass rendering
369
- if (isExpanded && !isMassRender()) detailScroller.reApplyScroll();
379
+ if (isExpanded) detailScroller.reApplyScroll();
380
381
// Render action buttons: get/create container, clear, append
382
const stepActionBtns = ensureChild(
@@ -502,7 +512,7 @@ export function _drawMessage({
512
}
513
514
// reapply scroll position or autoscroll
505
- const scroller = new Scroller(bodyDiv);
515
+ const scroller = new Scroller(bodyDiv, { smooth: !isMassRender() });
516
517
// Handle KVPs incrementally
518
drawKvpsIncremental(bodyDiv, kvps, false);
@@ -1587,22 +1597,26 @@ function adjustMarkdownRender(element) {
1597
}
1598
1599
export class Scroller {
1590
- constructor(element) {
1600
+ constructor(element, { smooth = false } = {}) {
1601
this.element = element;
1602
+ this.smooth = smooth;
1603
this.wasAtBottom = this.isAtBottom();
1604
}
1605
1606
isAtBottom(tolerance = 80) {
1596
- const scrollHeight = this.element.scrollHeight;
1597
- const clientHeight = this.element.clientHeight;
1598
- const distanceFromBottom =
1599
- scrollHeight - this.element.scrollTop - clientHeight;
1600
- return distanceFromBottom <= tolerance;
1607
+ const { scrollHeight, clientHeight, scrollTop } = this.element;
1608
+ return scrollHeight - scrollTop - clientHeight <= tolerance;
1609
+ }
1610
+
1611
+ scrollToBottom() {
1612
+ const target = this.element.scrollHeight;
1613
+ this.smooth
1614
+ ? this.element.scrollTo({ top: target, behavior: "smooth" })
1615
+ : (this.element.scrollTop = target);
1616
}
1617
1618
reApplyScroll() {
1604
- if (this.wasAtBottom && !this.isAtBottom())
1605
- this.element.scrollTop = this.element.scrollHeight;
1619
+ if (this.wasAtBottom && !this.isAtBottom()) this.scrollToBottom();
1620
}
1621
}
1622