collapsible responses rework

This commit is contained in:
3clyp50 2026-01-29 09:35:21 +01:00
parent d3f29fb99c
commit d912caae61
3 changed files with 108 additions and 102 deletions

View file

@ -14,6 +14,24 @@
pointer-events: none;
}
.step-action-buttons .expand-btn {
display: inline-flex;
align-items: center;
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;
}
.step-action-buttons .expand-btn:hover {
opacity: 1;
}
.step-action-buttons .action-button,
.action-button {
display: flex;
@ -53,6 +71,55 @@
font-variation-settings: 'FILL' 1, 'wght' 500, 'GRAD' 0, 'opsz' 20;
}
.show-more-btn,
.show-less-btn {
display: none;
}
.message.message-collapsible.has-overflow:not(.expanded) > .step-action-buttons {
opacity: 1;
pointer-events: auto;
}
.message.message-collapsible.has-overflow:not(.expanded)
> .step-action-buttons
.show-more-btn {
display: inline-flex;
pointer-events: auto;
}
.device-pointer
.message.message-collapsible.has-overflow:not(.expanded)
> .step-action-buttons
.action-button {
opacity: 0;
pointer-events: none;
}
.device-pointer
.message.message-collapsible.has-overflow:not(.expanded):hover
> .step-action-buttons
.action-button {
opacity: 1;
pointer-events: auto;
}
.device-pointer
.message.message-collapsible.has-overflow.expanded:hover
> .step-action-buttons
.show-less-btn {
display: inline-flex;
pointer-events: auto;
}
.device-touch
.message.message-collapsible.has-overflow.expanded
> .step-action-buttons
.show-less-btn {
display: inline-flex;
pointer-events: auto;
}
/* User messages: right-aligned */
.message-user .step-action-buttons {
justify-content: flex-end;
@ -70,6 +137,7 @@
.device-pointer .message-user:hover > .step-action-buttons,
.device-pointer .message-agent-response:hover > .step-action-buttons,
.device-pointer .message.message-collapsible.expanded:hover > .step-action-buttons,
.device-pointer .process-step:hover > .process-step-detail .step-action-buttons {
opacity: 1;
pointer-events: auto;

View file

@ -846,24 +846,3 @@
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;
}

View file

@ -518,45 +518,8 @@ 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,
".step-action-buttons",
"div",
"step-action-buttons",
);
actionButtonsContainer.textContent = "";
(actionButtons || [])
.filter(Boolean)
.forEach((button) => actionButtonsContainer.appendChild(button));
// Collapsible with action buttons
setupCollapsible(messageDiv, ".step-action-buttons", false, actionButtons);
return container;
}
@ -581,8 +544,10 @@ export function _drawMessage({
messageContainer.appendChild(messageDiv);
}
// Update message classes
messageDiv.className = `message ${mainClass} ${messageClasses.join(" ")}`;
// Update message classes (preserve collapsible state)
const preserve = ["message-collapsible", "expanded", "has-overflow"]
.filter((c) => messageDiv.classList.contains(c)).join(" ");
messageDiv.className = `message ${mainClass} ${messageClasses.join(" ")} ${preserve}`;
// Handle heading (important for error/rate_limit messages that show context)
if (heading) {
@ -855,35 +820,7 @@ 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
// Collapsible with action buttons
const responseText = String(content ?? "");
const responseActionButtons = responseText.trim()
? [
@ -891,17 +828,7 @@ 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,
":scope > .step-action-buttons",
"div",
"step-action-buttons",
);
actionButtonsContainer.textContent = "";
responseActionButtons.forEach((button) =>
actionButtonsContainer.appendChild(button),
);
setupCollapsible(messageDiv, ":scope > .step-action-buttons", !isMassRender(), responseActionButtons);
if (group) updateProcessGroupHeader(group);
@ -2199,6 +2126,38 @@ function ensureChild(parent, selector, tagName, ...classNames) {
return el;
}
// Setup collapsible message with expand button and action buttons
function setupCollapsible(messageDiv, containerSelector, initialExpanded, actionButtons = []) {
messageDiv.classList.add("message-collapsible");
messageDiv.classList.toggle("expanded", initialExpanded);
const container = ensureChild(messageDiv, containerSelector, "div", "step-action-buttons");
container.textContent = "";
const btn = ensureChild(container, ".expand-btn", "button", "expand-btn");
const syncBtn = () => {
const exp = messageDiv.classList.contains("expanded");
btn.textContent = exp ? "Show less" : "Show more";
btn.classList.toggle("show-less-btn", exp);
btn.classList.toggle("show-more-btn", !exp);
};
syncBtn();
btn.onclick = () => { messageDiv.classList.toggle("expanded"); syncBtn(); };
actionButtons.filter(Boolean).forEach((b) => container.appendChild(b));
// Detect overflow after render (skip if already detected to avoid scroll disruption)
if (!messageDiv.classList.contains("has-overflow")) {
requestAnimationFrame(() => {
const body = messageDiv.querySelector(".message-body");
const wasExp = messageDiv.classList.contains("expanded");
messageDiv.classList.remove("expanded");
messageDiv.classList.toggle("has-overflow", body?.scrollHeight > body?.clientHeight);
messageDiv.classList.toggle("expanded", wasExp);
});
}
}
// returns true if this is the initial render of a chat eg. when reloading window, switching chat or catching up after a break
// returns false when already in a rendered chat and adding messages regurarly
function isMassRender() {