Simplify the implementation

This commit is contained in:
oobabooga 2025-05-27 18:42:07 -07:00
parent 451337c9a5
commit 504d3d9de1
8 changed files with 214 additions and 505 deletions

View file

@ -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;

View file

@ -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();

View file

@ -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;
}

View file

@ -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"),

View file

@ -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">&lt;</button>'
right_arrow = f'<button class="footer-button version-nav-button"{right_disabled} onclick="navigateVersion(this, \'right\')" title="Next version">&gt;</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">'

View file

@ -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

View file

@ -157,6 +157,8 @@ def list_model_elements():
def list_interface_input_elements():
elements = [
'navigate_message_index',
'navigate_direction',
'temperature',
'dynatemp_low',
'dynatemp_high',

View file

@ -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)