diff --git a/modules/shared.py b/modules/shared.py index f712f7f8..2e500779 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -35,6 +35,8 @@ settings = { 'mode': 'instruct', 'chat_style': 'cai-chat', 'chat-instruct_command': 'Continue the chat dialogue below. Write a single reply for the character "<|character|>".\n\n<|prompt|>', + 'enable_web_search': False, + 'web_search_pages': 3, 'prompt-default': 'QA', 'prompt-notebook': 'QA', 'character': 'Assistant', diff --git a/modules/ui.py b/modules/ui.py index 89bbbdb6..db3adf0f 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1,4 +1,5 @@ import copy +import threading from pathlib import Path import gradio as gr @@ -7,6 +8,16 @@ import yaml import extensions from modules import shared from modules.chat import load_history +from modules.utils import gradio + +# Global state for auto-saving UI settings with debouncing +_auto_save_timer = None +_auto_save_lock = threading.Lock() +_last_interface_state = None +_last_preset = None +_last_extensions = None +_last_show_controls = None +_last_theme_state = None with open(Path(__file__).resolve().parent / '../css/NotoSans/stylesheet.css', 'r') as f: css = f.read() @@ -334,6 +345,101 @@ def save_settings(state, preset, extensions_list, show_controls, theme_state): return yaml.dump(output, sort_keys=False, width=float("inf"), allow_unicode=True) +def store_current_state_and_debounce(interface_state, preset, extensions, show_controls, theme_state): + """Store current state and trigger debounced save""" + global _auto_save_timer, _last_interface_state, _last_preset, _last_extensions, _last_show_controls, _last_theme_state + + if shared.args.multi_user: + return + + # Store the current state in global variables + _last_interface_state = interface_state + _last_preset = preset + _last_extensions = extensions + _last_show_controls = show_controls + _last_theme_state = theme_state + + # Reset the debounce timer + with _auto_save_lock: + if _auto_save_timer is not None: + _auto_save_timer.cancel() + + _auto_save_timer = threading.Timer(2.0, _perform_debounced_save) + _auto_save_timer.start() + + +def _perform_debounced_save(): + """Actually perform the save using the stored state""" + global _auto_save_timer + + try: + if _last_interface_state is not None: + contents = save_settings(_last_interface_state, _last_preset, _last_extensions, _last_show_controls, _last_theme_state) + settings_path = Path('user_data') / 'settings.yaml' + settings_path.parent.mkdir(exist_ok=True) + with open(settings_path, 'w', encoding='utf-8') as f: + f.write(contents) + except Exception as e: + print(f"Auto-save failed: {e}") + finally: + with _auto_save_lock: + _auto_save_timer = None + + +def setup_auto_save(): + """Attach auto-save to key UI elements""" + if shared.args.multi_user: + return + + change_elements = [ + # Chat tab (ui_chat.py) + 'start_with', + 'enable_web_search', + 'web_search_pages', + 'mode', + 'chat_style', + 'chat-instruct_command', + 'character_menu', + 'name1', + 'user_bio', + 'custom_system_message', + 'chat_template_str', + + # Parameters tab (ui_parameters.py) + 'preset_menu', + 'max_new_tokens', + 'prompt_lookup_num_tokens', + 'max_tokens_second', + 'auto_max_new_tokens', + 'ban_eos_token', + 'add_bos_token', + 'enable_thinking', + 'skip_special_tokens', + 'stream', + 'static_cache', + 'seed', + 'custom_stopping_strings', + 'custom_token_bans', + 'negative_prompt', + + # Default tab (ui_default.py) + 'prompt_menu-default', + + # Notebook tab (ui_notebook.py) + 'prompt_menu-notebook', + + # Session tab (ui_session.py) + 'show_controls', + 'theme_state', + ] + + for element_name in change_elements: + if element_name in shared.gradio: + shared.gradio[element_name].change( + gather_interface_values, gradio(shared.input_elements), gradio('interface_state')).then( + store_current_state_and_debounce, gradio('interface_state', 'preset_menu', 'extensions_menu', 'show_controls', 'theme_state'), None, show_progress=False) + + def create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_class, interactive=True): """ Copied from https://github.com/AUTOMATIC1111/stable-diffusion-webui diff --git a/modules/ui_session.py b/modules/ui_session.py index a4eba667..4ed740cd 100644 --- a/modules/ui_session.py +++ b/modules/ui_session.py @@ -10,11 +10,10 @@ def create_ui(): with gr.Tab("Session", elem_id="session-tab"): with gr.Row(): with gr.Column(): - shared.gradio['reset_interface'] = gr.Button("Apply flags/extensions and restart", interactive=not mu) with gr.Row(): shared.gradio['toggle_dark_mode'] = gr.Button('Toggle 💡') - shared.gradio['save_settings'] = gr.Button('Save UI defaults to user_data/settings.yaml', interactive=not mu) + shared.gradio['reset_interface'] = gr.Button("Apply flags/extensions and restart", interactive=not mu) with gr.Row(): with gr.Column(): shared.gradio['extensions_menu'] = gr.CheckboxGroup(choices=utils.get_available_extensions(), value=shared.args.extensions, label="Available extensions", info='Note that some of these extensions may require manually installing Python requirements through the command: pip install -r extensions/extension_name/requirements.txt', elem_classes='checkboxgroup-table') @@ -42,20 +41,6 @@ def create_ui(): lambda x: 'dark' if x == 'light' else 'light', gradio('theme_state'), gradio('theme_state')).then( None, None, None, js=f'() => {{{ui.dark_theme_js}; toggleDarkMode()}}') - shared.gradio['save_settings'].click( - ui.gather_interface_values, gradio(shared.input_elements), gradio('interface_state')).then( - handle_save_settings, gradio('interface_state', 'preset_menu', 'extensions_menu', 'show_controls', 'theme_state'), gradio('save_contents', 'save_filename', 'save_root', 'file_saver'), show_progress=False) - - -def handle_save_settings(state, preset, extensions, show_controls, theme): - contents = ui.save_settings(state, preset, extensions, show_controls, theme) - return [ - contents, - "settings.yaml", - "user_data/", - gr.update(visible=True) - ] - def set_interface_arguments(extensions, bool_active): shared.args.extensions = extensions diff --git a/server.py b/server.py index 99d2e171..3be4c27c 100644 --- a/server.py +++ b/server.py @@ -150,6 +150,9 @@ def create_interface(): ui_parameters.create_event_handlers() ui_model_menu.create_event_handlers() + # UI persistence events + ui.setup_auto_save() + # Interface launch events shared.gradio['interface'].load( None,