text-generation-webui-mirror/js/global_scope_js.js
Th-Underscore bc0df8a518
UI: Add message versioning and navigation (integrated boogaplus extension)
- Created a new module (message_versioning.py) for managing message versioning logic and data storage
- Added relevant Gradio communication in ui_chat.py
- Updated chat.py to append messages to versioning history and other history-related operations
- Updated HTML generation to include message version navigation elements
- Implemented JavaScript functions for handling message version navigation, selection, and history management
- Added relevant CSS styling (handling all three message classes is a little messy, but changing the original class structure might be problematic)

Notes:
- Doesn't always update on first load via `shared.gradio['unique_id'].select()`
- No go-to-first or go-to-last nav arrows
- Some of the methods and variables are... long-winded
- Have only tested with a couple extensions, but this _shouldn't_ break anything and should be completely backwards-compatible
- Haven't put any documentation for controls (Ctrl+Left/Right Arrow for navigation, Ctrl+Up/Down Arrow as an alternative for selection)
- The original footer buttons aren't set to full opacity when the message is selected, only the new nav container
- Output file extension is `.boogaplus` cuz it's cool
- Tried using `None` as a placeholder in the history storage dict (`null` in the JSON) instead of always saving the text to save space, but sometimes leads to client-server issues
2025-05-04 14:57:19 -04:00

128 lines
4.4 KiB
JavaScript

function copyToClipboard(element) {
if (!element) return;
const messageElement = element.closest(".message, .user-message, .assistant-message");
if (!messageElement) return;
const rawText = messageElement.getAttribute("data-raw");
if (!rawText) return;
navigator.clipboard.writeText(rawText).then(function() {
const originalSvg = element.innerHTML;
element.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-green-500 dark:text-green-400\"><path d=\"M5 12l5 5l10 -10\"></path></svg>";
setTimeout(() => {
element.innerHTML = originalSvg;
}, 1000);
}).catch(function(err) {
console.error("Failed to copy text: ", err);
});
}
function regenerateClick() {
document.getElementById("Regenerate").click();
}
function continueClick() {
document.getElementById("Continue").click();
}
function removeLastClick() {
document.getElementById("Remove-last").click();
}
// === History navigation handled in main.js === //
function handleMorphdomUpdate(text) {
// Track open blocks
const openBlocks = new Set();
document.querySelectorAll(".thinking-block").forEach(block => {
const blockId = block.getAttribute("data-block-id");
// If block exists and is open, add to open set
if (blockId && block.hasAttribute("open")) {
openBlocks.add(blockId);
}
});
// Store scroll positions for any open blocks
const scrollPositions = {};
document.querySelectorAll(".thinking-block[open]").forEach(block => {
const content = block.querySelector(".thinking-content");
const blockId = block.getAttribute("data-block-id");
if (content && blockId) {
const isAtBottom = Math.abs((content.scrollHeight - content.scrollTop) - content.clientHeight) < 5;
scrollPositions[blockId] = {
position: content.scrollTop,
isAtBottom: isAtBottom
};
}
});
morphdom(
document.getElementById("chat").parentNode,
"<div class=\"prose svelte-1ybaih5\">" + text + "</div>",
{
onBeforeElUpdated: function(fromEl, toEl) {
// Preserve code highlighting
if (fromEl.tagName === "PRE" && fromEl.querySelector("code[data-highlighted]")) {
const fromCode = fromEl.querySelector("code");
const toCode = toEl.querySelector("code");
if (fromCode && toCode && fromCode.textContent === toCode.textContent) {
toEl.className = fromEl.className;
toEl.innerHTML = fromEl.innerHTML;
return false;
}
}
// For thinking blocks, assume closed by default
if (fromEl.classList && fromEl.classList.contains("thinking-block") &&
toEl.classList && toEl.classList.contains("thinking-block")) {
const blockId = toEl.getAttribute("data-block-id");
// Remove open attribute by default
toEl.removeAttribute("open");
// If this block was explicitly opened by user, keep it open
if (blockId && openBlocks.has(blockId)) {
toEl.setAttribute("open", "");
}
}
return !fromEl.isEqualNode(toEl);
},
onElUpdated: function(el) {
// Restore scroll positions for open thinking blocks
if (el.classList && el.classList.contains("thinking-block") && el.hasAttribute("open")) {
const blockId = el.getAttribute("data-block-id");
const content = el.querySelector(".thinking-content");
if (content && blockId && scrollPositions[blockId]) {
setTimeout(() => {
if (scrollPositions[blockId].isAtBottom) {
content.scrollTop = content.scrollHeight;
} else {
content.scrollTop = scrollPositions[blockId].position;
}
}, 0);
}
}
}
}
);
// Add toggle listeners for new blocks
document.querySelectorAll(".thinking-block").forEach(block => {
if (!block._hasToggleListener) {
block.addEventListener("toggle", function(e) {
if (this.open) {
const content = this.querySelector(".thinking-content");
if (content) {
setTimeout(() => {
content.scrollTop = content.scrollHeight;
}, 0);
}
}
});
block._hasToggleListener = true;
}
});
}