mirror of
https://github.com/oobabooga/text-generation-webui.git
synced 2025-06-07 06:06:20 -04:00
Simplify the implementation
This commit is contained in:
parent
451337c9a5
commit
504d3d9de1
8 changed files with 214 additions and 505 deletions
90
css/main.css
90
css/main.css
|
@ -1260,7 +1260,7 @@ div.svelte-362y77>*, div.svelte-362y77>.form>* {
|
|||
position: absolute;
|
||||
bottom: -23px;
|
||||
left: 0;
|
||||
display: flex;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
|
@ -1458,95 +1458,41 @@ strong {
|
|||
color: #ccc;
|
||||
}
|
||||
|
||||
|
||||
/* --- Message Versioning Styles --- */
|
||||
|
||||
.message-versioning-container {
|
||||
/* --- Simple Version Navigation --- */
|
||||
.version-navigation {
|
||||
position: absolute;
|
||||
bottom: -23px;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.message:hover .message-versioning-container,
|
||||
.user-message:hover .message-versioning-container,
|
||||
.assistant-message:hover .message-versioning-container {
|
||||
.message:hover .version-navigation,
|
||||
.user-message:hover .version-navigation,
|
||||
.assistant-message:hover .version-navigation {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.selected-message .message-versioning-container {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
.version-nav-button {
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.message-versioning-nav-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.message-versioning-nav-arrow {
|
||||
position: static;
|
||||
margin: 0;
|
||||
padding: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 0;
|
||||
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.message .message-versioning-nav-arrow:not([activated]),
|
||||
.user-message .message-versioning-nav-arrow:not([activated]),
|
||||
.assistant-message .message-versioning-nav-arrow:not([activated]) {
|
||||
.version-nav-button[disabled] {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.3;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.message .message-versioning-nav-arrow:not([activated]):hover,
|
||||
.user-message .message-versioning-nav-arrow:not([activated]):hover,
|
||||
.assistant-message .message-versioning-nav-arrow:not([activated]):hover {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.message-versioning-nav-pos {
|
||||
.version-position {
|
||||
font-size: 11px;
|
||||
color: currentColor;
|
||||
font-family: monospace;
|
||||
color: rgb(156 163 175); /* Match SVG stroke color */
|
||||
user-select: none;
|
||||
min-width: 25px;
|
||||
min-width: 35px;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
padding: 0 2px;
|
||||
opacity: 0.8;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.dark .message-versioning-nav-pos {
|
||||
color: rgb(156 163 175); /* Same color for dark mode */
|
||||
}
|
||||
|
||||
/* If you want the other buttons to be visible when a message is selected, uncomment the lines below */
|
||||
.selected-message:not(:hover) .message-versioning-nav-arrow[activated]/*,
|
||||
.selected-message:not(:hover) .footer-button*/ {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.selected-message:not(:hover) .message-versioning-nav-arrow:not([activated]) {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.selected-message:hover .message-versioning-nav-arrow[activated]/*,
|
||||
.selected-message:hover .footer-button*/ {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.selected-message:hover .message-versioning-nav-arrow:not([activated]) {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.message-versioning-container[hidden] {
|
||||
display: none;
|
||||
|
|
|
@ -49,6 +49,44 @@ function branchHere(element) {
|
|||
|
||||
}
|
||||
|
||||
function navigateVersion(element, direction) {
|
||||
if (!element) return;
|
||||
|
||||
const messageElement = element.closest(".message, .user-message, .assistant-message");
|
||||
if (!messageElement) return;
|
||||
|
||||
const index = messageElement.getAttribute("data-index");
|
||||
if (!index) return;
|
||||
|
||||
const indexInput = document.getElementById("Navigate-message-index").querySelector("input");
|
||||
if (!indexInput) {
|
||||
console.error("Element with ID 'Navigate-message-index' not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const directionInput = document.getElementById("Navigate-direction").querySelector("textarea");
|
||||
if (!directionInput) {
|
||||
console.error("Element with ID 'Navigate-direction' not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const navigateButton = document.getElementById("Navigate-version");
|
||||
if (!navigateButton) {
|
||||
console.error("Required element 'Navigate-version' not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
indexInput.value = index;
|
||||
directionInput.value = direction;
|
||||
|
||||
// Trigger any 'change' or 'input' events Gradio might be listening for
|
||||
const event = new Event("input", { bubbles: true });
|
||||
indexInput.dispatchEvent(event);
|
||||
directionInput.dispatchEvent(event);
|
||||
|
||||
navigateButton.click();
|
||||
}
|
||||
|
||||
function regenerateClick() {
|
||||
document.getElementById("Regenerate").click();
|
||||
}
|
||||
|
@ -61,8 +99,6 @@ function removeLastClick() {
|
|||
document.getElementById("Remove-last").click();
|
||||
}
|
||||
|
||||
// === Version navigation handled in main.js === //
|
||||
|
||||
function handleMorphdomUpdate(text) {
|
||||
// Track open blocks
|
||||
const openBlocks = new Set();
|
||||
|
|
214
js/main.js
214
js/main.js
|
@ -39,23 +39,24 @@ document.querySelector(".header_bar").addEventListener("click", function(event)
|
|||
//------------------------------------------------
|
||||
// Keyboard shortcuts
|
||||
//------------------------------------------------
|
||||
|
||||
// --- Helper functions --- //
|
||||
function isModifiedKeyboardEvent() {
|
||||
return (event instanceof KeyboardEvent &&
|
||||
event.shiftKey ||
|
||||
event.ctrlKey ||
|
||||
event.altKey ||
|
||||
event.metaKey);
|
||||
}
|
||||
|
||||
function isFocusedOnEditableTextbox() {
|
||||
if (event.target.tagName === "INPUT" || event.target.tagName === "TEXTAREA") {
|
||||
return !!event.target.value;
|
||||
}
|
||||
}
|
||||
|
||||
let previousTabId = "chat-tab-button";
|
||||
document.addEventListener("keydown", function(event) {
|
||||
// --- Helper functions --- //
|
||||
function isModifiedKeyboardEvent() {
|
||||
return (event instanceof KeyboardEvent &&
|
||||
event.shiftKey ||
|
||||
event.ctrlKey ||
|
||||
event.altKey ||
|
||||
event.metaKey);
|
||||
}
|
||||
|
||||
function isFocusedOnEditableTextbox() {
|
||||
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
|
||||
return !!event.target.value;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop generation on Esc pressed
|
||||
if (event.key === "Escape") {
|
||||
// Find the element with id 'stop' and click it
|
||||
|
@ -119,51 +120,20 @@ document.addEventListener("keydown", function(event) {
|
|||
document.getElementById("Impersonate").click();
|
||||
}
|
||||
|
||||
// --- Non-textbox controls --- //
|
||||
// --- Simple version navigation --- //
|
||||
if (!isFocusedOnEditableTextbox()) {
|
||||
// Version navigation on Ctrl + Arrow (horizontal)
|
||||
if (!isModifiedKeyboardEvent() && event.key === 'ArrowLeft') {
|
||||
// Version navigation on Arrow keys (horizontal)
|
||||
if (!isModifiedKeyboardEvent() && event.key === "ArrowLeft") {
|
||||
event.preventDefault();
|
||||
triggerVersionNavigateBackend(selectedMessageHistoryIndex, selectedMessageType, 'left');
|
||||
navigateLastAssistantMessage("left");
|
||||
}
|
||||
|
||||
else if (!isModifiedKeyboardEvent() && event.key === 'ArrowRight') {
|
||||
else if (!isModifiedKeyboardEvent() && event.key === "ArrowRight") {
|
||||
event.preventDefault();
|
||||
let regenerateConditionMet = false;
|
||||
|
||||
const chatContainer = gradioApp().querySelector('#chat');
|
||||
const selectedMessageElement = chat.querySelector('.selected-message');
|
||||
|
||||
if (selectedMessageElement) {
|
||||
const rightNavArrow = selectedMessageElement.querySelector('.message-versioning-nav-right');
|
||||
const isRightArrowDisabled = !rightNavArrow?.hasAttribute('activated');
|
||||
|
||||
// Selected message is the last element in #chat
|
||||
if (chatContainer) {
|
||||
const allMessages = Array.from(chatContainer.querySelectorAll('.message, .user-message, .assistant-message'));
|
||||
const isLastMessage = allMessages.length > 0 && selectedMessageElement === allMessages[allMessages.length - 1];
|
||||
|
||||
if (isRightArrowDisabled && isLastMessage) {
|
||||
document.getElementById("Regenerate").click();
|
||||
regenerateConditionMet = true;
|
||||
}
|
||||
}
|
||||
if (!navigateLastAssistantMessage("right")) {
|
||||
// If can't navigate right (last version), regenerate
|
||||
document.getElementById("Regenerate").click();
|
||||
}
|
||||
|
||||
if (!regenerateConditionMet) {
|
||||
triggerVersionNavigateBackend(selectedMessageHistoryIndex, selectedMessageType, 'right');
|
||||
}
|
||||
}
|
||||
|
||||
// Select relative message on Ctrl + Arrow (vertical)
|
||||
else if (!isModifiedKeyboardEvent() && event.key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
selectRelativeMessage(-1)
|
||||
}
|
||||
|
||||
else if (!isModifiedKeyboardEvent() && event.key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
selectRelativeMessage(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -857,118 +827,54 @@ function createMobileTopBar() {
|
|||
|
||||
createMobileTopBar();
|
||||
|
||||
|
||||
//------------------------------------------------
|
||||
// Message Versioning Integration
|
||||
// Simple Navigation Functions
|
||||
//------------------------------------------------
|
||||
|
||||
// --- Message Versioning Variables ---
|
||||
let selectedMessageHistoryIndex = null;
|
||||
let selectedMessageType = null;
|
||||
function navigateLastAssistantMessage(direction) {
|
||||
const chat = document.querySelector("#chat");
|
||||
if (!chat) return false;
|
||||
|
||||
// --- Message Versioning Helper Functions ---
|
||||
const messages = chat.querySelectorAll("[data-index]");
|
||||
if (messages.length === 0) return false;
|
||||
|
||||
// Helper function to get Gradio app root (if needed, otherwise use document)
|
||||
function gradioApp() {
|
||||
const elems = document.querySelectorAll('gradio-app');
|
||||
const gradioShadowRoot = elems.length > 0 ? elems[0].shadowRoot : null;
|
||||
return gradioShadowRoot || document;
|
||||
}
|
||||
|
||||
// Helper to update Gradio text/number inputs (if needed for backend communication)
|
||||
function updateGradioInput(element, value) {
|
||||
if (element) {
|
||||
element.value = value;
|
||||
element.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
} else {
|
||||
console.warn("Attempted to update a null Gradio input element.");
|
||||
// Find the last assistant message (starting from the end)
|
||||
let lastAssistantMessage = null;
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const msg = messages[i];
|
||||
if (
|
||||
msg.classList.contains("assistant-message") ||
|
||||
msg.querySelector(".circle-bot") ||
|
||||
msg.querySelector(".text-bot")
|
||||
) {
|
||||
lastAssistantMessage = msg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Message Versioning Core Functions ---
|
||||
|
||||
function triggerVersionNavigateBackend(historyIndex, messageType, direction) {
|
||||
const gradio = gradioApp();
|
||||
const historyIndexInput = gradio.querySelector('#message-versioning-history-index-hidden input[type="number"]');
|
||||
const messageTypeInput = gradio.querySelector('#message-versioning-message-type-hidden input[type="number"]');
|
||||
const directionInput = gradio.querySelector('#message-versioning-direction-hidden textarea');
|
||||
const navigateButton = gradio.querySelector('#message-versioning-navigate-hidden');
|
||||
|
||||
if (historyIndexInput && messageTypeInput && directionInput && navigateButton) {
|
||||
console.debug("Found hidden Gradio elements for navigation.", navigateButton);
|
||||
updateGradioInput(historyIndexInput, historyIndex);
|
||||
updateGradioInput(messageTypeInput, messageType);
|
||||
updateGradioInput(directionInput, direction);
|
||||
navigateButton.click();
|
||||
} else {
|
||||
console.error("Message Versioning: Could not find hidden Gradio elements for navigation. Backend communication needs setup.");
|
||||
}
|
||||
}
|
||||
|
||||
// Function called by the nav arrow's onclick attribute
|
||||
function versioningNavigateClick(arrowButton, historyIndex, messageType, direction) {
|
||||
// Keep the message selected
|
||||
const messageElement = arrowButton.closest('.message, .user-message, .assistant-message');
|
||||
if (messageElement) {
|
||||
selectMessage(messageElement, historyIndex, messageType);
|
||||
}
|
||||
|
||||
triggerVersionNavigateBackend(historyIndex, messageType, direction);
|
||||
}
|
||||
if (!lastAssistantMessage) return false;
|
||||
|
||||
const buttons = lastAssistantMessage.querySelectorAll(".version-nav-button");
|
||||
|
||||
function selectMessage(element, historyIndex, messageType) {
|
||||
// Remove previous selection
|
||||
deselectMessages();
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
const button = buttons[i];
|
||||
const onclick = button.getAttribute("onclick");
|
||||
const disabled = button.hasAttribute("disabled");
|
||||
|
||||
if (element) {
|
||||
selectedMessageHistoryIndex = historyIndex;
|
||||
selectedMessageType = messageType;
|
||||
element.classList.add('selected-message');
|
||||
}
|
||||
}
|
||||
const isLeft = onclick && onclick.includes("'left'");
|
||||
const isRight = onclick && onclick.includes("'right'");
|
||||
|
||||
function deselectMessages() {
|
||||
const selectedMessageElement = gradioApp().querySelector('#chat .selected-message');
|
||||
if (selectedMessageElement) {
|
||||
selectedMessageElement.classList.remove('selected-message');
|
||||
}
|
||||
selectedMessageHistoryIndex = null;
|
||||
selectedMessageType = null;
|
||||
}
|
||||
|
||||
function selectRelativeMessage(offset) {
|
||||
const chat = gradioApp().querySelector('#chat');
|
||||
if (!chat) return;
|
||||
const messages = Array.from(chat.querySelectorAll('.message, .user-message, .assistant-message'));
|
||||
if (messages.length === 0) return;
|
||||
const selectedMessageChatIndex = messages.findIndex(msg => msg.classList.contains('selected-message')); // Could be saved in a variable rather than run each time
|
||||
const index = selectedMessageChatIndex + offset;
|
||||
if (index >= 0 && index < messages.length) messages[index]?.click();
|
||||
}
|
||||
|
||||
// --- Message Versioning Global Listeners ---
|
||||
|
||||
// Global click listener for buttons and (de)selecting messages
|
||||
document.addEventListener('click', function(e) {
|
||||
const target = e.target;
|
||||
|
||||
const msg = target.closest('.message, .user-message, .assistant-message')
|
||||
if (msg) {
|
||||
const historyIndex = msg.getAttribute('data-index');
|
||||
const msgType = (msg.classList.contains('assistant-message') || msg.querySelector('.circle-bot, .text-bot')) ? 1 : 0;
|
||||
if (target.closest('button')) {
|
||||
const button = target.closest('.message-versioning-nav-arrow');
|
||||
if (button && button.hasAttribute('activated')) {
|
||||
const direction = button.closest('.message-versioning-nav-left') ? 'left' : 'right';
|
||||
versioningNavigateClick(button, parseFloat(historyIndex), parseFloat(msgType), direction);
|
||||
if (!disabled) {
|
||||
if (direction === "left" && isLeft) {
|
||||
navigateVersion(button, direction);
|
||||
return true;
|
||||
}
|
||||
if (direction === "right" && isRight) {
|
||||
navigateVersion(button, direction);
|
||||
return true;
|
||||
}
|
||||
} else if (msg.classList.contains('selected-message') && !e.ctrlKey) {
|
||||
deselectMessages();
|
||||
} else {
|
||||
selectMessage(msg, parseInt(historyIndex), parseInt(msgType));
|
||||
}
|
||||
} else if (target.closest('#chat') && !target.closest('#message-versioning-navigate-hidden')) { // Deselect if the click is in-chat, outside a message
|
||||
deselectMessages();
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -414,22 +414,26 @@ def add_message_version(history, row_idx, is_current=True):
|
|||
if "versions" not in history['metadata'][key]:
|
||||
history['metadata'][key]["versions"] = []
|
||||
|
||||
index = None
|
||||
for index, version in enumerate(history['metadata'][key]["versions"]):
|
||||
if version['content'] == history['internal'][row_idx][1] and version['visible_content'] == history['visible'][row_idx][1]:
|
||||
break
|
||||
# Check if this version already exists
|
||||
current_content = history['internal'][row_idx][1]
|
||||
current_visible = history['visible'][row_idx][1]
|
||||
|
||||
if index is None:
|
||||
# Add current message as a version
|
||||
history['metadata'][key]["versions"].append({
|
||||
"content": history['internal'][row_idx][1],
|
||||
"visible_content": history['visible'][row_idx][1],
|
||||
"timestamp": get_current_timestamp()
|
||||
})
|
||||
for i, version in enumerate(history['metadata'][key]["versions"]):
|
||||
if version['content'] == current_content and version['visible_content'] == current_visible:
|
||||
if is_current:
|
||||
history['metadata'][key]["current_version_index"] = i
|
||||
return
|
||||
|
||||
# Add current message as a version
|
||||
history['metadata'][key]["versions"].append({
|
||||
"content": current_content,
|
||||
"visible_content": current_visible,
|
||||
"timestamp": get_current_timestamp()
|
||||
})
|
||||
|
||||
# Update index if this is the current version
|
||||
if is_current:
|
||||
history['metadata'][key]["current_version_index"] = index if index is not None else len(history['metadata'][key]["versions"]) - 1
|
||||
history['metadata'][key]["current_version_index"] = len(history['metadata'][key]["versions"]) - 1
|
||||
|
||||
|
||||
def add_message_attachment(history, row_idx, file_path, is_user=True):
|
||||
|
@ -687,8 +691,6 @@ def generate_chat_reply_wrapper(text, state, regenerate=False, _continue=False):
|
|||
|
||||
save_history(history, state['unique_id'], state['character_menu'], state['mode'])
|
||||
|
||||
yield chat_html_wrapper(history, state['name1'], state['name2'], state['mode'], state['chat_style'], state['character_menu']), history
|
||||
|
||||
|
||||
def remove_last_message(history):
|
||||
if 'metadata' not in history:
|
||||
|
@ -729,12 +731,9 @@ def replace_last_reply(textbox, state):
|
|||
return history
|
||||
elif len(history['visible']) > 0:
|
||||
row_idx = len(history['internal']) - 1
|
||||
if not history['metadata'].get(f"assistant_{row_idx}", {}).get('versions'):
|
||||
add_message_version(history, row_idx, is_current=False)
|
||||
history['visible'][-1][1] = html.escape(text)
|
||||
history['internal'][-1][1] = apply_extensions('input', text, state, is_chat=True)
|
||||
update_message_metadata(history['metadata'], "assistant", row_idx, timestamp=get_current_timestamp())
|
||||
add_message_version(history, row_idx, is_current=True)
|
||||
|
||||
return history
|
||||
|
||||
|
@ -1426,6 +1425,46 @@ def handle_branch_chat_click(state):
|
|||
return [history, html, past_chats_update, -1]
|
||||
|
||||
|
||||
def handle_navigate_version_click(state):
|
||||
history = state['history']
|
||||
message_index = int(state['navigate_message_index'])
|
||||
direction = state['navigate_direction']
|
||||
|
||||
# Get assistant message metadata
|
||||
key = f"assistant_{message_index}"
|
||||
if key not in history['metadata'] or 'versions' not in history['metadata'][key]:
|
||||
# No versions to navigate
|
||||
html = redraw_html(history, state['name1'], state['name2'], state['mode'], state['chat_style'], state['character_menu'])
|
||||
return [history, html]
|
||||
|
||||
metadata = history['metadata'][key]
|
||||
current_idx = metadata.get('current_version_index', 0)
|
||||
versions = metadata['versions']
|
||||
|
||||
# Calculate new index
|
||||
if direction == 'left':
|
||||
new_idx = max(0, current_idx - 1)
|
||||
else: # right
|
||||
new_idx = min(len(versions) - 1, current_idx + 1)
|
||||
|
||||
if new_idx == current_idx:
|
||||
# No change needed
|
||||
html = redraw_html(history, state['name1'], state['name2'], state['mode'], state['chat_style'], state['character_menu'])
|
||||
return [history, html]
|
||||
|
||||
# Update history with new version
|
||||
version = versions[new_idx]
|
||||
history['internal'][message_index][1] = version['content']
|
||||
history['visible'][message_index][1] = version['visible_content']
|
||||
metadata['current_version_index'] = new_idx
|
||||
|
||||
# Redraw and save
|
||||
html = redraw_html(history, state['name1'], state['name2'], state['mode'], state['chat_style'], state['character_menu'])
|
||||
save_history(history, state['unique_id'], state['character_menu'], state['mode'])
|
||||
|
||||
return [history, html]
|
||||
|
||||
|
||||
def handle_rename_chat_click():
|
||||
return [
|
||||
gr.update(value="My New Chat"),
|
||||
|
|
|
@ -9,7 +9,7 @@ from pathlib import Path
|
|||
import markdown
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
from modules import shared, message_versioning
|
||||
from modules import shared
|
||||
from modules.sane_markdown_lists import SaneListExtension
|
||||
from modules.utils import get_available_chat_styles
|
||||
|
||||
|
@ -380,6 +380,30 @@ def format_message_attachments(history, role, index):
|
|||
return ""
|
||||
|
||||
|
||||
def get_version_navigation_html(history, i):
|
||||
"""Generate simple navigation arrows for message versions"""
|
||||
key = f"assistant_{i}"
|
||||
metadata = history.get('metadata', {})
|
||||
|
||||
if key not in metadata or 'versions' not in metadata[key]:
|
||||
return ""
|
||||
|
||||
versions = metadata[key]['versions']
|
||||
current_idx = metadata[key].get('current_version_index', 0)
|
||||
|
||||
if len(versions) <= 1:
|
||||
return ""
|
||||
|
||||
left_disabled = ' disabled' if current_idx == 0 else ''
|
||||
right_disabled = ' disabled' if current_idx >= len(versions) - 1 else ''
|
||||
|
||||
left_arrow = f'<button class="footer-button version-nav-button"{left_disabled} onclick="navigateVersion(this, \'left\')" title="Previous version"><</button>'
|
||||
right_arrow = f'<button class="footer-button version-nav-button"{right_disabled} onclick="navigateVersion(this, \'right\')" title="Next version">></button>'
|
||||
position = f'<span class="version-position">{current_idx + 1}/{len(versions)}</span>'
|
||||
|
||||
return f'<div class="version-navigation">{left_arrow}{position}{right_arrow}</div>'
|
||||
|
||||
|
||||
def actions_html(history, i, info_message=""):
|
||||
return (f'<div class="message-actions">'
|
||||
f'{copy_button}'
|
||||
|
@ -389,7 +413,7 @@ def actions_html(history, i, info_message=""):
|
|||
f'{branch_button}'
|
||||
f'{info_message}'
|
||||
f'</div>'
|
||||
f'{message_versioning.get_message_version_nav_elements(history, i, 1)}')
|
||||
f'{get_version_navigation_html(history, i)}')
|
||||
|
||||
|
||||
def generate_instruct_html(history):
|
||||
|
@ -422,9 +446,8 @@ def generate_instruct_html(history):
|
|||
info_message_assistant = info_button.replace("message", assistant_timestamp_value)
|
||||
|
||||
if converted_visible[0]: # Don't display empty user messages
|
||||
selected_class = " selected-message" if message_versioning.is_message_selected(i, 0) else ""
|
||||
output += (
|
||||
f'<div class="user-message{selected_class}" '
|
||||
f'<div class="user-message" '
|
||||
f'data-raw="{html.escape(row_internal[0], quote=True)}">'
|
||||
f'<div class="text">'
|
||||
f'<div class="message-body">{converted_visible[0]}</div>'
|
||||
|
@ -434,9 +457,8 @@ def generate_instruct_html(history):
|
|||
f'</div>'
|
||||
)
|
||||
|
||||
selected_class = " selected-message" if message_versioning.is_message_selected(i, 1) else ""
|
||||
output += (
|
||||
f'<div class="assistant-message{selected_class}" '
|
||||
f'<div class="assistant-message" '
|
||||
f'data-raw="{html.escape(row_internal[1], quote=True)}"'
|
||||
f'data-index={i}>'
|
||||
f'<div class="text">'
|
||||
|
@ -479,9 +501,8 @@ def generate_cai_chat_html(history, name1, name2, style, character, reset_cache=
|
|||
assistant_attachments = format_message_attachments(history, "assistant", i)
|
||||
|
||||
if converted_visible[0]: # Don't display empty user messages
|
||||
selected_class = " selected-message" if message_versioning.is_message_selected(i, 0) else ""
|
||||
output += (
|
||||
f'<div class="message{selected_class}" '
|
||||
f'<div class="message" '
|
||||
f'data-raw="{html.escape(row_internal[0], quote=True)}">'
|
||||
f'<div class="circle-you">{img_me}</div>'
|
||||
f'<div class="text">'
|
||||
|
@ -493,9 +514,8 @@ def generate_cai_chat_html(history, name1, name2, style, character, reset_cache=
|
|||
f'</div>'
|
||||
)
|
||||
|
||||
selected_class = " selected-message" if message_versioning.is_message_selected(i, 1) else ""
|
||||
output += (
|
||||
f'<div class="message{selected_class}"'
|
||||
f'<div class="message" '
|
||||
f'data-raw="{html.escape(row_internal[1], quote=True)}"'
|
||||
f'data-index={i}>'
|
||||
f'<div class="circle-bot">{img_bot}</div>'
|
||||
|
@ -542,9 +562,8 @@ def generate_chat_html(history, name1, name2, reset_cache=False):
|
|||
info_message_assistant = info_button.replace("message", assistant_timestamp_value)
|
||||
|
||||
if converted_visible[0]: # Don't display empty user messages
|
||||
selected_class = " selected-message" if message_versioning.is_message_selected(i, 0) else ""
|
||||
output += (
|
||||
f'<div class="message{selected_class}" '
|
||||
f'<div class="message" '
|
||||
f'data-raw="{html.escape(row_internal[0], quote=True)}">'
|
||||
f'<div class="text-you">'
|
||||
f'<div class="message-body">{converted_visible[0]}</div>'
|
||||
|
@ -554,9 +573,8 @@ def generate_chat_html(history, name1, name2, reset_cache=False):
|
|||
f'</div>'
|
||||
)
|
||||
|
||||
selected_class = " selected-message" if message_versioning.is_message_selected(i, 1) else ""
|
||||
output += (
|
||||
f'<div class="message{selected_class}" '
|
||||
f'<div class="message" '
|
||||
f'data-raw="{html.escape(row_internal[1], quote=True)}"'
|
||||
f'data-index={i}>'
|
||||
f'<div class="text-bot">'
|
||||
|
|
|
@ -1,225 +0,0 @@
|
|||
from typing import Dict, List, Iterable
|
||||
from functools import reduce
|
||||
from operator import getitem
|
||||
import logging as logger
|
||||
|
||||
last_state = {
|
||||
'display_mode': 'html', # Default display mode
|
||||
}
|
||||
|
||||
# --- Functions to be called by integrated logic ---
|
||||
|
||||
|
||||
def set_versioning_display_mode(new_dmode: str):
|
||||
"""Sets the display mode ('html', 'overlay', 'off')."""
|
||||
global last_state
|
||||
last_state['display_mode'] = new_dmode
|
||||
|
||||
|
||||
def get_versioning_display_mode():
|
||||
"""Gets the current display mode from the loaded history."""
|
||||
current_mode = last_state.get('display_mode', 'html')
|
||||
return current_mode
|
||||
|
||||
|
||||
# --- Core Logic ---
|
||||
|
||||
last_history_index = -1
|
||||
last_msg_type = -1
|
||||
|
||||
|
||||
# --- Helper Functions ---
|
||||
|
||||
def recursive_get(data: Dict | Iterable, keyList: List[int | str], default=None):
|
||||
"""Iterate nested dictionary / iterable safely."""
|
||||
try:
|
||||
result = reduce(getitem, keyList, data)
|
||||
return result
|
||||
except (KeyError, IndexError, TypeError):
|
||||
return default
|
||||
except Exception as e:
|
||||
logger.error(f"Error in recursive_get: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
def length(data: Iterable):
|
||||
"""Get length of iterable with try-catch."""
|
||||
try:
|
||||
result = len(data)
|
||||
return result
|
||||
except TypeError:
|
||||
return 0
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting length: {e}", exc_info=True)
|
||||
return 0
|
||||
|
||||
|
||||
def get_message_metadata(history: dict, history_index: int, msg_type: int) -> dict:
|
||||
msg_role = 'assistant' if msg_type == 1 else 'user'
|
||||
return recursive_get(history, ['metadata', f'{msg_role}_{history_index}'])
|
||||
|
||||
# --- Core Functions ---
|
||||
|
||||
|
||||
def get_message_positions(history: dict, history_index: int, msg_type: int):
|
||||
"""
|
||||
Get the message position and total message versions for a specific message from the currently loaded history data.
|
||||
Assumes the correct history data has already been loaded by the calling context.
|
||||
"""
|
||||
msg_data = get_message_metadata(history, history_index, msg_type)
|
||||
|
||||
if msg_data is None or msg_data['versions'] is None:
|
||||
return 0, 0
|
||||
|
||||
pos = msg_data.get('current_version_index', 0)
|
||||
total = length(msg_data['versions'])
|
||||
return pos, total
|
||||
|
||||
|
||||
def navigate_message_version(history_index: float, msg_type: float, direction: str, history: Dict):
|
||||
"""
|
||||
Handles navigation (left/right swipe) for a message version.
|
||||
Updates the history dictionary directly and returns the modified history.
|
||||
The calling function will be responsible for regenerating the HTML.
|
||||
"""
|
||||
global last_history_index, last_msg_type
|
||||
try:
|
||||
i = int(history_index)
|
||||
m_type = int(msg_type)
|
||||
last_history_index = i # Default to current index/type
|
||||
last_msg_type = m_type
|
||||
|
||||
current_pos, total_pos = get_message_positions(history, i, m_type)
|
||||
|
||||
if total_pos <= 1:
|
||||
return history
|
||||
|
||||
# Calculate new position
|
||||
new_pos = current_pos
|
||||
if direction == "right":
|
||||
new_pos += 1
|
||||
if new_pos >= total_pos:
|
||||
return history
|
||||
elif direction == "left":
|
||||
new_pos -= 1
|
||||
if new_pos < 0:
|
||||
return history
|
||||
else:
|
||||
logger.warning(f"Invalid navigation direction: {direction}")
|
||||
return history
|
||||
|
||||
history_metadata = history['metadata']
|
||||
msg_metadata = get_message_metadata(history, i, m_type)
|
||||
if msg_metadata is None:
|
||||
logger.error(f"Message metadata not found for index {i}, type {m_type}. Data: {history_metadata}")
|
||||
return history
|
||||
versions = msg_metadata.get('versions', [])
|
||||
new_ver = versions[new_pos]
|
||||
if new_ver is None:
|
||||
logger.error(f"Message version not found for metadata index {i}, type {m_type}, version index {new_pos}. Data: {history_metadata}")
|
||||
return history
|
||||
|
||||
ver_content = new_ver['content']
|
||||
ver_visible_content = new_ver['visible_content']
|
||||
|
||||
if ver_content is None or ver_visible_content is None:
|
||||
logger.error(f"Loaded version data structure invalid during navigation for index {i}, type {m_type}. Data: {history_metadata}")
|
||||
return history
|
||||
|
||||
# Check bounds for the new position against the text lists
|
||||
if new_pos < 0 or new_pos >= len(msg_metadata['versions']):
|
||||
logger.error(f"Navigation error: new_pos {new_pos} out of bounds for history text list (len {len(versions)})")
|
||||
return history
|
||||
|
||||
history['internal'][i][m_type] = ver_content
|
||||
history['visible'][i][m_type] = ver_visible_content
|
||||
msg_metadata['timestamp'] = new_ver['timestamp']
|
||||
msg_metadata['current_version_index'] = new_pos # Updates history object
|
||||
|
||||
last_history_index = i
|
||||
last_msg_type = m_type
|
||||
|
||||
return history
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during message version navigation: {e}", exc_info=True)
|
||||
return history
|
||||
|
||||
|
||||
# --- Functions to be called during HTML Generation ---
|
||||
|
||||
|
||||
def get_message_version_nav_elements(history: dict, history_index: int, msg_type: int):
|
||||
"""
|
||||
Generates the HTML for the message version navigation arrows and position indicator.
|
||||
Should be called by the HTML generator.
|
||||
Returns an empty string if history storage mode is 'off' or navigation is not needed.
|
||||
"""
|
||||
if get_versioning_display_mode() == 'off':
|
||||
return ""
|
||||
|
||||
try:
|
||||
current_pos, total_pos = get_message_positions(history, history_index, msg_type)
|
||||
|
||||
if total_pos <= 1:
|
||||
return ""
|
||||
|
||||
left_activated_attr = ' activated="true"' if current_pos > 0 else ''
|
||||
right_activated_attr = ' activated="true"' if current_pos < total_pos - 1 else ''
|
||||
|
||||
left_arrow_svg = '''<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>'''
|
||||
right_arrow_svg = '''<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>'''
|
||||
|
||||
nav_left = f'<button class="footer-button message-versioning-nav-arrow message-versioning-nav-left"{left_activated_attr}>{left_arrow_svg}</button>'
|
||||
nav_pos = f'<div class="message-versioning-nav-pos">{current_pos + 1}/{total_pos}</div>'
|
||||
nav_right = f'<button class="footer-button message-versioning-nav-arrow message-versioning-nav-right"{right_activated_attr}>{right_arrow_svg}</button>'
|
||||
|
||||
nav_container = (
|
||||
f'<div class="message-versioning-nav-container">'
|
||||
f'{nav_left}'
|
||||
f'{nav_pos}'
|
||||
f'{nav_right}'
|
||||
f'</div>'
|
||||
)
|
||||
|
||||
versioning_container = (
|
||||
f'<div class="message-versioning-container">'
|
||||
f'{nav_container}'
|
||||
f'</div>'
|
||||
)
|
||||
|
||||
return versioning_container
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating message versioning nav elements for index {history_index}, type {msg_type}: {e}", exc_info=True)
|
||||
return ""
|
||||
|
||||
|
||||
def is_message_selected(history_index: int, msg_type: int) -> bool:
|
||||
"""
|
||||
Returns True if the message at the specified index and type is selected.
|
||||
"""
|
||||
global last_history_index, last_msg_type
|
||||
return last_history_index == history_index and last_msg_type == msg_type
|
||||
|
||||
|
||||
def handle_display_mode_change(display_mode: str):
|
||||
"""
|
||||
Handles changes to the message versioning display mode radio button.
|
||||
Updates the history storage mode. The UI redraw should be triggered by the main logic.
|
||||
"""
|
||||
set_versioning_display_mode(display_mode)
|
||||
|
||||
|
||||
def handle_navigate_click(history_index: float, msg_type: float, direction: str, history: Dict, character: str, unique_id: str, mode: str, name1: str, name2: str, chat_style: str):
|
||||
"""
|
||||
Handles clicks on the navigation buttons generated by get_message_version_nav_elements.
|
||||
Calls navigate_message_version and returns the updated history and potentially the regenerated HTML.
|
||||
"""
|
||||
updated_history = navigate_message_version(history_index, msg_type, direction, history)
|
||||
|
||||
import modules.chat as chat
|
||||
html = chat.redraw_html(updated_history, name1, name2, mode, chat_style, character)
|
||||
chat.save_history(history, unique_id, character, mode)
|
||||
|
||||
return updated_history, html
|
|
@ -157,6 +157,8 @@ def list_model_elements():
|
|||
|
||||
def list_interface_input_elements():
|
||||
elements = [
|
||||
'navigate_message_index',
|
||||
'navigate_direction',
|
||||
'temperature',
|
||||
'dynatemp_low',
|
||||
'dynatemp_high',
|
||||
|
|
|
@ -5,7 +5,7 @@ from pathlib import Path
|
|||
import gradio as gr
|
||||
from PIL import Image
|
||||
|
||||
from modules import chat, shared, ui, utils, message_versioning
|
||||
from modules import chat, shared, ui, utils
|
||||
from modules.html_generator import chat_html_wrapper
|
||||
from modules.text_generation import stop_everything_event
|
||||
from modules.utils import gradio
|
||||
|
@ -94,18 +94,14 @@ def create_ui():
|
|||
with gr.Row():
|
||||
shared.gradio['chat_style'] = gr.Dropdown(choices=utils.get_available_chat_styles(), label='Chat style', value=shared.settings['chat_style'], visible=shared.settings['mode'] != 'instruct')
|
||||
|
||||
with gr.Row(visible=True):
|
||||
shared.gradio['message_versioning_display_mode'] = gr.Radio(choices=['html', 'off'], value='html', label="Message Versioning Display", info="Controls how message version navigation is displayed.", elem_id="message-versioning-display-mode", elem_classes=['slim-dropdown'])
|
||||
|
||||
with gr.Row():
|
||||
shared.gradio['chat-instruct_command'] = gr.Textbox(value=shared.settings['chat-instruct_command'], lines=12, label='Command for chat-instruct mode', info='<|character|> and <|prompt|> get replaced with the bot name and the regular chat prompt respectively.', visible=shared.settings['mode'] == 'chat-instruct', elem_classes=['add_scrollbar'])
|
||||
|
||||
# Hidden elements for message versioning JS communication
|
||||
with gr.Row(visible=False, elem_id="message-versioning-hidden-row"):
|
||||
shared.gradio['message_versioning_history_index_hidden'] = gr.Number(value=0, elem_id="message-versioning-history-index-hidden")
|
||||
shared.gradio['message_versioning_message_type_hidden'] = gr.Number(value=0, elem_id="message-versioning-message-type-hidden")
|
||||
shared.gradio['message_versioning_direction_hidden'] = gr.Textbox(value="", elem_id="message-versioning-direction-hidden")
|
||||
shared.gradio['message_versioning_navigate_hidden'] = gr.Button(elem_id="message-versioning-navigate-hidden")
|
||||
# Hidden elements for version navigation (similar to branch)
|
||||
with gr.Row(visible=False):
|
||||
shared.gradio['navigate_message_index'] = gr.Number(value=-1, precision=0, elem_id="Navigate-message-index")
|
||||
shared.gradio['navigate_direction'] = gr.Textbox(value="", elem_id="Navigate-direction")
|
||||
shared.gradio['navigate_version'] = gr.Button(elem_id="Navigate-version")
|
||||
|
||||
|
||||
def create_chat_settings_ui():
|
||||
|
@ -303,18 +299,9 @@ def create_event_handlers():
|
|||
shared.gradio['chat_style'].change(chat.redraw_html, gradio(reload_arr), gradio('display'), show_progress=False)
|
||||
shared.gradio['Copy last reply'].click(chat.send_last_reply_to_input, gradio('history'), gradio('textbox'), show_progress=False)
|
||||
|
||||
# Message Versioning event handlers
|
||||
shared.gradio['message_versioning_display_mode'].change(
|
||||
shared.gradio['navigate_version'].click(
|
||||
ui.gather_interface_values, gradio(shared.input_elements), gradio('interface_state')).then(
|
||||
message_versioning.handle_display_mode_change, gradio('message_versioning_display_mode'), None).then(
|
||||
chat.redraw_html, gradio(reload_arr), gradio('display'), show_progress=False)
|
||||
|
||||
shared.gradio['message_versioning_navigate_hidden'].click(
|
||||
ui.gather_interface_values, gradio(shared.input_elements), gradio('interface_state')).then(
|
||||
message_versioning.handle_navigate_click,
|
||||
gradio('message_versioning_history_index_hidden', 'message_versioning_message_type_hidden', 'message_versioning_direction_hidden', 'history', 'character_menu', 'unique_id', 'mode', 'name1', 'name2', 'chat_style'),
|
||||
gradio('history', 'display'),
|
||||
show_progress=False)
|
||||
chat.handle_navigate_version_click, gradio('interface_state'), gradio('history', 'display'), show_progress=False)
|
||||
|
||||
# Save/delete a character
|
||||
shared.gradio['save_character'].click(chat.handle_save_character_click, gradio('name2'), gradio('save_character_filename', 'character_saver'), show_progress=False)
|
||||
|
|
Loading…
Add table
Reference in a new issue