text-generation-webui/modules/ui.py

503 lines
16 KiB
Python
Raw Normal View History

import copy
2025-06-07 22:46:52 -03:00
import threading
2023-03-15 12:33:26 -03:00
from pathlib import Path
import gradio as gr
import yaml
import extensions
2023-04-12 10:27:06 -03:00
from modules import shared
from modules.chat import load_history
2025-06-07 22:46:52 -03:00
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
2023-04-12 10:27:06 -03:00
with open(Path(__file__).resolve().parent / '../css/NotoSans/stylesheet.css', 'r') as f:
2023-03-15 12:33:26 -03:00
css = f.read()
with open(Path(__file__).resolve().parent / '../css/main.css', 'r') as f:
css += f.read()
with open(Path(__file__).resolve().parent / '../css/katex/katex.min.css', 'r') as f:
css += f.read()
with open(Path(__file__).resolve().parent / '../css/highlightjs/highlightjs-copy.min.css', 'r') as f:
css += f.read()
2023-08-06 21:49:27 -03:00
with open(Path(__file__).resolve().parent / '../js/main.js', 'r') as f:
2023-08-13 01:12:15 -03:00
js = f.read()
with open(Path(__file__).resolve().parent / '../js/global_scope_js.js', 'r') as f:
global_scope_js = f.read()
2023-08-06 21:49:27 -03:00
with open(Path(__file__).resolve().parent / '../js/save_files.js', 'r') as f:
save_files_js = f.read()
with open(Path(__file__).resolve().parent / '../js/switch_tabs.js', 'r') as f:
switch_tabs_js = f.read()
with open(Path(__file__).resolve().parent / '../js/show_controls.js', 'r') as f:
show_controls_js = f.read()
with open(Path(__file__).resolve().parent / '../js/update_big_picture.js', 'r') as f:
update_big_picture_js = f.read()
with open(Path(__file__).resolve().parent / '../js/dark_theme.js', 'r') as f:
dark_theme_js = f.read()
refresh_symbol = '🔄'
delete_symbol = '🗑️'
save_symbol = '💾'
2023-04-18 23:36:23 -03:00
theme = gr.themes.Default(
font=['Noto Sans', 'Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'],
2023-04-18 23:36:23 -03:00
font_mono=['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'],
).set(
border_color_primary='#c5c5d2',
button_large_padding='6px 12px',
2023-04-21 02:47:18 -03:00
body_text_color_subdued='#484848',
2024-03-13 08:18:49 -07:00
background_fill_secondary='#eaeaea',
background_fill_primary='var(--neutral-50)',
2024-06-27 20:47:42 -07:00
body_background_fill="white",
block_background_fill="#f4f4f4",
body_text_color="#333",
button_secondary_background_fill="#f4f4f4",
button_secondary_border_color="var(--border-color-primary)"
2023-04-18 23:36:23 -03:00
)
2024-12-17 00:47:41 -03:00
if not shared.args.old_colors:
theme = theme.set(
# General Colors
border_color_primary='#c5c5d2',
body_text_color_subdued='#484848',
background_fill_secondary='#eaeaea',
2024-12-17 07:59:55 -08:00
background_fill_secondary_dark='var(--selected-item-color-dark)',
2024-12-17 00:47:41 -03:00
background_fill_primary='var(--neutral-50)',
background_fill_primary_dark='var(--darker-gray)',
body_background_fill="white",
block_background_fill="transparent",
2025-05-05 06:16:11 -07:00
body_text_color='rgb(64, 64, 64)',
2024-12-17 00:47:41 -03:00
button_secondary_background_fill="#f4f4f4",
button_secondary_border_color="var(--border-color-primary)",
# Dark Mode Colors
input_background_fill_dark='var(--darker-gray)',
checkbox_background_color_dark='var(--darker-gray)',
block_background_fill_dark='transparent',
block_border_color_dark='transparent',
input_border_color_dark='var(--border-color-dark)',
2025-05-30 11:32:24 -07:00
input_border_color_focus_dark='var(--border-color-dark)',
2024-12-17 00:47:41 -03:00
checkbox_border_color_dark='var(--border-color-dark)',
border_color_primary_dark='var(--border-color-dark)',
button_secondary_border_color_dark='var(--border-color-dark)',
body_background_fill_dark='var(--dark-gray)',
button_primary_background_fill_dark='transparent',
button_secondary_background_fill_dark='transparent',
checkbox_label_background_fill_dark='transparent',
button_cancel_background_fill_dark='transparent',
button_secondary_background_fill_hover_dark='var(--selected-item-color-dark)',
checkbox_label_background_fill_hover_dark='var(--selected-item-color-dark)',
table_even_background_fill_dark='var(--darker-gray)',
2024-12-17 07:59:55 -08:00
table_odd_background_fill_dark='var(--selected-item-color-dark)',
2024-12-17 19:58:09 -08:00
code_background_fill_dark='var(--darker-gray)',
2024-12-17 00:47:41 -03:00
# Shadows and Radius
checkbox_label_shadow='none',
block_shadow='none',
block_shadow_dark='none',
2025-05-30 11:32:24 -07:00
input_shadow_focus='none',
input_shadow_focus_dark='none',
2024-12-17 00:47:41 -03:00
button_large_radius='0.375rem',
button_large_padding='6px 12px',
input_radius='0.375rem',
)
2025-04-26 08:56:54 -03:00
if Path("user_data/notification.mp3").exists():
2023-08-06 21:49:27 -03:00
audio_notification_js = "document.querySelector('#audio_notification audio')?.play();"
else:
audio_notification_js = ""
2023-05-03 21:43:17 -03:00
def list_model_elements():
elements = [
'filter_by_loader',
2025-01-10 18:04:32 -03:00
'loader',
'cpu_memory',
'gpu_layers',
'threads',
2023-10-01 21:27:04 -07:00
'threads_batch',
2025-04-18 09:59:37 -03:00
'batch_size',
'ctx_size',
2025-01-10 18:04:32 -03:00
'cache_type',
'tensor_split',
'extra_flags',
'streaming_llm',
2025-01-10 18:04:32 -03:00
'gpu_split',
'alpha_value',
'rope_freq_base',
2025-01-10 18:04:32 -03:00
'compress_pos_emb',
'compute_dtype',
'quant_type',
'num_experts_per_token',
'load_in_8bit',
'load_in_4bit',
'torch_compile',
'flash_attn',
2025-01-10 18:04:32 -03:00
'use_flash_attention_2',
'cpu',
'disk',
'row_split',
2025-04-18 09:59:37 -03:00
'no_kv_offload',
2025-01-10 18:04:32 -03:00
'no_mmap',
'mlock',
'numa',
'use_double_quant',
'use_eager_attention',
'bf16',
'autosplit',
'enable_tp',
'no_flash_attn',
'no_xformers',
'no_sdpa',
'cfg_cache',
2024-06-24 02:30:03 -03:00
'cpp_runner',
2025-01-10 18:04:32 -03:00
'trust_remote_code',
'no_use_fast',
'model_draft',
'draft_max',
'gpu_layers_draft',
'device_draft',
'ctx_size_draft',
]
2024-07-21 22:06:49 -07:00
return elements
def list_interface_input_elements():
elements = [
'temperature',
'dynatemp_low',
'dynatemp_high',
'dynatemp_exponent',
'smoothing_factor',
'smoothing_curve',
'min_p',
2025-01-10 18:04:32 -03:00
'top_p',
'top_k',
'typical_p',
2025-01-10 18:04:32 -03:00
'xtc_threshold',
'xtc_probability',
'epsilon_cutoff',
'eta_cutoff',
2025-01-10 18:04:32 -03:00
'tfs',
'top_a',
2025-03-14 16:45:11 -03:00
'top_n_sigma',
2025-01-10 18:04:32 -03:00
'dry_multiplier',
'dry_allowed_length',
'dry_base',
'repetition_penalty',
'frequency_penalty',
2025-01-10 18:04:32 -03:00
'presence_penalty',
'encoder_repetition_penalty',
'no_repeat_ngram_size',
2025-01-10 18:04:32 -03:00
'repetition_penalty_range',
'penalty_alpha',
2025-01-10 18:04:32 -03:00
'guidance_scale',
'mirostat_mode',
'mirostat_tau',
'mirostat_eta',
2025-01-10 18:04:32 -03:00
'max_new_tokens',
'prompt_lookup_num_tokens',
'max_tokens_second',
'do_sample',
'dynamic_temperature',
'temperature_last',
'auto_max_new_tokens',
'ban_eos_token',
2025-01-10 18:04:32 -03:00
'add_bos_token',
'enable_thinking',
'skip_special_tokens',
'stream',
'static_cache',
2025-01-10 18:04:32 -03:00
'truncation_length',
'seed',
'sampler_priority',
'custom_stopping_strings',
'custom_token_bans',
'negative_prompt',
'dry_sequence_breakers',
'grammar_string',
'navigate_message_index',
'navigate_direction',
'navigate_message_role',
'edit_message_index',
'edit_message_text',
'edit_message_role',
'branch_index',
2025-05-28 04:27:28 -03:00
'enable_web_search',
'web_search_pages',
]
2023-08-13 01:12:15 -03:00
# Chat elements
elements += [
'history',
2025-01-02 18:46:40 -08:00
'search_chat',
2024-07-21 00:01:42 -03:00
'unique_id',
2025-01-10 18:04:32 -03:00
'textbox',
'start_with',
'mode',
'chat_style',
'chat-instruct_command',
'character_menu',
2023-08-13 01:12:15 -03:00
'name2',
'context',
2025-01-10 18:04:32 -03:00
'greeting',
'name1',
'user_bio',
'custom_system_message',
'instruction_template_str',
'chat_template_str',
2023-08-13 01:12:15 -03:00
]
2023-08-13 01:12:15 -03:00
# Notebook/default elements
elements += [
'textbox-default',
2025-01-10 18:04:32 -03:00
'textbox-notebook',
'prompt_menu-default',
'prompt_menu-notebook',
2025-01-10 18:04:32 -03:00
'output_textbox',
2023-08-13 01:12:15 -03:00
]
# Model elements
elements += list_model_elements()
2023-08-13 01:12:15 -03:00
2023-04-12 10:27:06 -03:00
return elements
def gather_interface_values(*args):
2024-07-21 22:06:49 -07:00
interface_elements = list_interface_input_elements()
2023-04-12 10:27:06 -03:00
output = {}
2024-07-21 22:06:49 -07:00
for element, value in zip(interface_elements, args):
output[element] = value
if not shared.args.multi_user:
shared.persistent_interface_state = output
# Remove the chat input, as it gets cleared after this function call
shared.persistent_interface_state.pop('textbox')
# Prevent history loss if backend is restarted but UI is not refreshed
if output['history'] is None and output['unique_id'] is not None:
output['history'] = load_history(output['unique_id'], output['character_menu'], output['mode'])
2023-04-12 10:27:06 -03:00
return output
def apply_interface_values(state, use_persistent=False):
if use_persistent:
state = shared.persistent_interface_state
2024-12-17 00:47:41 -03:00
if 'textbox-default' in state and 'prompt_menu-default' in state:
state.pop('prompt_menu-default')
if 'textbox-notebook' in state and 'prompt_menu-notebook' in state:
state.pop('prompt_menu-notebook')
elements = list_interface_input_elements()
if len(state) == 0:
return [gr.update() for k in elements] # Dummy, do nothing
else:
2023-07-07 09:09:14 -07:00
return [state[k] if k in state else gr.update() for k in elements]
def save_settings(state, preset, extensions_list, show_controls, theme_state):
output = copy.deepcopy(shared.settings)
exclude = []
for k in state:
if k in shared.settings and k not in exclude:
output[k] = state[k]
output['preset'] = preset
output['prompt-default'] = state['prompt_menu-default']
output['prompt-notebook'] = state['prompt_menu-notebook']
output['character'] = state['character_menu']
output['default_extensions'] = extensions_list
output['seed'] = int(output['seed'])
output['show_controls'] = show_controls
output['dark_theme'] = True if theme_state == 'dark' else False
output.pop('instruction_template_str')
# Save extension values in the UI
for extension_name in extensions_list:
extension = getattr(extensions, extension_name, None)
if extension:
extension = extension.script
if hasattr(extension, 'params'):
params = getattr(extension, 'params')
for param in params:
_id = f"{extension_name}-{param}"
# Only save if different from default value
if param not in shared.default_settings or params[param] != shared.default_settings[param]:
output[_id] = params[param]
# Do not save unchanged settings
for key in list(output.keys()):
if key in shared.default_settings and output[key] == shared.default_settings[key]:
output.pop(key)
return yaml.dump(output, sort_keys=False, width=float("inf"), allow_unicode=True)
2025-06-07 22:46:52 -03:00
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(1.0, _perform_debounced_save)
2025-06-07 22:46:52 -03:00
_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',
'name2',
'context',
'greeting',
2025-06-07 22:46:52 -03:00
'user_bio',
'custom_system_message',
'chat_template_str',
# Parameters tab (ui_parameters.py) - Generation parameters
2025-06-07 22:46:52 -03:00
'preset_menu',
'temperature',
'dynatemp_low',
'dynatemp_high',
'dynatemp_exponent',
'smoothing_factor',
'smoothing_curve',
'min_p',
'top_p',
'top_k',
'typical_p',
'xtc_threshold',
'xtc_probability',
'epsilon_cutoff',
'eta_cutoff',
'tfs',
'top_a',
'top_n_sigma',
'dry_multiplier',
'dry_allowed_length',
'dry_base',
'repetition_penalty',
'frequency_penalty',
'presence_penalty',
'encoder_repetition_penalty',
'no_repeat_ngram_size',
'repetition_penalty_range',
'penalty_alpha',
'guidance_scale',
'mirostat_mode',
'mirostat_tau',
'mirostat_eta',
2025-06-07 22:46:52 -03:00
'max_new_tokens',
'prompt_lookup_num_tokens',
'max_tokens_second',
'do_sample',
'dynamic_temperature',
'temperature_last',
2025-06-07 22:46:52 -03:00
'auto_max_new_tokens',
'ban_eos_token',
'add_bos_token',
'enable_thinking',
'skip_special_tokens',
'stream',
'static_cache',
'truncation_length',
2025-06-07 22:46:52 -03:00
'seed',
'sampler_priority',
2025-06-07 22:46:52 -03:00
'custom_stopping_strings',
'custom_token_bans',
'negative_prompt',
'dry_sequence_breakers',
'grammar_string',
2025-06-07 22:46:52 -03:00
# 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)
2023-09-26 05:44:04 -07:00
def create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_class, interactive=True):
2023-07-25 15:49:04 -07:00
"""
Copied from https://github.com/AUTOMATIC1111/stable-diffusion-webui
"""
def refresh():
refresh_method()
args = refreshed_args() if callable(refreshed_args) else refreshed_args
return gr.update(**(args or {}))
2023-10-10 22:20:49 -03:00
refresh_button = gr.Button(refresh_symbol, elem_classes=elem_class, interactive=interactive)
refresh_button.click(
fn=lambda: {k: tuple(v) if type(k) is list else v for k, v in refresh().items()},
inputs=[],
outputs=[refresh_component]
)
return refresh_button