ui: autoscroll polish, image preview sizes reduces
frdel committed
Jan 31, 2026 at 13:41 UTC
40c7c22156eadf6940695869ce865f0676bc082b
3 files changed
+28
-5
webui/css/messages.css
+2
-2
@@ -516,8 +516,8 @@
516
517
.message-agent-response .msg-content img {
518
margin: var(--spacing-xs);
519
- max-width: 25em;
520
- max-height: 25em;
519
+ max-width: 30rem;
520
+ max-height: 15rem;
521
border-radius: var(--border-radius);
522
background: repeating-linear-gradient(
523
45deg,
webui/js/messages.js
+11
-3
@@ -76,17 +76,21 @@ export function setMessages(messages) {
76
const massRender = historyEmpty || isLargeAppend;
77
78
const mainScroller = new Scroller(history, { smooth: !massRender, toleranceRem: 4, reapplyDelayMs: 1000 });
79
+
80
+ const results = []
81
82
// process messages
83
for (let i = 0; i < messages.length; i++) {
84
_massRender = historyEmpty || (isLargeAppend && i < cutoff);
83
- setMessage(messages[i]);
85
+ results.push(setMessage(messages[i]) || {});
86
}
87
88
// reset _massRender flag
89
_massRender = false;
90
89
- mainScroller.reApplyScroll();
91
+ const shouldScroll = !results[results.length - 1]?.dontScroll;
92
+
93
+ if(shouldScroll) mainScroller.reApplyScroll();
94
}
95
96
// entrypoint called from poll/WS communication, this is how all messages are rendered and updated
@@ -1199,7 +1203,7 @@ export function drawMessageUtil({
1203
].filter(Boolean)
1204
: [];
1205
1202
- return drawProcessStep({
1206
+ const result = drawProcessStep({
1207
id,
1208
title,
1209
code: "UTL",
@@ -1210,6 +1214,10 @@ export function drawMessageUtil({
1214
log: arguments[0],
1215
allowCompletedGroup: true,
1216
});
1217
+
1218
+
1219
+ result.dontScroll = preferencesStore.showUtils;
1220
+ return result;
1221
}
1222
1223
export function drawMessageHint({
webui/js/scroller.js
+15
@@ -134,18 +134,33 @@ export class Scroller {
134
return parsed;
135
}
136
137
+ _getReapplyScrollSnapshot() {
138
+ const raw = this.element?.dataset?.scrollerReapplySnapshot;
139
+ const parsed = raw == null ? null : Number(raw);
140
+ if (!Number.isFinite(parsed)) return null;
141
+ return parsed;
142
+ }
143
+
144
_clearReapplyTimeout() {
145
const id = this._getReapplyTimeoutId();
146
if (id != null) clearTimeout(id);
147
delete this.element.dataset.scrollerTimeout;
148
+ delete this.element.dataset.scrollerReapplySnapshot;
149
}
150
151
_scheduleReapplyScrollToBottom() {
152
this._clearReapplyTimeout();
153
154
+ const snapshot = this._getEffectiveScrollTop();
155
+ this.element.dataset.scrollerReapplySnapshot = String(snapshot);
156
+
157
const id = setTimeout(() => {
158
delete this.element.dataset.scrollerTimeout;
159
160
+ const expected = this._getReapplyScrollSnapshot();
161
+ delete this.element.dataset.scrollerReapplySnapshot;
162
+ if (expected != null && this._getEffectiveScrollTop() !== expected) return;
163
+
164
if (!this.wasAtBottom) return;
165
if (!this.isAtBottom()) return;
166