collapsible standalone msgs

3clyp50 committed Jan 27, 2026 at 14:39 UTC 700d0041cf817817d0c5b1b1aa8c83410113ab88
2 files changed +93
webui/css/messages.css
+61
@@ -803,3 +803,64 @@
803 .dark-mode .message {
804 box-shadow: none;
805 }
806 +
807 +/* ===========================================
808 + Collapsible Standalone Messages
809 + Shows ~10 lines with fade-out, expand button reveals full content
810 + =========================================== */
811 +
812 +.message.message-collapsible .message-body {
813 + position: relative;
814 + max-height: 15em !important;
815 + overflow: hidden;
816 + transition: max-height 0.3s ease-out;
817 +}
818 +
819 +.message.message-collapsible .message-body::after {
820 + content: "";
821 + position: absolute;
822 + bottom: 0;
823 + left: 0;
824 + right: 0;
825 + height: 3em;
826 + background: linear-gradient(transparent, var(--color-chat-background));
827 + pointer-events: none;
828 + opacity: 1;
829 + transition: opacity 0.3s ease-out;
830 +}
831 +
832 +/* Expanded state */
833 +.message.message-collapsible.expanded .message-body {
834 + max-height: none !important;
835 +}
836 +
837 +.message.message-collapsible.expanded .message-body::after {
838 + opacity: 0;
839 +}
840 +
841 +/* No fade when content fits */
842 +.message.message-collapsible:not(.has-overflow) .message-body::after {
843 + opacity: 0;
844 +}
845 +
846 +/* Expand button - hidden by default, shown when overflow */
847 +.message.message-collapsible .expand-btn {
848 + display: none;
849 + background: transparent;
850 + border: none;
851 + color: var(--color-text-muted);
852 + font-family: "Rubik", Arial, Helvetica, sans-serif;
853 + font-size: var(--font-size-xs);
854 + cursor: pointer;
855 + padding: var(--spacing-xs) 0;
856 + opacity: 0.7;
857 + transition: opacity 0.15s ease;
858 +}
859 +
860 +.message.message-collapsible .expand-btn:hover {
861 + opacity: 1;
862 +}
863 +
864 +.message.message-collapsible.has-overflow .expand-btn {
865 + display: block;
866 +}
webui/js/messages.js
+32
@@ -668,6 +668,22 @@ function drawStandaloneMessage({
668 mainClass,
669 });
670
671 + // Collapsible: show ~10 lines with fade, expand button reveals full content
672 + messageDiv.classList.add("message-collapsible");
673 +
674 + const expandBtn = ensureChild(messageDiv, ".expand-btn", "button", "expand-btn");
675 + expandBtn.textContent = messageDiv.classList.contains("expanded") ? "Show less" : "Show more";
676 + expandBtn.onclick = () => {
677 + messageDiv.classList.toggle("expanded");
678 + expandBtn.textContent = messageDiv.classList.contains("expanded") ? "Show less" : "Show more";
679 + };
680 +
681 + // Detect overflow after render - CSS handles visibility based on .has-overflow class
682 + requestAnimationFrame(() => {
683 + const body = messageDiv.querySelector(".message-body");
684 + messageDiv.classList.toggle("has-overflow", body.scrollHeight > body.clientHeight);
685 + });
686 +
687 // Render action buttons: get/create container, clear, append
688 const actionButtonsContainer = ensureChild(
689 messageDiv,
@@ -967,6 +983,22 @@ export function drawMessageResponse({
983 mainClass: "message-agent-response",
984 });
985
986 + // Collapsible: show ~10 lines with fade, expand button reveals full content
987 + messageDiv.classList.add("message-collapsible");
988 +
989 + const expandBtn = ensureChild(messageDiv, ".expand-btn", "button", "expand-btn");
990 + expandBtn.textContent = messageDiv.classList.contains("expanded") ? "Show less" : "Show more";
991 + expandBtn.onclick = () => {
992 + messageDiv.classList.toggle("expanded");
993 + expandBtn.textContent = messageDiv.classList.contains("expanded") ? "Show less" : "Show more";
994 + };
995 +
996 + // Detect overflow after render - CSS handles visibility based on .has-overflow class
997 + requestAnimationFrame(() => {
998 + const body = messageDiv.querySelector(".message-body");
999 + messageDiv.classList.toggle("has-overflow", body.scrollHeight > body.clientHeight);
1000 + });
1001 +
1002 // Render action buttons: get/create container, clear, append
1003 const responseText = String(content ?? "");
1004 const responseActionButtons = responseText.trim()