UI: Make message editing work the same for user and assistant messages

This commit is contained in:
oobabooga 2025-05-28 17:09:05 -07:00
parent 6c3590ba9a
commit 27641ac182
5 changed files with 94 additions and 77 deletions

View file

@ -186,31 +186,33 @@ function navigateVersion(element, direction) {
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;
// Determine role based on message element classes
let role = "assistant"; // Default role
if (messageElement.classList.contains("user-message") ||
messageElement.querySelector(".text-you") ||
messageElement.querySelector(".circle-you")) {
role = "user";
}
const indexInput = document.getElementById("Navigate-message-index")?.querySelector("input");
const directionInput = document.getElementById("Navigate-direction")?.querySelector("textarea");
const roleInput = document.getElementById("Navigate-message-role")?.querySelector("textarea");
const navigateButton = document.getElementById("Navigate-version");
if (!navigateButton) {
console.error("Required element 'Navigate-version' not found.");
if (!indexInput || !directionInput || !roleInput || !navigateButton) {
console.error("Navigation control elements (index, direction, role, or button) not found.");
return;
}
indexInput.value = index;
directionInput.value = direction;
roleInput.value = role;
// Trigger any 'change' or 'input' events Gradio might be listening for
// Trigger 'input' events for Gradio to pick up changes
const event = new Event("input", { bubbles: true });
indexInput.dispatchEvent(event);
directionInput.dispatchEvent(event);
roleInput.dispatchEvent(event);
navigateButton.click();
}

View file

@ -451,19 +451,21 @@ def get_stopping_strings(state):
return result
def add_message_version(history, row_idx, is_current=True):
key = f"assistant_{row_idx}"
def add_message_version(history, role, row_idx, is_current=True):
key = f"{role}_{row_idx}"
if 'metadata' not in history:
history['metadata'] = {}
if key not in history['metadata']:
history['metadata'][key] = {}
if "versions" not in history['metadata'][key]:
history['metadata'][key]["versions"] = []
current_content = history['internal'][row_idx][1]
current_visible = history['visible'][row_idx][1]
# Determine which index to use for content based on role
content_idx = 0 if role == 'user' else 1
current_content = history['internal'][row_idx][content_idx]
current_visible = history['visible'][row_idx][content_idx]
# Always add the current message as a new version entry.
# The timestamp will differentiate it even if content is identical to a previous version.
history['metadata'][key]["versions"].append({
"content": current_content,
"visible_content": current_visible,
@ -594,7 +596,7 @@ def chatbot_wrapper(text, state, regenerate=False, _continue=False, loading_mess
# Store the first response as a version before regenerating
if not output['metadata'].get(f"assistant_{row_idx}", {}).get('versions'):
add_message_version(output, row_idx, is_current=False)
add_message_version(output, "assistant", row_idx, is_current=False)
if loading_message:
yield {
@ -656,12 +658,13 @@ def chatbot_wrapper(text, state, regenerate=False, _continue=False, loading_mess
if is_stream:
yield output
output['visible'][-1][1] = apply_extensions('output', output['visible'][-1][1], state, is_chat=True)
# Add the newly generated response as a version (only for regeneration)
if regenerate:
row_idx = len(output['internal']) - 1
add_message_version(output, row_idx, is_current=True)
add_message_version(output, "assistant", row_idx, is_current=True)
output['visible'][-1][1] = apply_extensions('output', output['visible'][-1][1], state, is_chat=True)
yield output
@ -1441,37 +1444,35 @@ def handle_edit_message_click(state):
if message_index >= len(history['internal']):
html_output = redraw_html(history, state['name1'], state['name2'], state['mode'], state['chat_style'], state['character_menu'])
return [history, html_output, gr.update()]
return [history, html_output, gr.update()] # No unique_id change
# Use the role passed from frontend
is_user_msg = (role == "user")
role_idx = 0 if is_user_msg else 1
role_idx = 0 if role == "user" else 1
# For assistant messages, save the original version BEFORE updating content
if not is_user_msg:
if not history['metadata'].get(f"assistant_{message_index}", {}).get('versions'):
add_message_version(history, message_index, is_current=False)
if 'metadata' not in history:
history['metadata'] = {}
key = f"{role}_{message_index}"
if key not in history['metadata']:
history['metadata'][key] = {}
# If no versions exist yet for this message, store the current (pre-edit) content as the first version.
if "versions" not in history['metadata'][key] or not history['metadata'][key]["versions"]:
original_content = history['internal'][message_index][role_idx]
original_visible = history['visible'][message_index][role_idx]
history['metadata'][key]["versions"] = [{
"content": original_content,
"visible_content": original_visible,
"timestamp": get_current_timestamp()
}]
# NOW update the message content
history['internal'][message_index][role_idx] = apply_extensions('input', new_text, state, is_chat=True)
history['visible'][message_index][role_idx] = html.escape(new_text)
# Branch if editing user message, add version if editing assistant message
if is_user_msg:
# Branch like branch-here
history['visible'] = history['visible'][:message_index + 1]
history['internal'] = history['internal'][:message_index + 1]
new_unique_id = datetime.now().strftime('%Y%m%d-%H-%M-%S')
save_history(history, new_unique_id, state['character_menu'], state['mode'])
histories = find_all_histories_with_first_prompts(state)
past_chats_update = gr.update(choices=histories, value=new_unique_id)
state['unique_id'] = new_unique_id
elif not is_user_msg:
# Add the new version as current
add_message_version(history, message_index, is_current=True)
past_chats_update = gr.update()
else:
past_chats_update = gr.update()
add_message_version(history, role, message_index, is_current=True)
# Since we are not branching, unique_id does not change.
past_chats_update = gr.update()
save_history(history, state['unique_id'], state['character_menu'], state['mode'])
html_output = redraw_html(history, state['name1'], state['name2'], state['mode'], state['chat_style'], state['character_menu'])
@ -1483,33 +1484,36 @@ def handle_navigate_version_click(state):
history = state['history']
message_index = int(state['navigate_message_index'])
direction = state['navigate_direction']
role = state['navigate_message_role']
# 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
if not role:
logger.error("Role not provided for version navigation.")
html = redraw_html(history, state['name1'], state['name2'], state['mode'], state['chat_style'], state['character_menu'])
return [history, html]
key = f"{role}_{message_index}"
if 'metadata' not in history or key not in history['metadata'] or 'versions' not in history['metadata'][key]:
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']
# Default to the last version if current_version_index is not set
current_idx = metadata.get('current_version_index', len(versions) - 1 if versions else 0)
# 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']
msg_content_idx = 0 if role == 'user' else 1 # 0 for user content, 1 for assistant content in the pair
version_to_load = versions[new_idx]
history['internal'][message_index][msg_content_idx] = version_to_load['content']
history['visible'][message_index][msg_content_idx] = version_to_load['visible_content']
metadata['current_version_index'] = new_idx
# Redraw and save

View file

@ -388,16 +388,17 @@ def format_message_attachments(history, role, index):
return ""
def get_version_navigation_html(history, i):
def get_version_navigation_html(history, i, role):
"""Generate simple navigation arrows for message versions"""
key = f"assistant_{i}"
key = f"{role}_{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)
# Default to the last version if current_version_index isn't set in metadata
current_idx = metadata[key].get('current_version_index', len(versions) - 1 if versions else 0)
if len(versions) <= 1:
return ""
@ -413,22 +414,33 @@ def get_version_navigation_html(history, i):
def actions_html(history, i, role, info_message=""):
action_buttons = ""
version_nav_html = ""
if role == "assistant":
return (f'<div class="message-actions">'
f'{copy_button}'
f'{edit_button}'
f'{refresh_button if i == len(history["visible"]) - 1 else ""}'
f'{continue_button if i == len(history["visible"]) - 1 else ""}'
f'{remove_button if i == len(history["visible"]) - 1 else ""}'
f'{branch_button}'
f'{info_message}'
f'</div>'
f'{get_version_navigation_html(history, i)}')
return (f'<div class="message-actions">'
action_buttons = (
f'{copy_button}'
f'{edit_button}'
f'{refresh_button if i == len(history["visible"]) - 1 else ""}'
f'{continue_button if i == len(history["visible"]) - 1 else ""}'
f'{remove_button if i == len(history["visible"]) - 1 else ""}'
f'{branch_button}'
)
version_nav_html = get_version_navigation_html(history, i, "assistant")
elif role == "user":
action_buttons = (
f'{copy_button}'
f'{edit_button}'
)
version_nav_html = get_version_navigation_html(history, i, "user")
return (f'<div class="message-actions">'
f'{action_buttons}'
f'{info_message}'
f'</div>')
f'</div>'
f'{version_nav_html}')
def generate_instruct_html(history):

View file

@ -212,14 +212,13 @@ def list_interface_input_elements():
'grammar_string',
'navigate_message_index',
'navigate_direction',
'navigate_message_role',
'edit_message_index',
'edit_message_text',
'edit_message_role',
'branch_index',
'enable_web_search',
'web_search_pages',
'navigate_message_index',
'navigate_direction',
]
# Chat elements

View file

@ -110,6 +110,7 @@ def create_ui():
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_message_role'] = gr.Textbox(value="", elem_id="Navigate-message-role")
shared.gradio['navigate_version'] = gr.Button(elem_id="Navigate-version")
shared.gradio['edit_message_index'] = gr.Number(value=-1, precision=0, elem_id="Edit-message-index")
shared.gradio['edit_message_text'] = gr.Textbox(value="", elem_id="Edit-message-text")
@ -313,8 +314,7 @@ def create_event_handlers():
shared.gradio['edit_message'].click(
ui.gather_interface_values, gradio(shared.input_elements), gradio('interface_state')).then(
chat.handle_edit_message_click, gradio('interface_state'), gradio('history', 'display', 'unique_id'), show_progress=False).then(
lambda: None, None, None, js='() => { const role = document.getElementById("Edit-message-role").querySelector("textarea").value; if (role === "user") document.getElementById("Regenerate").click(); }')
chat.handle_edit_message_click, gradio('interface_state'), gradio('history', 'display', 'unique_id'), 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)