From 700d0041cf817817d0c5b1b1aa8c83410113ab88 Mon Sep 17 00:00:00 2001 From: 3clyp50 Date: Tue, 27 Jan 2026 14:39:32 +0100 Subject: [PATCH 1/9] collapsible standalone msgs --- webui/css/messages.css | 61 ++++++++++++++++++++++++++++++++++++++++++ webui/js/messages.js | 32 ++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/webui/css/messages.css b/webui/css/messages.css index 176ae987d..118e826f7 100644 --- a/webui/css/messages.css +++ b/webui/css/messages.css @@ -803,3 +803,64 @@ .dark-mode .message { box-shadow: none; } + +/* =========================================== + Collapsible Standalone Messages + Shows ~10 lines with fade-out, expand button reveals full content + =========================================== */ + +.message.message-collapsible .message-body { + position: relative; + max-height: 15em !important; + overflow: hidden; + transition: max-height 0.3s ease-out; +} + +.message.message-collapsible .message-body::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 3em; + background: linear-gradient(transparent, var(--color-chat-background)); + pointer-events: none; + opacity: 1; + transition: opacity 0.3s ease-out; +} + +/* Expanded state */ +.message.message-collapsible.expanded .message-body { + max-height: none !important; +} + +.message.message-collapsible.expanded .message-body::after { + opacity: 0; +} + +/* No fade when content fits */ +.message.message-collapsible:not(.has-overflow) .message-body::after { + opacity: 0; +} + +/* Expand button - hidden by default, shown when overflow */ +.message.message-collapsible .expand-btn { + display: none; + background: transparent; + border: none; + color: var(--color-text-muted); + font-family: "Rubik", Arial, Helvetica, sans-serif; + font-size: var(--font-size-xs); + cursor: pointer; + padding: var(--spacing-xs) 0; + opacity: 0.7; + transition: opacity 0.15s ease; +} + +.message.message-collapsible .expand-btn:hover { + opacity: 1; +} + +.message.message-collapsible.has-overflow .expand-btn { + display: block; +} diff --git a/webui/js/messages.js b/webui/js/messages.js index 3337b5f09..d8a228a90 100644 --- a/webui/js/messages.js +++ b/webui/js/messages.js @@ -668,6 +668,22 @@ function drawStandaloneMessage({ mainClass, }); + // Collapsible: show ~10 lines with fade, expand button reveals full content + messageDiv.classList.add("message-collapsible"); + + const expandBtn = ensureChild(messageDiv, ".expand-btn", "button", "expand-btn"); + expandBtn.textContent = messageDiv.classList.contains("expanded") ? "Show less" : "Show more"; + expandBtn.onclick = () => { + messageDiv.classList.toggle("expanded"); + expandBtn.textContent = messageDiv.classList.contains("expanded") ? "Show less" : "Show more"; + }; + + // Detect overflow after render - CSS handles visibility based on .has-overflow class + requestAnimationFrame(() => { + const body = messageDiv.querySelector(".message-body"); + messageDiv.classList.toggle("has-overflow", body.scrollHeight > body.clientHeight); + }); + // Render action buttons: get/create container, clear, append const actionButtonsContainer = ensureChild( messageDiv, @@ -967,6 +983,22 @@ export function drawMessageResponse({ mainClass: "message-agent-response", }); + // Collapsible: show ~10 lines with fade, expand button reveals full content + messageDiv.classList.add("message-collapsible"); + + const expandBtn = ensureChild(messageDiv, ".expand-btn", "button", "expand-btn"); + expandBtn.textContent = messageDiv.classList.contains("expanded") ? "Show less" : "Show more"; + expandBtn.onclick = () => { + messageDiv.classList.toggle("expanded"); + expandBtn.textContent = messageDiv.classList.contains("expanded") ? "Show less" : "Show more"; + }; + + // Detect overflow after render - CSS handles visibility based on .has-overflow class + requestAnimationFrame(() => { + const body = messageDiv.querySelector(".message-body"); + messageDiv.classList.toggle("has-overflow", body.scrollHeight > body.clientHeight); + }); + // Render action buttons: get/create container, clear, append const responseText = String(content ?? ""); const responseActionButtons = responseText.trim() From d189156a1658726752d41118275b9c44c33a888b Mon Sep 17 00:00:00 2001 From: 3clyp50 Date: Tue, 27 Jan 2026 18:07:39 +0100 Subject: [PATCH 2/9] add copy btn to code blocks and tables --- .../action-buttons/simple-action-buttons.css | 22 ++++++-- webui/css/messages.css | 17 ++++++ webui/js/messages.js | 55 ++++++++++++++++--- 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/webui/components/messages/action-buttons/simple-action-buttons.css b/webui/components/messages/action-buttons/simple-action-buttons.css index b9d3279f1..83450cc5a 100644 --- a/webui/components/messages/action-buttons/simple-action-buttons.css +++ b/webui/components/messages/action-buttons/simple-action-buttons.css @@ -14,7 +14,8 @@ pointer-events: none; } -.step-action-buttons .action-button { +.step-action-buttons .action-button, +.action-button { display: flex; align-items: center; justify-content: center; @@ -24,7 +25,8 @@ cursor: pointer; } -.step-action-buttons .action-button .material-symbols-outlined { +.step-action-buttons .action-button .material-symbols-outlined, +.action-button .material-symbols-outlined { font-size: 0.85rem; line-height: 1; color: var(--color-message-text); @@ -32,18 +34,21 @@ transition: color 0.15s ease; } -.step-action-buttons .action-button:hover .material-symbols-outlined { +.step-action-buttons .action-button:hover .material-symbols-outlined, +.action-button:hover .material-symbols-outlined { color: var(--color-text); } /* Success state */ -.step-action-buttons .action-button.success .material-symbols-outlined { +.step-action-buttons .action-button.success .material-symbols-outlined, +.action-button.success .material-symbols-outlined { color: #4CAF50; font-variation-settings: 'FILL' 1, 'wght' 500, 'GRAD' 0, 'opsz' 20; } /* Error state */ -.step-action-buttons .action-button.error .material-symbols-outlined { +.step-action-buttons .action-button.error .material-symbols-outlined, +.action-button.error .material-symbols-outlined { color: var(--color-accent); font-variation-settings: 'FILL' 1, 'wght' 500, 'GRAD' 0, 'opsz' 20; } @@ -65,6 +70,13 @@ pointer-events: auto; } +/* Code blocks and tables: show copy button on hover */ +.device-pointer .code-block-wrapper:hover .step-action-buttons, +.device-pointer .message-markdown-table-wrap:hover .step-action-buttons { + opacity: 1; + pointer-events: auto; +} + /* =========================================== Touch devices - Always visible =========================================== */ diff --git a/webui/css/messages.css b/webui/css/messages.css index 176ae987d..30a9fffc6 100644 --- a/webui/css/messages.css +++ b/webui/css/messages.css @@ -732,6 +732,23 @@ border-radius: 0.3em; } +/* Code block and table copy buttons */ +.code-block-wrapper, +.message-markdown-table-wrap { + position: relative; +} + +.overlay-actions { + position: absolute; + top: 0; + right: 0; + background: var(--color-panel); + border-radius: var(--border-radius-sm); + z-index: 1; + padding: 2px !important; + gap: 0 !important; +} + .msg-content hr { border: 0; border-top: 1px solid var(--color-border); diff --git a/webui/js/messages.js b/webui/js/messages.js index 3337b5f09..e96f96bb8 100644 --- a/webui/js/messages.js +++ b/webui/js/messages.js @@ -1883,16 +1883,57 @@ function convertPathsToLinks(str) { .join(""); } +// markdown render helpers // + +// wraps an element with a container div +const wrapElement = (el, className) => { + const wrapper = document.createElement("div"); + wrapper.className = className; + el.parentNode.insertBefore(wrapper, el); + wrapper.appendChild(el); + return wrapper; +}; + +// data extractors +const extractTableTSV = (table) => + [...table.rows] + .map((row) => + [...row.cells] + .map((cell) => cell.textContent.replace(/\t/g, " ").replace(/\n/g, " ")) + .join("\t"), + ) + .join("\n"); + function adjustMarkdownRender(element) { // find all tables in the element - const elements = element.querySelectorAll("table"); + const tables = element.querySelectorAll("table"); + tables.forEach((el) => { + if (el.parentNode.classList.contains("message-markdown-table-wrap")) + return; + const wrapper = wrapElement(el, "message-markdown-table-wrap"); + + // create copy button directly + const copyBtn = createActionButton("copy", "", () => + copyToClipboard(extractTableTSV(el)) + ); + copyBtn.classList.add("step-action-buttons", "overlay-actions"); + wrapper.appendChild(copyBtn); + }); - // wrap each with a div with class message-markdown-table-wrap - elements.forEach((el) => { - const wrapper = document.createElement("div"); - wrapper.className = "message-markdown-table-wrap"; - el.parentNode.insertBefore(wrapper, el); - wrapper.appendChild(el); + // find all code blocks + // we select the code element to ensure valid targets, then wrap the parent pre + const codeElements = element.querySelectorAll("pre > code"); + codeElements.forEach((code) => { + const pre = code.parentNode; + if (pre.parentNode.classList.contains("code-block-wrapper")) return; + const wrapper = wrapElement(pre, "code-block-wrapper"); + + // create copy button directly + const copyBtn = createActionButton("copy", "", () => + copyToClipboard(code.textContent) + ); + copyBtn.classList.add("step-action-buttons", "overlay-actions"); + wrapper.appendChild(copyBtn); }); // find all images From e14c527126751fb9ca52d53486d9161bd9f8b6b5 Mon Sep 17 00:00:00 2001 From: 3clyp50 Date: Tue, 27 Jan 2026 18:16:46 +0100 Subject: [PATCH 3/9] remove double wrap guards --- webui/js/messages.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/webui/js/messages.js b/webui/js/messages.js index e96f96bb8..c254f5c99 100644 --- a/webui/js/messages.js +++ b/webui/js/messages.js @@ -1908,11 +1908,7 @@ function adjustMarkdownRender(element) { // find all tables in the element const tables = element.querySelectorAll("table"); tables.forEach((el) => { - if (el.parentNode.classList.contains("message-markdown-table-wrap")) - return; const wrapper = wrapElement(el, "message-markdown-table-wrap"); - - // create copy button directly const copyBtn = createActionButton("copy", "", () => copyToClipboard(extractTableTSV(el)) ); @@ -1921,14 +1917,10 @@ function adjustMarkdownRender(element) { }); // find all code blocks - // we select the code element to ensure valid targets, then wrap the parent pre const codeElements = element.querySelectorAll("pre > code"); codeElements.forEach((code) => { const pre = code.parentNode; - if (pre.parentNode.classList.contains("code-block-wrapper")) return; const wrapper = wrapElement(pre, "code-block-wrapper"); - - // create copy button directly const copyBtn = createActionButton("copy", "", () => copyToClipboard(code.textContent) ); From dcaf1331068061bed81a68de2a717a50bf67e61e Mon Sep 17 00:00:00 2001 From: 3clyp50 Date: Tue, 27 Jan 2026 22:07:05 +0100 Subject: [PATCH 4/9] prevent nested selection; css polish --- .../action-buttons/simple-action-buttons.css | 13 +++++++--- webui/css/messages.css | 17 ------------ webui/js/messages.js | 26 +++++++++++-------- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/webui/components/messages/action-buttons/simple-action-buttons.css b/webui/components/messages/action-buttons/simple-action-buttons.css index 83450cc5a..f914ef8ba 100644 --- a/webui/components/messages/action-buttons/simple-action-buttons.css +++ b/webui/components/messages/action-buttons/simple-action-buttons.css @@ -59,20 +59,25 @@ width: 100%; } +.code-block-wrapper > .step-action-buttons, +.message-markdown-table-wrap > .step-action-buttons { + padding-top: 0; +} + /* =========================================== Hover behavior - Pointer devices =========================================== */ -.device-pointer .message-user:hover .step-action-buttons, -.device-pointer .message-agent-response:hover .step-action-buttons, +.device-pointer .message-user:hover > .step-action-buttons, +.device-pointer .message-agent-response:hover > .step-action-buttons, .device-pointer .process-step:hover > .process-step-detail .step-action-buttons { opacity: 1; pointer-events: auto; } /* Code blocks and tables: show copy button on hover */ -.device-pointer .code-block-wrapper:hover .step-action-buttons, -.device-pointer .message-markdown-table-wrap:hover .step-action-buttons { +.device-pointer .code-block-wrapper:hover > .step-action-buttons, +.device-pointer .message-markdown-table-wrap:hover > .step-action-buttons { opacity: 1; pointer-events: auto; } diff --git a/webui/css/messages.css b/webui/css/messages.css index 30a9fffc6..176ae987d 100644 --- a/webui/css/messages.css +++ b/webui/css/messages.css @@ -732,23 +732,6 @@ border-radius: 0.3em; } -/* Code block and table copy buttons */ -.code-block-wrapper, -.message-markdown-table-wrap { - position: relative; -} - -.overlay-actions { - position: absolute; - top: 0; - right: 0; - background: var(--color-panel); - border-radius: var(--border-radius-sm); - z-index: 1; - padding: 2px !important; - gap: 0 !important; -} - .msg-content hr { border: 0; border-top: 1px solid var(--color-border); diff --git a/webui/js/messages.js b/webui/js/messages.js index c254f5c99..348248fa6 100644 --- a/webui/js/messages.js +++ b/webui/js/messages.js @@ -671,7 +671,7 @@ function drawStandaloneMessage({ // Render action buttons: get/create container, clear, append const actionButtonsContainer = ensureChild( messageDiv, - ".step-action-buttons", + ":scope > .step-action-buttons", "div", "step-action-buttons", ); @@ -975,9 +975,10 @@ export function drawMessageResponse({ createActionButton("copy", "", () => copyToClipboard(responseText)), ].filter(Boolean) : []; + // Look for direct child only to avoid finding nested code block buttons const actionButtonsContainer = ensureChild( messageDiv, - ".step-action-buttons", + ":scope > .step-action-buttons", "div", "step-action-buttons", ); @@ -1114,9 +1115,10 @@ export function drawMessageUser({ createActionButton("copy", "", () => copyToClipboard(userText)), ].filter(Boolean) : []; + // Look for direct child only to avoid finding nested code block buttons const actionButtonsContainer = ensureChild( messageDiv, - ".step-action-buttons", + ":scope > .step-action-buttons", "div", "step-action-buttons", ); @@ -1909,11 +1911,12 @@ function adjustMarkdownRender(element) { const tables = element.querySelectorAll("table"); tables.forEach((el) => { const wrapper = wrapElement(el, "message-markdown-table-wrap"); - const copyBtn = createActionButton("copy", "", () => - copyToClipboard(extractTableTSV(el)) + const actionsDiv = document.createElement("div"); + actionsDiv.className = "step-action-buttons"; + actionsDiv.appendChild( + createActionButton("copy", "", () => copyToClipboard(extractTableTSV(el))) ); - copyBtn.classList.add("step-action-buttons", "overlay-actions"); - wrapper.appendChild(copyBtn); + wrapper.appendChild(actionsDiv); }); // find all code blocks @@ -1921,11 +1924,12 @@ function adjustMarkdownRender(element) { codeElements.forEach((code) => { const pre = code.parentNode; const wrapper = wrapElement(pre, "code-block-wrapper"); - const copyBtn = createActionButton("copy", "", () => - copyToClipboard(code.textContent) + const actionsDiv = document.createElement("div"); + actionsDiv.className = "step-action-buttons"; + actionsDiv.appendChild( + createActionButton("copy", "", () => copyToClipboard(code.textContent)) ); - copyBtn.classList.add("step-action-buttons", "overlay-actions"); - wrapper.appendChild(copyBtn); + wrapper.appendChild(actionsDiv); }); // find all images From 8a782914c977ac9fbfb50cc3b30487060132c0e7 Mon Sep 17 00:00:00 2001 From: 3clyp50 Date: Tue, 27 Jan 2026 22:14:54 +0100 Subject: [PATCH 5/9] limit :scope to messageResponse --- webui/js/messages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webui/js/messages.js b/webui/js/messages.js index 348248fa6..767d93884 100644 --- a/webui/js/messages.js +++ b/webui/js/messages.js @@ -671,7 +671,7 @@ function drawStandaloneMessage({ // Render action buttons: get/create container, clear, append const actionButtonsContainer = ensureChild( messageDiv, - ":scope > .step-action-buttons", + ".step-action-buttons", "div", "step-action-buttons", ); @@ -1118,7 +1118,7 @@ export function drawMessageUser({ // Look for direct child only to avoid finding nested code block buttons const actionButtonsContainer = ensureChild( messageDiv, - ":scope > .step-action-buttons", + ".step-action-buttons", "div", "step-action-buttons", ); From 7c6482aa709b1b4d8cfaf6e1003e2453a44f71d1 Mon Sep 17 00:00:00 2001 From: 3clyp50 Date: Tue, 27 Jan 2026 22:45:30 +0100 Subject: [PATCH 6/9] chore: comments cleanup --- webui/js/messages.js | 1 - 1 file changed, 1 deletion(-) diff --git a/webui/js/messages.js b/webui/js/messages.js index 767d93884..f5ca611a3 100644 --- a/webui/js/messages.js +++ b/webui/js/messages.js @@ -1115,7 +1115,6 @@ export function drawMessageUser({ createActionButton("copy", "", () => copyToClipboard(userText)), ].filter(Boolean) : []; - // Look for direct child only to avoid finding nested code block buttons const actionButtonsContainer = ensureChild( messageDiv, ".step-action-buttons", From 5fdc0bdef470992d3f6bc5267490d345200b1353 Mon Sep 17 00:00:00 2001 From: frdel <38891707+frdel@users.noreply.github.com> Date: Wed, 28 Jan 2026 11:18:23 +0100 Subject: [PATCH 7/9] thoughts alignment in kvps --- webui/css/messages.css | 4 ++++ webui/js/messages.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/webui/css/messages.css b/webui/css/messages.css index 118e826f7..c2a04b5b9 100644 --- a/webui/css/messages.css +++ b/webui/css/messages.css @@ -438,6 +438,10 @@ font-size: 0.75rem; } +.kvps-val p{ + margin:0; +} + .kvps-val pre { white-space: pre-wrap; /* keep \n, collapse no spaces, allow wrapping */ word-break: break-word; /* optional – forces really long “words” to break */ diff --git a/webui/js/messages.js b/webui/js/messages.js index e615f76c2..84079160b 100644 --- a/webui/js/messages.js +++ b/webui/js/messages.js @@ -1778,7 +1778,7 @@ function drawKvpsIncremental(container, kvps, latex) { imageViewerStore.open(imgElement.src, { refreshInterval: 1000 }); }); } else { - const span = document.createElement("span"); + const span = document.createElement("p"); span.innerHTML = convertHTML(value); tdiv.appendChild(span); From 2463b857490c15a84de2ca105ddfc9210afc74f3 Mon Sep 17 00:00:00 2001 From: 3clyp50 Date: Tue, 27 Jan 2026 12:09:37 +0100 Subject: [PATCH 8/9] generic modals with kvps for step detail --- .../process-step-detail.html | 370 +----------------- .../process-step-detail/step-detail-store.js | 1 - webui/js/messages.js | 34 +- 3 files changed, 47 insertions(+), 358 deletions(-) diff --git a/webui/components/modals/process-step-detail/process-step-detail.html b/webui/components/modals/process-step-detail/process-step-detail.html index a49daeb71..4a162db64 100644 --- a/webui/components/modals/process-step-detail/process-step-detail.html +++ b/webui/components/modals/process-step-detail/process-step-detail.html @@ -19,14 +19,10 @@ :class="$store.stepDetail.selectedStepForDetail?.statusClass || ''" x-text="$store.stepDetail.selectedStepForDetail?.statusCode || ''"> - - - - - - - -