scroller update, action buttons - WIP

frdel committed Jan 26, 2026 at 09:53 UTC 6c822f1446f59a1b534b827d5b2de04d491f5501
3 files changed +63 -45
webui/components/messages/process-group/process-group.css
+1 -1
@@ -630,7 +630,7 @@
630 white-space: pre;
631 font-family: monospace;
632 max-width: fit-content;
633 - max-height: 300px;
633 + /* max-height: 300px; */
634 overflow: auto;
635 -webkit-overflow-scrolling: touch;
636 scrollbar-width: thin;
webui/index.js
+7 -6
@@ -200,12 +200,14 @@ async function updateUserTime() {
200 updateUserTime();
201 setInterval(updateUserTime, 1000);
202
203 -function setMessages(logs) {
204 - const result = msgs.setMessages(logs);
203 +function setMessages(...params) {
204 const chatHistoryEl = document.getElementById("chat-history");
206 - if (preferencesStore.autoScroll && chatHistoryEl) {
207 - chatHistoryEl.scrollTop = chatHistoryEl.scrollHeight;
208 - }
205 + let scroller;
206 +
207 + if (preferencesStore.autoScroll && chatHistoryEl) scroller = new msgs.Scroller(chatHistoryEl)
208 + const result = msgs.setMessages(...params);
209 + if (scroller) scroller.reApplyScroll();
210 +
211 return result;
212 }
213
@@ -317,7 +319,6 @@ export async function poll() {
319 if (lastLogVersion != response.log_version) {
320 updated = true;
321 setMessages(response.logs);
320 -
322 afterMessagesUpdate(response.logs);
323 applyModeSteps(preferencesStore.detailMode, preferencesStore.showUtils);
324 }
webui/js/messages.js
+55 -38
@@ -651,20 +651,22 @@ function drawProcessStep({
651 };
652 }
653
654 -function drawStandaloneMessage(id, heading, content, options = {}) {
655 - const {
656 - position = "mid",
657 - forceNewGroup = false,
658 - containerClasses = [],
659 - mainClass = "",
660 - messageClasses = [],
661 - contentClasses = [],
662 - markdown = false,
663 - latex = false,
664 - kvps = null,
665 - copyContent = null,
666 - speakContent = null,
667 - } = options;
654 +function drawStandaloneMessage({
655 + id,
656 + heading,
657 + content,
658 + position = "mid",
659 + forceNewGroup = false,
660 + containerClasses = [],
661 + mainClass = "",
662 + messageClasses = [],
663 + contentClasses = [],
664 + markdown = false,
665 + latex = false,
666 + kvps = null,
667 + copyContent = null,
668 + speakContent = null,
669 +}) {
670
671 const container = getOrCreateMessageContainer(
672 id,
@@ -857,7 +859,10 @@ export function drawMessageDefault({
859 kvps = null,
860 ...additional
861 }) {
860 - return drawStandaloneMessage(id, heading, content, {
862 + return drawStandaloneMessage({
863 + id,
864 + heading,
865 + content,
866 position: "left",
867 containerClasses: ["ai-container"],
868 mainClass: "message-default",
@@ -886,7 +891,6 @@ export function drawMessageAgent({
891 id,
892 title,
893 code: "GEN",
889 - codeClass: "status-gen",
894 classes: null,
895 kvps: displayKvps,
896 copyText: kvps?.thoughts,
@@ -1101,7 +1105,6 @@ export function drawMessageTool({
1105 id,
1106 title,
1107 code: "USE",
1104 - codeClass: "status-tool",
1108 classes: null,
1109 kvps: displayKvps,
1110 content,
@@ -1125,7 +1128,6 @@ export function drawMessageCodeExe({
1128 let title = "Code Execution";
1129 // show command at the start and end
1130 if (
1128 - type === "code_exe" &&
1131 kvps?.code &&
1132 /done_all|code_execution_tool/.test(heading || "")
1133 ) {
@@ -1146,7 +1148,6 @@ export function drawMessageCodeExe({
1148 id,
1149 title,
1150 code: "EXE",
1149 - codeClass: "status-exe",
1151 classes: null,
1152 kvps: displayKvps,
1153 content,
@@ -1174,7 +1175,6 @@ export function drawMessageBrowser({
1175 id,
1176 title,
1177 code: "HDL",
1177 - codeClass: "status-hdl",
1178 classes: null,
1179 kvps: displayKvps,
1180 content,
@@ -1202,17 +1202,23 @@ export function drawMessageMcp({
1202 id,
1203 title,
1204 code: "MCP",
1205 - codeClass: "status-mcp",
1205 classes: null,
1206 kvps: displayKvps,
1207 content,
1208 // contentClasses: [],
1210 - copyText: content,
1211 - speakText: content,
1209 + actionButtons:[
1210 + createActionButton("detail","", ()=>{ openDetail(arguments[0]) }),
1211 + createActionButton("speak","", ()=>{ speakText(content) }),
1212 + createActionButton("copy","Command", ()=>{ copyToClipboard(kvps?.code) }),
1213 + createActionButton("copy","Output", ()=>{ copyText(content) })
1214 + ],
1215 log: arguments[0],
1216 });
1217 }
1218
1219 +// todo - move to store
1220 +function createActionButton(icon, text, handler){}
1221 +
1222 export function drawMessageSubagent({
1223 id,
1224 type,
@@ -1230,7 +1236,6 @@ export function drawMessageSubagent({
1236 id,
1237 title,
1238 code: "SUB",
1233 - codeClass: "status-sub",
1239 classes: null,
1240 kvps: displayKvps,
1241 content,
@@ -1248,11 +1253,20 @@ export function drawMessageInfo({
1253 kvps = null,
1254 ...additional
1255 }) {
1251 - return drawStandaloneMessage(id, heading, content, {
1252 - position: "mid",
1253 - containerClasses: ["ai-container", "center-container"],
1254 - mainClass: "message-info",
1255 - kvps,
1256 + const title = cleanStepTitle(heading);
1257 + let displayKvps = { ...kvps };
1258 +
1259 + return drawProcessStep({
1260 + id,
1261 + title,
1262 + code: "INF",
1263 + classes: null,
1264 + kvps: displayKvps,
1265 + content,
1266 + // contentClasses: [],
1267 + copyText: content,
1268 + speakText: content,
1269 + log: arguments[0],
1270 });
1271 }
1272
@@ -1272,7 +1286,6 @@ export function drawMessageUtil({
1286 id,
1287 title,
1288 code: "UTL",
1275 - codeClass: "status-utl",
1289 classes: ["message-util"],
1290 kvps,
1291 copyText: null,
@@ -1296,7 +1309,7 @@ export function drawMessageHint({
1309 const statusCode = getStatusCode(type);
1310 const statusClass = getStatusClass(type);
1311
1299 - return drawProcessStep({
1312 + return drawStandaloneMessage({
1313 id,
1314 title,
1315 statusClass,
@@ -1327,7 +1340,6 @@ export function drawMessageProgress({
1340 id,
1341 title,
1342 code: "HDL",
1330 - codeClass: "status-hdl",
1343 classes: null,
1344 kvps: displayKvps,
1345 content,
@@ -1353,7 +1365,6 @@ export function drawMessageWarning({
1365 // id,
1366 // title,
1367 // code: "WRN",
1356 - // codeClass: "status-wrn",
1368 // classes: null,
1369 // kvps: displayKvps,
1370 // content,
@@ -1362,7 +1373,10 @@ export function drawMessageWarning({
1373 // speakText: content,
1374 // log: arguments[0],
1375 // });
1365 - return drawStandaloneMessage(id, heading, content, {
1376 + return drawStandaloneMessage({
1377 + id,
1378 + heading,
1379 + content,
1380 position: "mid",
1381 containerClasses: ["ai-container", "center-container"],
1382 mainClass: "message-warning",
@@ -1377,7 +1391,10 @@ export function drawMessageError({
1391 kvps = null,
1392 ...additional
1393 }) {
1380 - return drawStandaloneMessage(id, heading, content, {
1394 + return drawStandaloneMessage({
1395 + id,
1396 + heading,
1397 + content,
1398 position: "mid",
1399 containerClasses: ["ai-container", "center-container"],
1400 mainClass: "message-error",
@@ -1783,13 +1800,13 @@ function adjustMarkdownRender(element) {
1800 });
1801 }
1802
1786 -class Scroller {
1803 +export class Scroller {
1804 constructor(element) {
1805 this.element = element;
1806 this.wasAtBottom = this.isAtBottom();
1807 }
1808
1792 - isAtBottom(tolerance = 10) {
1809 + isAtBottom(tolerance = 80) {
1810 const scrollHeight = this.element.scrollHeight;
1811 const clientHeight = this.element.clientHeight;
1812 const distanceFromBottom =
@@ -1798,7 +1815,7 @@ class Scroller {
1815 }
1816
1817 reApplyScroll() {
1801 - if (this.wasAtBottom) this.element.scrollTop = this.element.scrollHeight;
1818 + if (this.wasAtBottom && !this.isAtBottom()) this.element.scrollTop = this.element.scrollHeight;
1819 }
1820 }
1821