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