mirror of
https://github.com/oobabooga/text-generation-webui.git
synced 2026-03-17 19:04:39 +01:00
UI: Optimize syntax highlighting and autoscroll by moving from MutationObserver to morphdom updates
This commit is contained in:
parent
aab2596d29
commit
d4c22ced83
|
|
@ -269,6 +269,34 @@ function removeLastClick() {
|
|||
document.getElementById("Remove-last").click();
|
||||
}
|
||||
|
||||
function autoScrollToBottom() {
|
||||
if (!window.isScrolled) {
|
||||
const chatParent = document.getElementById("chat")?.parentNode?.parentNode?.parentNode;
|
||||
if (chatParent) {
|
||||
const maxScroll = chatParent.scrollHeight - chatParent.clientHeight;
|
||||
if (maxScroll > 0 && chatParent.scrollTop < maxScroll - 1) {
|
||||
chatParent.scrollTop = maxScroll;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateInstructPadding() {
|
||||
const chatElement = document.getElementById("chat");
|
||||
if (chatElement && chatElement.getAttribute("data-mode") === "instruct") {
|
||||
const messagesContainer = chatElement.querySelector(".messages");
|
||||
const lastChild = messagesContainer?.lastElementChild;
|
||||
const prevSibling = lastChild?.previousElementSibling;
|
||||
if (lastChild && prevSibling) {
|
||||
let bufferHeight = Math.max(0, Math.max(window.innerHeight - 128 - 84, window.innerHeight - prevSibling.offsetHeight - 84) - lastChild.offsetHeight);
|
||||
if (window.innerWidth <= 924) {
|
||||
bufferHeight = Math.max(0, bufferHeight - 32);
|
||||
}
|
||||
messagesContainer.style.paddingBottom = `${bufferHeight}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pendingMorphdomData = null;
|
||||
let morphdomRafId = null;
|
||||
|
||||
|
|
@ -373,10 +401,23 @@ function applyMorphdomUpdate(data) {
|
|||
}
|
||||
);
|
||||
|
||||
// Syntax highlighting and LaTeX
|
||||
if (window.doSyntaxHighlighting) {
|
||||
window.doSyntaxHighlighting();
|
||||
}
|
||||
|
||||
// Auto-scroll runs both before and after padding update.
|
||||
// Before: so content growth isn't hidden by padding absorption.
|
||||
// After: so padding-added space is also scrolled into view.
|
||||
autoScrollToBottom();
|
||||
updateInstructPadding();
|
||||
autoScrollToBottom();
|
||||
|
||||
// Add toggle listeners for new blocks
|
||||
queryScope.querySelectorAll(".thinking-block").forEach(block => {
|
||||
if (!block._hasToggleListener) {
|
||||
block.addEventListener("toggle", function(e) {
|
||||
const wasScrolled = window.isScrolled;
|
||||
if (this.open) {
|
||||
const content = this.querySelector(".thinking-content");
|
||||
if (content) {
|
||||
|
|
@ -385,6 +426,10 @@ function applyMorphdomUpdate(data) {
|
|||
}, 0);
|
||||
}
|
||||
}
|
||||
updateInstructPadding();
|
||||
// Restore scroll state so the browser's layout adjustment
|
||||
// from the toggle doesn't disable auto-scroll
|
||||
window.isScrolled = wasScrolled;
|
||||
});
|
||||
block._hasToggleListener = true;
|
||||
}
|
||||
|
|
|
|||
121
js/main.js
121
js/main.js
|
|
@ -147,6 +147,7 @@ window.isScrolled = false;
|
|||
let scrollTimeout;
|
||||
let lastScrollTop = 0;
|
||||
let lastScrollHeight = 0;
|
||||
let lastClientHeight = 0;
|
||||
|
||||
targetElement.addEventListener("scroll", function() {
|
||||
let diff = targetElement.scrollHeight - targetElement.clientHeight;
|
||||
|
|
@ -159,11 +160,12 @@ targetElement.addEventListener("scroll", function() {
|
|||
|
||||
if(isAtBottomNow) {
|
||||
window.isScrolled = false;
|
||||
} else if (targetElement.scrollTop < lastScrollTop && targetElement.scrollHeight >= lastScrollHeight) {
|
||||
} else if (targetElement.scrollTop < lastScrollTop && targetElement.scrollHeight >= lastScrollHeight && targetElement.clientHeight <= lastClientHeight) {
|
||||
window.isScrolled = true;
|
||||
}
|
||||
lastScrollTop = targetElement.scrollTop;
|
||||
lastScrollHeight = targetElement.scrollHeight;
|
||||
lastClientHeight = targetElement.clientHeight;
|
||||
|
||||
// Clear previous timeout and set new one
|
||||
clearTimeout(scrollTimeout);
|
||||
|
|
@ -174,14 +176,7 @@ targetElement.addEventListener("scroll", function() {
|
|||
});
|
||||
|
||||
// Create a MutationObserver instance
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
// Check if this is just the scrolling class being toggled
|
||||
const isScrollingClassOnly = mutations.every(mutation =>
|
||||
mutation.type === "attributes" &&
|
||||
mutation.attributeName === "class" &&
|
||||
mutation.target === targetElement
|
||||
);
|
||||
|
||||
const observer = new MutationObserver(function() {
|
||||
if (targetElement.classList.contains("_generating")) {
|
||||
typing.parentNode.classList.add("visible-dots");
|
||||
document.getElementById("stop").style.display = "flex";
|
||||
|
|
@ -191,44 +186,11 @@ const observer = new MutationObserver(function(mutations) {
|
|||
document.getElementById("stop").style.display = "none";
|
||||
document.getElementById("Generate").style.display = "flex";
|
||||
}
|
||||
|
||||
doSyntaxHighlighting();
|
||||
|
||||
if (!window.isScrolled && !isScrollingClassOnly) {
|
||||
const maxScroll = targetElement.scrollHeight - targetElement.clientHeight;
|
||||
if (maxScroll > 0 && targetElement.scrollTop < maxScroll - 1) {
|
||||
targetElement.scrollTop = maxScroll;
|
||||
}
|
||||
}
|
||||
|
||||
const chatElement = document.getElementById("chat");
|
||||
if (chatElement && chatElement.getAttribute("data-mode") === "instruct") {
|
||||
const messagesContainer = chatElement.querySelector(".messages");
|
||||
const lastChild = messagesContainer?.lastElementChild;
|
||||
const prevSibling = lastChild?.previousElementSibling;
|
||||
if (lastChild && prevSibling) {
|
||||
// Add padding to the messages container to create room for the last message.
|
||||
// The purpose of this is to avoid constant scrolling during streaming in
|
||||
// instruct mode.
|
||||
let bufferHeight = Math.max(0, Math.max(window.innerHeight - 128 - 84, window.innerHeight - prevSibling.offsetHeight - 84) - lastChild.offsetHeight);
|
||||
|
||||
// Subtract header height when screen width is <= 924px
|
||||
if (window.innerWidth <= 924) {
|
||||
bufferHeight = Math.max(0, bufferHeight - 32);
|
||||
}
|
||||
|
||||
messagesContainer.style.paddingBottom = `${bufferHeight}px`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Configure the observer to watch for changes in the subtree and attributes
|
||||
// Only watch for attribute changes on targetElement (e.g. _generating class)
|
||||
const config = {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
attributeOldValue: true,
|
||||
characterDataOldValue: true
|
||||
attributes: true
|
||||
};
|
||||
|
||||
// Start observing the target element
|
||||
|
|
@ -247,55 +209,50 @@ function isElementVisibleOnScreen(element) {
|
|||
);
|
||||
}
|
||||
|
||||
function doSyntaxHighlighting() {
|
||||
window.doSyntaxHighlighting = function() {
|
||||
const messageBodies = document.getElementById("chat").querySelectorAll(".message-body");
|
||||
|
||||
if (messageBodies.length > 0) {
|
||||
observer.disconnect();
|
||||
let hasSeenVisible = false;
|
||||
|
||||
try {
|
||||
let hasSeenVisible = false;
|
||||
// Go from last message to first
|
||||
for (let i = messageBodies.length - 1; i >= 0; i--) {
|
||||
const messageBody = messageBodies[i];
|
||||
|
||||
// Go from last message to first
|
||||
for (let i = messageBodies.length - 1; i >= 0; i--) {
|
||||
const messageBody = messageBodies[i];
|
||||
if (isElementVisibleOnScreen(messageBody)) {
|
||||
hasSeenVisible = true;
|
||||
|
||||
if (isElementVisibleOnScreen(messageBody)) {
|
||||
hasSeenVisible = true;
|
||||
// Handle both code and math in a single pass through each message
|
||||
const codeBlocks = messageBody.querySelectorAll("pre code:not([data-highlighted])");
|
||||
codeBlocks.forEach((codeBlock) => {
|
||||
hljs.highlightElement(codeBlock);
|
||||
codeBlock.setAttribute("data-highlighted", "true");
|
||||
codeBlock.classList.add("pretty_scrollbar");
|
||||
});
|
||||
|
||||
// Handle both code and math in a single pass through each message
|
||||
const codeBlocks = messageBody.querySelectorAll("pre code:not([data-highlighted])");
|
||||
codeBlocks.forEach((codeBlock) => {
|
||||
hljs.highlightElement(codeBlock);
|
||||
codeBlock.setAttribute("data-highlighted", "true");
|
||||
codeBlock.classList.add("pretty_scrollbar");
|
||||
});
|
||||
|
||||
// Only render math in visible elements
|
||||
const mathContainers = messageBody.querySelectorAll("p, span, li, td, th, h1, h2, h3, h4, h5, h6, blockquote, figcaption, caption, dd, dt");
|
||||
mathContainers.forEach(container => {
|
||||
if (isElementVisibleOnScreen(container)) {
|
||||
renderMathInElement(container, {
|
||||
delimiters: [
|
||||
{ left: "$$", right: "$$", display: true },
|
||||
{ left: "$", right: "$", display: false },
|
||||
{ left: "\\(", right: "\\)", display: false },
|
||||
{ left: "\\[", right: "\\]", display: true },
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (hasSeenVisible) {
|
||||
// We've seen visible messages but this one is not visible
|
||||
// Since we're going from last to first, we can break
|
||||
break;
|
||||
}
|
||||
// Only render math in visible elements
|
||||
const mathContainers = messageBody.querySelectorAll("p, span, li, td, th, h1, h2, h3, h4, h5, h6, blockquote, figcaption, caption, dd, dt");
|
||||
mathContainers.forEach(container => {
|
||||
if (isElementVisibleOnScreen(container)) {
|
||||
renderMathInElement(container, {
|
||||
delimiters: [
|
||||
{ left: "$$", right: "$$", display: true },
|
||||
{ left: "$", right: "$", display: false },
|
||||
{ left: "\\(", right: "\\)", display: false },
|
||||
{ left: "\\[", right: "\\]", display: true },
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (hasSeenVisible) {
|
||||
// We've seen visible messages but this one is not visible
|
||||
// Since we're going from last to first, we can break
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
observer.observe(targetElement, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
const doSyntaxHighlighting = window.doSyntaxHighlighting;
|
||||
|
||||
//------------------------------------------------
|
||||
// Add some scrollbars
|
||||
|
|
|
|||
Loading…
Reference in a new issue