From 504d3d9de1fbf180e94791294771ecda99c8d0f3 Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Tue, 27 May 2025 18:42:07 -0700 Subject: [PATCH] Simplify the implementation --- css/main.css | 90 +++----------- js/global_scope_js.js | 40 +++++- js/main.js | 214 +++++++++----------------------- modules/chat.py | 73 ++++++++--- modules/html_generator.py | 46 ++++--- modules/message_versioning.py | 225 ---------------------------------- modules/ui.py | 2 + modules/ui_chat.py | 29 ++--- 8 files changed, 214 insertions(+), 505 deletions(-) delete mode 100644 modules/message_versioning.py diff --git a/css/main.css b/css/main.css index 888d50c0..46c0fec4 100644 --- a/css/main.css +++ b/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; diff --git a/js/global_scope_js.js b/js/global_scope_js.js index b81736d3..9174622e 100644 --- a/js/global_scope_js.js +++ b/js/global_scope_js.js @@ -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(); diff --git a/js/main.js b/js/main.js index 9f616fef..d90e8ade 100644 --- a/js/main.js +++ b/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; +} diff --git a/modules/chat.py b/modules/chat.py index 3f3c6f41..6eed47ee 100644 --- a/modules/chat.py +++ b/modules/chat.py @@ -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"), diff --git a/modules/html_generator.py b/modules/html_generator.py index 665cb4de..1dfeb445 100644 --- a/modules/html_generator.py +++ b/modules/html_generator.py @@ -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'' + right_arrow = f'' + position = f'{current_idx + 1}/{len(versions)}' + + return f'
{left_arrow}{position}{right_arrow}
' + + def actions_html(history, i, info_message=""): return (f'
' f'{copy_button}' @@ -389,7 +413,7 @@ def actions_html(history, i, info_message=""): f'{branch_button}' f'{info_message}' f'
' - 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'
' f'
' f'
{converted_visible[0]}
' @@ -434,9 +457,8 @@ def generate_instruct_html(history): f'
' ) - selected_class = " selected-message" if message_versioning.is_message_selected(i, 1) else "" output += ( - f'
' f'
' @@ -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'
' f'
{img_me}
' f'
' @@ -493,9 +514,8 @@ def generate_cai_chat_html(history, name1, name2, style, character, reset_cache= f'
' ) - selected_class = " selected-message" if message_versioning.is_message_selected(i, 1) else "" output += ( - f'
' f'
{img_bot}
' @@ -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'
' f'
' f'
{converted_visible[0]}
' @@ -554,9 +573,8 @@ def generate_chat_html(history, name1, name2, reset_cache=False): f'
' ) - selected_class = " selected-message" if message_versioning.is_message_selected(i, 1) else "" output += ( - f'
' f'
' diff --git a/modules/message_versioning.py b/modules/message_versioning.py deleted file mode 100644 index 5ed7c6fa..00000000 --- a/modules/message_versioning.py +++ /dev/null @@ -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 = '''''' - right_arrow_svg = '''''' - - nav_left = f'' - nav_pos = f'
{current_pos + 1}/{total_pos}
' - nav_right = f'' - - nav_container = ( - f'
' - f'{nav_left}' - f'{nav_pos}' - f'{nav_right}' - f'
' - ) - - versioning_container = ( - f'
' - f'{nav_container}' - f'
' - ) - - 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 diff --git a/modules/ui.py b/modules/ui.py index 5e8fa14e..52c095a2 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -157,6 +157,8 @@ def list_model_elements(): def list_interface_input_elements(): elements = [ + 'navigate_message_index', + 'navigate_direction', 'temperature', 'dynatemp_low', 'dynatemp_high', diff --git a/modules/ui_chat.py b/modules/ui_chat.py index b2bc8210..7a9f6f76 100644 --- a/modules/ui_chat.py +++ b/modules/ui_chat.py @@ -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)