From e59cfab25326c034b9370c56e00886aa5aadc8b7 Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 1 Jun 2025 00:49:17 -0700 Subject: [PATCH 1/8] Reapply "Dynamic Chat Message UI Update Speed (#6952)" (for now) This reverts commit 126b3a768fa9af7f5318dbfd70b7e6ad00defc68. --- modules/shared.py | 1 - modules/text_generation.py | 18 ++++++++---------- modules/ui.py | 1 - modules/ui_parameters.py | 2 -- user_data/settings-template.yaml | 1 - 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index d2305f30..9a181f3e 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -47,7 +47,6 @@ settings = { 'max_new_tokens_max': 4096, 'prompt_lookup_num_tokens': 0, 'max_tokens_second': 0, - 'max_updates_second': 12, 'auto_max_new_tokens': True, 'ban_eos_token': False, 'add_bos_token': True, diff --git a/modules/text_generation.py b/modules/text_generation.py index 1fd6d810..0d499d50 100644 --- a/modules/text_generation.py +++ b/modules/text_generation.py @@ -65,41 +65,39 @@ def _generate_reply(question, state, stopping_strings=None, is_chat=False, escap all_stop_strings += st shared.stop_everything = False - last_update = -1 reply = '' is_stream = state['stream'] if len(all_stop_strings) > 0 and not state['stream']: state = copy.deepcopy(state) state['stream'] = True - min_update_interval = 0 - if state.get('max_updates_second', 0) > 0: - min_update_interval = 1 / state['max_updates_second'] - # Generate + last_update = -1 + latency_threshold = 1 / 1000 for reply in generate_func(question, original_question, state, stopping_strings, is_chat=is_chat): + cur_time = time.monotonic() reply, stop_found = apply_stopping_strings(reply, all_stop_strings) if escape_html: reply = html.escape(reply) if is_stream: - cur_time = time.time() - # Limit number of tokens/second to make text readable in real time if state['max_tokens_second'] > 0: diff = 1 / state['max_tokens_second'] - (cur_time - last_update) if diff > 0: time.sleep(diff) - last_update = time.time() + last_update = time.monotonic() yield reply # Limit updates to avoid lag in the Gradio UI # API updates are not limited else: - if cur_time - last_update > min_update_interval: - last_update = cur_time + # If 'generate_func' takes less than 0.001 seconds to yield the next token + # (equivalent to more than 1000 tok/s), assume that the UI is lagging behind and skip yielding + if (cur_time - last_update) > latency_threshold: yield reply + last_update = time.monotonic() if stop_found or (state['max_tokens_second'] > 0 and shared.stop_everything): break diff --git a/modules/ui.py b/modules/ui.py index 9f4d67cb..422db740 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -194,7 +194,6 @@ def list_interface_input_elements(): 'max_new_tokens', 'prompt_lookup_num_tokens', 'max_tokens_second', - 'max_updates_second', 'do_sample', 'dynamic_temperature', 'temperature_last', diff --git a/modules/ui_parameters.py b/modules/ui_parameters.py index 733d0901..84f9fbfc 100644 --- a/modules/ui_parameters.py +++ b/modules/ui_parameters.py @@ -71,8 +71,6 @@ def create_ui(default_preset): shared.gradio['max_new_tokens'] = gr.Slider(minimum=shared.settings['max_new_tokens_min'], maximum=shared.settings['max_new_tokens_max'], value=shared.settings['max_new_tokens'], step=1, label='max_new_tokens', info='⚠️ Setting this too high can cause prompt truncation.') shared.gradio['prompt_lookup_num_tokens'] = gr.Slider(value=shared.settings['prompt_lookup_num_tokens'], minimum=0, maximum=10, step=1, label='prompt_lookup_num_tokens', info='Activates Prompt Lookup Decoding.') shared.gradio['max_tokens_second'] = gr.Slider(value=shared.settings['max_tokens_second'], minimum=0, maximum=20, step=1, label='Maximum tokens/second', info='To make text readable in real time.') - shared.gradio['max_updates_second'] = gr.Slider(value=shared.settings['max_updates_second'], minimum=0, maximum=24, step=1, label='Maximum UI updates/second', info='Set this if you experience lag in the UI during streaming.') - with gr.Column(): with gr.Row(): with gr.Column(): diff --git a/user_data/settings-template.yaml b/user_data/settings-template.yaml index ce0f77e1..db481e84 100644 --- a/user_data/settings-template.yaml +++ b/user_data/settings-template.yaml @@ -18,7 +18,6 @@ max_new_tokens_min: 1 max_new_tokens_max: 4096 prompt_lookup_num_tokens: 0 max_tokens_second: 0 -max_updates_second: 12 auto_max_new_tokens: true ban_eos_token: false add_bos_token: true From 67bb6190643a343156cb2b723d4de71aeb3a6819 Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 1 Jun 2025 07:46:33 -0700 Subject: [PATCH 2/8] Render only the last message during streaming --- js/global_scope_js.js | 16 ++- js/main.js | 2 +- modules/chat.py | 2 +- modules/html_generator.py | 233 ++++++++++++++++++++++---------------- modules/ui_chat.py | 4 +- 5 files changed, 151 insertions(+), 106 deletions(-) diff --git a/js/global_scope_js.js b/js/global_scope_js.js index 3274f47e..89498814 100644 --- a/js/global_scope_js.js +++ b/js/global_scope_js.js @@ -229,7 +229,7 @@ function removeLastClick() { document.getElementById("Remove-last").click(); } -function handleMorphdomUpdate(text) { +function handleMorphdomUpdate(data) { // Track open blocks const openBlocks = new Set(); document.querySelectorAll(".thinking-block").forEach(block => { @@ -254,9 +254,19 @@ function handleMorphdomUpdate(text) { } }); + var target_element, target_html; + if (data.last_message_only) { + const childNodes = document.getElementsByClassName("messages")[0].childNodes; + target_element = childNodes[childNodes.length - 1]; + target_html = data.html; + } else { + target_element = document.getElementById("chat").parentNode; + target_html = "
" + data.html + "
"; + } + morphdom( - document.getElementById("chat").parentNode, - "
" + text + "
", + target_element, + target_html, { onBeforeElUpdated: function(fromEl, toEl) { // Preserve code highlighting diff --git a/js/main.js b/js/main.js index d152a572..05c19571 100644 --- a/js/main.js +++ b/js/main.js @@ -184,7 +184,7 @@ const observer = new MutationObserver(function(mutations) { const prevSibling = lastChild?.previousElementSibling; if (lastChild && prevSibling) { lastChild.style.setProperty("margin-bottom", - `max(0px, calc(max(70vh, 100vh - ${prevSibling.offsetHeight}px - 102px) - ${lastChild.offsetHeight}px))`, + `max(0px, calc(max(70vh, 100vh - ${prevSibling.offsetHeight}px - 84px) - ${lastChild.offsetHeight}px))`, "important" ); } diff --git a/modules/chat.py b/modules/chat.py index 1222d2bb..fff82613 100644 --- a/modules/chat.py +++ b/modules/chat.py @@ -825,7 +825,7 @@ def generate_chat_reply_wrapper(text, state, regenerate=False, _continue=False): last_save_time = time.monotonic() save_interval = 8 for i, history in enumerate(generate_chat_reply(text, state, regenerate, _continue, loading_message=True, for_ui=True)): - yield chat_html_wrapper(history, state['name1'], state['name2'], state['mode'], state['chat_style'], state['character_menu']), history + yield chat_html_wrapper(history, state['name1'], state['name2'], state['mode'], state['chat_style'], state['character_menu'], last_message_only=(i > 0)), history current_time = time.monotonic() # Save on first iteration or if save_interval seconds have passed diff --git a/modules/html_generator.py b/modules/html_generator.py index 03b5d485..2c3a3e70 100644 --- a/modules/html_generator.py +++ b/modules/html_generator.py @@ -462,175 +462,210 @@ def actions_html(history, i, role, info_message=""): f'{version_nav_html}') -def generate_instruct_html(history): - output = f'
' +def generate_instruct_message_html(role, converted_content, row_internal, i, attachments, actions_html_content): + """Generate HTML for a single message in instruct style.""" + class_name = "user-message" if role == "user" else "assistant-message" - for i in range(len(history['visible'])): + return ( + f'
' + f'
' + f'
{converted_content}
' + f'{attachments}' + f'{actions_html_content}' + f'
' + f'
' + ) + + +def generate_cai_message_html(role, converted_content, row_internal, i, attachments, actions_html_content, name, timestamp, img): + """Generate HTML for a single message in CAI style.""" + circle_class = "circle-you" if role == "user" else "circle-bot" + + return ( + f'
' + f'
{img}
' + f'
' + f'
{name}{timestamp}
' + f'
{converted_content}
' + f'{attachments}' + f'{actions_html_content}' + f'
' + f'
' + ) + + +def generate_wpp_message_html(role, converted_content, row_internal, i, attachments, actions_html_content): + """Generate HTML for a single message in WPP style.""" + text_class = "text-you" if role == "user" else "text-bot" + + return ( + f'
' + f'
' + f'
{converted_content}
' + f'{attachments}' + f'{actions_html_content}' + f'
' + f'
' + ) + + +def generate_instruct_html(history, last_message_only=False): + if not last_message_only: + output = f'
' + else: + output = "" + + # Determine range - either last message only or all messages + start_idx = len(history['visible']) - 1 if last_message_only else 0 + end_idx = len(history['visible']) + + for i in range(start_idx, end_idx): row_visible = history['visible'][i] row_internal = history['internal'][i] converted_visible = [convert_to_markdown_wrapped(entry, message_id=i, use_cache=i != len(history['visible']) - 1) for entry in row_visible] - # Get timestamps + # Get timestamps and attachments user_timestamp = format_message_timestamp(history, "user", i) assistant_timestamp = format_message_timestamp(history, "assistant", i) - - # Get attachments user_attachments = format_message_attachments(history, "user", i) assistant_attachments = format_message_attachments(history, "assistant", i) # Create info buttons for timestamps if they exist info_message_user = "" - if user_timestamp != "": + if user_timestamp: tooltip_text = get_message_tooltip(history, "user", i) info_message_user = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') info_message_assistant = "" - if assistant_timestamp != "": + if assistant_timestamp: tooltip_text = get_message_tooltip(history, "assistant", i) info_message_assistant = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') - if converted_visible[0]: # Don't display empty user messages - output += ( - f'
' - f'
' - f'
{converted_visible[0]}
' - f'{user_attachments}' - f'{actions_html(history, i, "user", info_message_user)}' - f'
' - f'
' + # Generate user message if not empty + if converted_visible[0] and not last_message_only: + output += generate_instruct_message_html( + "user", converted_visible[0], row_internal[0], i, + user_attachments, actions_html(history, i, "user", info_message_user) ) - output += ( - f'
' - f'
' - f'
{converted_visible[1]}
' - f'{assistant_attachments}' - f'{actions_html(history, i, "assistant", info_message_assistant)}' - f'
' - f'
' + # Generate assistant message + output += generate_instruct_message_html( + "assistant", converted_visible[1], row_internal[1], i, + assistant_attachments, actions_html(history, i, "assistant", info_message_assistant) ) - output += "
" + if not last_message_only: + output += "
" + return output -def generate_cai_chat_html(history, name1, name2, style, character, reset_cache=False): - output = f'
' +def generate_cai_chat_html(history, name1, name2, style, character, reset_cache=False, last_message_only=False): + if not last_message_only: + output = f'
' + else: + output = "" - # We use ?character and ?time.time() to force the browser to reset caches + # Profile images img_bot = ( f'' if Path("user_data/cache/pfp_character_thumb.png").exists() else '' ) - img_me = ( f'' if Path("user_data/cache/pfp_me.png").exists() else '' ) - for i in range(len(history['visible'])): + # Determine range - either last message only or all messages + start_idx = len(history['visible']) - 1 if last_message_only else 0 + end_idx = len(history['visible']) + + for i in range(start_idx, end_idx): row_visible = history['visible'][i] row_internal = history['internal'][i] converted_visible = [convert_to_markdown_wrapped(entry, message_id=i, use_cache=i != len(history['visible']) - 1) for entry in row_visible] - # Get timestamps + # Get timestamps and attachments user_timestamp = format_message_timestamp(history, "user", i, tooltip_include_timestamp=False) assistant_timestamp = format_message_timestamp(history, "assistant", i, tooltip_include_timestamp=False) - - # Get attachments user_attachments = format_message_attachments(history, "user", i) assistant_attachments = format_message_attachments(history, "assistant", i) - if converted_visible[0]: # Don't display empty user messages - output += ( - f'
' - f'
{img_me}
' - f'
' - f'
{name1}{user_timestamp}
' - f'
{converted_visible[0]}
' - f'{user_attachments}' - f'{actions_html(history, i, "user")}' - f'
' - f'
' + # Generate user message if not empty + if converted_visible[0] and not last_message_only: + output += generate_cai_message_html( + "user", converted_visible[0], row_internal[0], i, + user_attachments, actions_html(history, i, "user"), + name1, user_timestamp, img_me ) - output += ( - f'
' - f'
{img_bot}
' - f'
' - f'
{name2}{assistant_timestamp}
' - f'
{converted_visible[1]}
' - f'{assistant_attachments}' - f'{actions_html(history, i, "assistant")}' - f'
' - f'
' + # Generate assistant message + output += generate_cai_message_html( + "assistant", converted_visible[1], row_internal[1], i, + assistant_attachments, actions_html(history, i, "assistant"), + name2, assistant_timestamp, img_bot ) - output += "
" + if not last_message_only: + output += "
" + return output -def generate_chat_html(history, name1, name2, reset_cache=False): - output = f'
' +def generate_chat_html(history, name1, name2, reset_cache=False, last_message_only=False): + if not last_message_only: + output = f'
' + else: + output = "" - for i in range(len(history['visible'])): + # Determine range - either last message only or all messages + start_idx = len(history['visible']) - 1 if last_message_only else 0 + end_idx = len(history['visible']) + + for i in range(start_idx, end_idx): row_visible = history['visible'][i] row_internal = history['internal'][i] converted_visible = [convert_to_markdown_wrapped(entry, message_id=i, use_cache=i != len(history['visible']) - 1) for entry in row_visible] - # Get timestamps + # Get timestamps and attachments user_timestamp = format_message_timestamp(history, "user", i) assistant_timestamp = format_message_timestamp(history, "assistant", i) - - # Get attachments user_attachments = format_message_attachments(history, "user", i) assistant_attachments = format_message_attachments(history, "assistant", i) # Create info buttons for timestamps if they exist info_message_user = "" - if user_timestamp != "": + if user_timestamp: tooltip_text = get_message_tooltip(history, "user", i) info_message_user = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') info_message_assistant = "" - if assistant_timestamp != "": + if assistant_timestamp: tooltip_text = get_message_tooltip(history, "assistant", i) info_message_assistant = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') - if converted_visible[0]: # Don't display empty user messages - output += ( - f'
' - f'
' - f'
{converted_visible[0]}
' - f'{user_attachments}' - f'{actions_html(history, i, "user", info_message_user)}' - f'
' - f'
' + # Generate user message if not empty + if converted_visible[0] and not last_message_only: + output += generate_wpp_message_html( + "user", converted_visible[0], row_internal[0], i, + user_attachments, actions_html(history, i, "user", info_message_user) ) - output += ( - f'
' - f'
' - f'
{converted_visible[1]}
' - f'{assistant_attachments}' - f'{actions_html(history, i, "assistant", info_message_assistant)}' - f'
' - f'
' + # Generate assistant message + output += generate_wpp_message_html( + "assistant", converted_visible[1], row_internal[1], i, + assistant_attachments, actions_html(history, i, "assistant", info_message_assistant) ) - output += "
" + if not last_message_only: + output += "
" + return output @@ -644,15 +679,15 @@ def time_greeting(): return "Good evening!" -def chat_html_wrapper(history, name1, name2, mode, style, character, reset_cache=False): +def chat_html_wrapper(history, name1, name2, mode, style, character, reset_cache=False, last_message_only=False): if len(history['visible']) == 0: greeting = f"
{time_greeting()} How can I help you today?
" result = f'
{greeting}
' elif mode == 'instruct': - result = generate_instruct_html(history) + result = generate_instruct_html(history, last_message_only=last_message_only) elif style == 'wpp': - result = generate_chat_html(history, name1, name2) + result = generate_chat_html(history, name1, name2, last_message_only=last_message_only) else: - result = generate_cai_chat_html(history, name1, name2, style, character, reset_cache) + result = generate_cai_chat_html(history, name1, name2, style, character, reset_cache=reset_cache, last_message_only=last_message_only) - return {'html': result} + return {'html': result, 'last_message_only': last_message_only} diff --git a/modules/ui_chat.py b/modules/ui_chat.py index 822b77b8..e71341f1 100644 --- a/modules/ui_chat.py +++ b/modules/ui_chat.py @@ -47,7 +47,7 @@ def create_ui(): with gr.Row(): with gr.Column(elem_id='chat-col'): - shared.gradio['display'] = gr.JSON(value={}, visible=False) # Hidden buffer + shared.gradio['display'] = gr.JSON(value={}, visible=True) # Hidden buffer shared.gradio['html_display'] = gr.HTML(value=chat_html_wrapper({'internal': [], 'visible': [], 'metadata': {}}, '', '', 'chat', 'cai-chat', '')['html'], visible=True) with gr.Row(elem_id="chat-input-row"): with gr.Column(scale=1, elem_id='gr-hover-container'): @@ -195,7 +195,7 @@ def create_event_handlers(): shared.reload_inputs = gradio(reload_arr) # Morph HTML updates instead of updating everything - shared.gradio['display'].change(None, gradio('display'), None, js="(data) => handleMorphdomUpdate(data.html)") + shared.gradio['display'].change(None, gradio('display'), None, js="(data) => handleMorphdomUpdate(data)") shared.gradio['Generate'].click( ui.gather_interface_values, gradio(shared.input_elements), gradio('interface_state')).then( From af947fcbb6c0bc82f301910150e99eac79837d2f Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 1 Jun 2025 08:16:45 -0700 Subject: [PATCH 3/8] Hide the hidden buffer --- modules/ui_chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_chat.py b/modules/ui_chat.py index e71341f1..24ec5270 100644 --- a/modules/ui_chat.py +++ b/modules/ui_chat.py @@ -47,7 +47,7 @@ def create_ui(): with gr.Row(): with gr.Column(elem_id='chat-col'): - shared.gradio['display'] = gr.JSON(value={}, visible=True) # Hidden buffer + shared.gradio['display'] = gr.JSON(value={}, visible=False) # Hidden buffer shared.gradio['html_display'] = gr.HTML(value=chat_html_wrapper({'internal': [], 'visible': [], 'metadata': {}}, '', '', 'chat', 'cai-chat', '')['html'], visible=True) with gr.Row(elem_id="chat-input-row"): with gr.Column(scale=1, elem_id='gr-hover-container'): From e20989171c80a53b419cdef62da2390f7f7e09ca Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 1 Jun 2025 10:30:31 -0700 Subject: [PATCH 4/8] Make 'history' a gr.State() --- modules/ui_chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_chat.py b/modules/ui_chat.py index 24ec5270..a8eaadfa 100644 --- a/modules/ui_chat.py +++ b/modules/ui_chat.py @@ -18,7 +18,7 @@ def create_ui(): mu = shared.args.multi_user shared.gradio['Chat input'] = gr.State() - shared.gradio['history'] = gr.JSON(visible=False) + shared.gradio['history'] = gr.State() with gr.Tab('Chat', id='Chat', elem_id='chat-tab'): with gr.Row(elem_id='past-chats-row', elem_classes=['pretty_scrollbar']): From 1a086adec0a546ccfacd5e46163f5bfeb253c27c Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 1 Jun 2025 11:31:07 -0700 Subject: [PATCH 5/8] Avoid converting user messages to markdown when last_message_only is set --- modules/html_generator.py | 104 +++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 40 deletions(-) diff --git a/modules/html_generator.py b/modules/html_generator.py index 2c3a3e70..5b694ea4 100644 --- a/modules/html_generator.py +++ b/modules/html_generator.py @@ -528,32 +528,40 @@ def generate_instruct_html(history, last_message_only=False): for i in range(start_idx, end_idx): row_visible = history['visible'][i] row_internal = history['internal'][i] - converted_visible = [convert_to_markdown_wrapped(entry, message_id=i, use_cache=i != len(history['visible']) - 1) for entry in row_visible] - # Get timestamps and attachments - user_timestamp = format_message_timestamp(history, "user", i) + # Only convert what we need + if last_message_only: + converted_visible = [None, convert_to_markdown_wrapped(row_visible[1], message_id=i, use_cache=i != len(history['visible']) - 1)] + else: + converted_visible = [convert_to_markdown_wrapped(entry, message_id=i, use_cache=i != len(history['visible']) - 1) for entry in row_visible] + + # Get assistant timestamps and attachments (always needed) assistant_timestamp = format_message_timestamp(history, "assistant", i) - user_attachments = format_message_attachments(history, "user", i) assistant_attachments = format_message_attachments(history, "assistant", i) - # Create info buttons for timestamps if they exist - info_message_user = "" - if user_timestamp: - tooltip_text = get_message_tooltip(history, "user", i) - info_message_user = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') + # Only compute user-related variables and generate user message when needed + if not last_message_only and converted_visible[0]: + user_timestamp = format_message_timestamp(history, "user", i) + user_attachments = format_message_attachments(history, "user", i) - info_message_assistant = "" - if assistant_timestamp: - tooltip_text = get_message_tooltip(history, "assistant", i) - info_message_assistant = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') + # Create info button for user timestamp if it exists + info_message_user = "" + if user_timestamp: + tooltip_text = get_message_tooltip(history, "user", i) + info_message_user = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') - # Generate user message if not empty - if converted_visible[0] and not last_message_only: + # Generate user message output += generate_instruct_message_html( "user", converted_visible[0], row_internal[0], i, user_attachments, actions_html(history, i, "user", info_message_user) ) + # Create info button for assistant timestamp if it exists + info_message_assistant = "" + if assistant_timestamp: + tooltip_text = get_message_tooltip(history, "assistant", i) + info_message_assistant = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') + # Generate assistant message output += generate_instruct_message_html( "assistant", converted_visible[1], row_internal[1], i, @@ -577,10 +585,6 @@ def generate_cai_chat_html(history, name1, name2, style, character, reset_cache= f'' if Path("user_data/cache/pfp_character_thumb.png").exists() else '' ) - img_me = ( - f'' - if Path("user_data/cache/pfp_me.png").exists() else '' - ) # Determine range - either last message only or all messages start_idx = len(history['visible']) - 1 if last_message_only else 0 @@ -589,16 +593,28 @@ def generate_cai_chat_html(history, name1, name2, style, character, reset_cache= for i in range(start_idx, end_idx): row_visible = history['visible'][i] row_internal = history['internal'][i] - converted_visible = [convert_to_markdown_wrapped(entry, message_id=i, use_cache=i != len(history['visible']) - 1) for entry in row_visible] - # Get timestamps and attachments - user_timestamp = format_message_timestamp(history, "user", i, tooltip_include_timestamp=False) + # Only convert what we need + if last_message_only: + converted_visible = [None, convert_to_markdown_wrapped(row_visible[1], message_id=i, use_cache=i != len(history['visible']) - 1)] + else: + converted_visible = [convert_to_markdown_wrapped(entry, message_id=i, use_cache=i != len(history['visible']) - 1) for entry in row_visible] + + # Get assistant timestamps and attachments (always needed) assistant_timestamp = format_message_timestamp(history, "assistant", i, tooltip_include_timestamp=False) - user_attachments = format_message_attachments(history, "user", i) assistant_attachments = format_message_attachments(history, "assistant", i) - # Generate user message if not empty - if converted_visible[0] and not last_message_only: + # Only compute user-related variables and generate user message when needed + if not last_message_only and converted_visible[0]: + img_me = ( + f'' + if Path("user_data/cache/pfp_me.png").exists() else '' + ) + + user_timestamp = format_message_timestamp(history, "user", i, tooltip_include_timestamp=False) + user_attachments = format_message_attachments(history, "user", i) + + # Generate user message output += generate_cai_message_html( "user", converted_visible[0], row_internal[0], i, user_attachments, actions_html(history, i, "user"), @@ -631,32 +647,40 @@ def generate_chat_html(history, name1, name2, reset_cache=False, last_message_on for i in range(start_idx, end_idx): row_visible = history['visible'][i] row_internal = history['internal'][i] - converted_visible = [convert_to_markdown_wrapped(entry, message_id=i, use_cache=i != len(history['visible']) - 1) for entry in row_visible] - # Get timestamps and attachments - user_timestamp = format_message_timestamp(history, "user", i) + # Only convert what we need + if last_message_only: + converted_visible = [None, convert_to_markdown_wrapped(row_visible[1], message_id=i, use_cache=i != len(history['visible']) - 1)] + else: + converted_visible = [convert_to_markdown_wrapped(entry, message_id=i, use_cache=i != len(history['visible']) - 1) for entry in row_visible] + + # Get assistant timestamps and attachments (always needed) assistant_timestamp = format_message_timestamp(history, "assistant", i) - user_attachments = format_message_attachments(history, "user", i) assistant_attachments = format_message_attachments(history, "assistant", i) - # Create info buttons for timestamps if they exist - info_message_user = "" - if user_timestamp: - tooltip_text = get_message_tooltip(history, "user", i) - info_message_user = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') + # Only compute user-related variables and generate user message when needed + if not last_message_only and converted_visible[0]: + user_timestamp = format_message_timestamp(history, "user", i) + user_attachments = format_message_attachments(history, "user", i) - info_message_assistant = "" - if assistant_timestamp: - tooltip_text = get_message_tooltip(history, "assistant", i) - info_message_assistant = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') + # Create info button for user timestamp if it exists + info_message_user = "" + if user_timestamp: + tooltip_text = get_message_tooltip(history, "user", i) + info_message_user = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') - # Generate user message if not empty - if converted_visible[0] and not last_message_only: + # Generate user message output += generate_wpp_message_html( "user", converted_visible[0], row_internal[0], i, user_attachments, actions_html(history, i, "user", info_message_user) ) + # Create info button for assistant timestamp if it exists + info_message_assistant = "" + if assistant_timestamp: + tooltip_text = get_message_tooltip(history, "assistant", i) + info_message_assistant = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') + # Generate assistant message output += generate_wpp_message_html( "assistant", converted_visible[1], row_internal[1], i, From 6f25e6d6acd2a442a2cd7bf12c6e7fc3b7b93cb5 Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 1 Jun 2025 11:40:41 -0700 Subject: [PATCH 6/8] Further optimize handleMorphdomUpdate a bit --- js/global_scope_js.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/js/global_scope_js.js b/js/global_scope_js.js index 89498814..d5140c93 100644 --- a/js/global_scope_js.js +++ b/js/global_scope_js.js @@ -230,9 +230,22 @@ function removeLastClick() { } function handleMorphdomUpdate(data) { + // Determine target element and use it as query scope + var target_element, target_html; + if (data.last_message_only) { + const childNodes = document.getElementsByClassName("messages")[0].childNodes; + target_element = childNodes[childNodes.length - 1]; + target_html = data.html; + } else { + target_element = document.getElementById("chat").parentNode; + target_html = "
" + data.html + "
"; + } + + const queryScope = target_element; + // Track open blocks const openBlocks = new Set(); - document.querySelectorAll(".thinking-block").forEach(block => { + queryScope.querySelectorAll(".thinking-block").forEach(block => { const blockId = block.getAttribute("data-block-id"); // If block exists and is open, add to open set if (blockId && block.hasAttribute("open")) { @@ -242,7 +255,7 @@ function handleMorphdomUpdate(data) { // Store scroll positions for any open blocks const scrollPositions = {}; - document.querySelectorAll(".thinking-block[open]").forEach(block => { + queryScope.querySelectorAll(".thinking-block[open]").forEach(block => { const content = block.querySelector(".thinking-content"); const blockId = block.getAttribute("data-block-id"); if (content && blockId) { @@ -254,16 +267,6 @@ function handleMorphdomUpdate(data) { } }); - var target_element, target_html; - if (data.last_message_only) { - const childNodes = document.getElementsByClassName("messages")[0].childNodes; - target_element = childNodes[childNodes.length - 1]; - target_html = data.html; - } else { - target_element = document.getElementById("chat").parentNode; - target_html = "
" + data.html + "
"; - } - morphdom( target_element, target_html, @@ -317,7 +320,7 @@ function handleMorphdomUpdate(data) { ); // Add toggle listeners for new blocks - document.querySelectorAll(".thinking-block").forEach(block => { + queryScope.querySelectorAll(".thinking-block").forEach(block => { if (!block._hasToggleListener) { block.addEventListener("toggle", function(e) { if (this.open) { From e3274bf87120fc699d3bbb00883b7e3bafeca2c1 Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 1 Jun 2025 13:41:25 -0700 Subject: [PATCH 7/8] Simplify the changes to html_generator.py --- modules/html_generator.py | 241 +++++++++++++++----------------------- 1 file changed, 97 insertions(+), 144 deletions(-) diff --git a/modules/html_generator.py b/modules/html_generator.py index 5b694ea4..d47360d3 100644 --- a/modules/html_generator.py +++ b/modules/html_generator.py @@ -462,66 +462,39 @@ def actions_html(history, i, role, info_message=""): f'{version_nav_html}') -def generate_instruct_message_html(role, converted_content, row_internal, i, attachments, actions_html_content): - """Generate HTML for a single message in instruct style.""" - class_name = "user-message" if role == "user" else "assistant-message" - - return ( - f'
' - f'
' - f'
{converted_content}
' - f'{attachments}' - f'{actions_html_content}' - f'
' - f'
' - ) - - -def generate_cai_message_html(role, converted_content, row_internal, i, attachments, actions_html_content, name, timestamp, img): - """Generate HTML for a single message in CAI style.""" - circle_class = "circle-you" if role == "user" else "circle-bot" - - return ( - f'
' - f'
{img}
' - f'
' - f'
{name}{timestamp}
' - f'
{converted_content}
' - f'{attachments}' - f'{actions_html_content}' - f'
' - f'
' - ) - - -def generate_wpp_message_html(role, converted_content, row_internal, i, attachments, actions_html_content): - """Generate HTML for a single message in WPP style.""" - text_class = "text-you" if role == "user" else "text-bot" - - return ( - f'
' - f'
' - f'
{converted_content}
' - f'{attachments}' - f'{actions_html_content}' - f'
' - f'
' - ) - - def generate_instruct_html(history, last_message_only=False): if not last_message_only: output = f'
' else: output = "" - # Determine range - either last message only or all messages + def create_message(role, content, raw_content): + """Inner function that captures variables from outer scope.""" + class_name = "user-message" if role == "user" else "assistant-message" + + # Get role-specific data + timestamp = format_message_timestamp(history, role, i) + attachments = format_message_attachments(history, role, i) + + # Create info button if timestamp exists + info_message = "" + if timestamp: + tooltip_text = get_message_tooltip(history, role, i) + info_message = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') + + return ( + f'
' + f'
' + f'
{content}
' + f'{attachments}' + f'{actions_html(history, i, role, info_message)}' + f'
' + f'
' + ) + + # Determine range start_idx = len(history['visible']) - 1 if last_message_only else 0 end_idx = len(history['visible']) @@ -529,44 +502,17 @@ def generate_instruct_html(history, last_message_only=False): row_visible = history['visible'][i] row_internal = history['internal'][i] - # Only convert what we need + # Convert content if last_message_only: converted_visible = [None, convert_to_markdown_wrapped(row_visible[1], message_id=i, use_cache=i != len(history['visible']) - 1)] else: converted_visible = [convert_to_markdown_wrapped(entry, message_id=i, use_cache=i != len(history['visible']) - 1) for entry in row_visible] - # Get assistant timestamps and attachments (always needed) - assistant_timestamp = format_message_timestamp(history, "assistant", i) - assistant_attachments = format_message_attachments(history, "assistant", i) - - # Only compute user-related variables and generate user message when needed + # Generate messages if not last_message_only and converted_visible[0]: - user_timestamp = format_message_timestamp(history, "user", i) - user_attachments = format_message_attachments(history, "user", i) + output += create_message("user", converted_visible[0], row_internal[0]) - # Create info button for user timestamp if it exists - info_message_user = "" - if user_timestamp: - tooltip_text = get_message_tooltip(history, "user", i) - info_message_user = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') - - # Generate user message - output += generate_instruct_message_html( - "user", converted_visible[0], row_internal[0], i, - user_attachments, actions_html(history, i, "user", info_message_user) - ) - - # Create info button for assistant timestamp if it exists - info_message_assistant = "" - if assistant_timestamp: - tooltip_text = get_message_tooltip(history, "assistant", i) - info_message_assistant = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') - - # Generate assistant message - output += generate_instruct_message_html( - "assistant", converted_visible[1], row_internal[1], i, - assistant_attachments, actions_html(history, i, "assistant", info_message_assistant) - ) + output += create_message("assistant", converted_visible[1], row_internal[1]) if not last_message_only: output += "
" @@ -586,7 +532,37 @@ def generate_cai_chat_html(history, name1, name2, style, character, reset_cache= if Path("user_data/cache/pfp_character_thumb.png").exists() else '' ) - # Determine range - either last message only or all messages + def create_message(role, content, raw_content): + """Inner function for CAI-style messages.""" + circle_class = "circle-you" if role == "user" else "circle-bot" + name = name1 if role == "user" else name2 + + # Get role-specific data + timestamp = format_message_timestamp(history, role, i, tooltip_include_timestamp=False) + attachments = format_message_attachments(history, role, i) + + # Get appropriate image + if role == "user": + img = (f'' + if Path("user_data/cache/pfp_me.png").exists() else '') + else: + img = img_bot + + return ( + f'
' + f'
{img}
' + f'
' + f'
{name}{timestamp}
' + f'
{content}
' + f'{attachments}' + f'{actions_html(history, i, role)}' + f'
' + f'
' + ) + + # Determine range start_idx = len(history['visible']) - 1 if last_message_only else 0 end_idx = len(history['visible']) @@ -594,39 +570,17 @@ def generate_cai_chat_html(history, name1, name2, style, character, reset_cache= row_visible = history['visible'][i] row_internal = history['internal'][i] - # Only convert what we need + # Convert content if last_message_only: converted_visible = [None, convert_to_markdown_wrapped(row_visible[1], message_id=i, use_cache=i != len(history['visible']) - 1)] else: converted_visible = [convert_to_markdown_wrapped(entry, message_id=i, use_cache=i != len(history['visible']) - 1) for entry in row_visible] - # Get assistant timestamps and attachments (always needed) - assistant_timestamp = format_message_timestamp(history, "assistant", i, tooltip_include_timestamp=False) - assistant_attachments = format_message_attachments(history, "assistant", i) - - # Only compute user-related variables and generate user message when needed + # Generate messages if not last_message_only and converted_visible[0]: - img_me = ( - f'' - if Path("user_data/cache/pfp_me.png").exists() else '' - ) + output += create_message("user", converted_visible[0], row_internal[0]) - user_timestamp = format_message_timestamp(history, "user", i, tooltip_include_timestamp=False) - user_attachments = format_message_attachments(history, "user", i) - - # Generate user message - output += generate_cai_message_html( - "user", converted_visible[0], row_internal[0], i, - user_attachments, actions_html(history, i, "user"), - name1, user_timestamp, img_me - ) - - # Generate assistant message - output += generate_cai_message_html( - "assistant", converted_visible[1], row_internal[1], i, - assistant_attachments, actions_html(history, i, "assistant"), - name2, assistant_timestamp, img_bot - ) + output += create_message("assistant", converted_visible[1], row_internal[1]) if not last_message_only: output += "" @@ -640,7 +594,33 @@ def generate_chat_html(history, name1, name2, reset_cache=False, last_message_on else: output = "" - # Determine range - either last message only or all messages + def create_message(role, content, raw_content): + """Inner function for WPP-style messages.""" + text_class = "text-you" if role == "user" else "text-bot" + + # Get role-specific data + timestamp = format_message_timestamp(history, role, i) + attachments = format_message_attachments(history, role, i) + + # Create info button if timestamp exists + info_message = "" + if timestamp: + tooltip_text = get_message_tooltip(history, role, i) + info_message = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') + + return ( + f'
' + f'
' + f'
{content}
' + f'{attachments}' + f'{actions_html(history, i, role, info_message)}' + f'
' + f'
' + ) + + # Determine range start_idx = len(history['visible']) - 1 if last_message_only else 0 end_idx = len(history['visible']) @@ -648,44 +628,17 @@ def generate_chat_html(history, name1, name2, reset_cache=False, last_message_on row_visible = history['visible'][i] row_internal = history['internal'][i] - # Only convert what we need + # Convert content if last_message_only: converted_visible = [None, convert_to_markdown_wrapped(row_visible[1], message_id=i, use_cache=i != len(history['visible']) - 1)] else: converted_visible = [convert_to_markdown_wrapped(entry, message_id=i, use_cache=i != len(history['visible']) - 1) for entry in row_visible] - # Get assistant timestamps and attachments (always needed) - assistant_timestamp = format_message_timestamp(history, "assistant", i) - assistant_attachments = format_message_attachments(history, "assistant", i) - - # Only compute user-related variables and generate user message when needed + # Generate messages if not last_message_only and converted_visible[0]: - user_timestamp = format_message_timestamp(history, "user", i) - user_attachments = format_message_attachments(history, "user", i) + output += create_message("user", converted_visible[0], row_internal[0]) - # Create info button for user timestamp if it exists - info_message_user = "" - if user_timestamp: - tooltip_text = get_message_tooltip(history, "user", i) - info_message_user = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') - - # Generate user message - output += generate_wpp_message_html( - "user", converted_visible[0], row_internal[0], i, - user_attachments, actions_html(history, i, "user", info_message_user) - ) - - # Create info button for assistant timestamp if it exists - info_message_assistant = "" - if assistant_timestamp: - tooltip_text = get_message_tooltip(history, "assistant", i) - info_message_assistant = info_button.replace('title="message"', f'title="{html.escape(tooltip_text)}"') - - # Generate assistant message - output += generate_wpp_message_html( - "assistant", converted_visible[1], row_internal[1], i, - assistant_attachments, actions_html(history, i, "assistant", info_message_assistant) - ) + output += create_message("assistant", converted_visible[1], row_internal[1]) if not last_message_only: output += "" From 2d049481927ea7d32d324ec78064f104df6ffe9b Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 1 Jun 2025 13:42:56 -0700 Subject: [PATCH 8/8] Change a comment --- modules/html_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/html_generator.py b/modules/html_generator.py index d47360d3..f90e3b04 100644 --- a/modules/html_generator.py +++ b/modules/html_generator.py @@ -526,7 +526,7 @@ def generate_cai_chat_html(history, name1, name2, style, character, reset_cache= else: output = "" - # Profile images + # We use ?character and ?time.time() to force the browser to reset caches img_bot = ( f'' if Path("user_data/cache/pfp_character_thumb.png").exists() else ''