├── README.md └── script.py /README.md: -------------------------------------------------------------------------------- 1 | # FPreloader (now with Attribute monitor) 2 | 3 | [](https://ko-fi.com/Q5Q5MOB4M) 4 | 5 | An extension that will make your life as an extension developer this much easier > ----- < 6 | 7 | FPreloader will HARD reload all your other extensions. It's like that one ring that rules the other rings, or something on that note. 8 | For developers and other strange people. 9 | 10 | for Python > 3.4 11 | 12 | How it works: 13 | ``` 14 | cd PATH_TO_text-generation-webui/extensions 15 | ``` 16 | then clone this repo 17 | ``` 18 | git clone https://github.com/FartyPants/FPreloader 19 | ``` 20 | 21 | Start ooba and in Interface enable FPReloader 22 | 23 |  24 | 25 | Apply and restart interface, now you should see tab FPReloader 26 | 27 |  28 | 29 | Anytime you press the big red button, your extensions will be reloaded (AKA if you made changes to your script.py file, the changes should be reloaded) then Gradio will be restarted 30 | ... or you can do it in two steps: Reload Extensions, then Restart Gradio 31 | (depending on the size of the extension there needs to be a slight time for python to recompile your modified version, the red button assumes 2.5 sec is enough as default) 32 | 33 | ## Nested imports (or whatever they are called) 34 | 35 |  36 | 37 | Use the Deep Reload for reloading all nested imports within the extensions (for example in superbooga chromadb or download_urls will reload as well before the script itself) 38 | 39 | ## Debug View Options 40 | allows you to see attributes etc... 41 | 42 |  43 | 44 | Attribute watch - type attribute of currently viewed module you want to see, instead of all. Comma delimited 45 | 46 |  47 | 48 | Dictionaries - you can type the exact dictionary key (you don't need to use ' or " for keys) 49 | 50 |  51 | 52 | The monitor doesn't update by itself, when the value change you need to press refresh. 53 | 54 | ## Tips 55 | ``` 56 | Task Module View Attribute Watch 57 | _____________________________________________________________________ 58 | Get python version sys version, _base_executable 59 | Arguments sys argv 60 | 61 | ``` 62 | ## Monkey stuff 63 | Detour sorting of LORA and Models so the dropdown displays the recently added models/lora first 64 | Detour allowing LORA to display and use checkpoints in the LORA menu 65 | 66 |  67 | 68 | 69 | ## Additions 70 | List imported python modules 71 |  72 | 73 | 74 | If nothing of this makes any sense then you are in the wrong repo. 75 | -------------------------------------------------------------------------------- /script.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import gradio as gr 3 | import modules.shared as shared 4 | from pathlib import Path 5 | import modules.extensions as extensions_module 6 | import sys 7 | import importlib 8 | import time 9 | import inspect 10 | from modules import utils 11 | import os 12 | import json 13 | from sys import version_info 14 | import modules.training as training 15 | 16 | file_nameJSON = "FPreloader.json" 17 | 18 | params = { 19 | "display_name": "FPreloader", 20 | "is_tab": True, 21 | "timeout": 2.5, 22 | "LORAsubs": False, 23 | "LORATime": False, 24 | "MODELTime": False, 25 | "additional":'' 26 | 27 | } 28 | 29 | original_get_available_loras = utils.get_available_loras 30 | original_get_available_models = utils.get_available_models 31 | loaded_extens = [] 32 | 33 | attribute_watch = [] 34 | 35 | Lora_sortedByTime=False 36 | Lora_witSubs=False 37 | 38 | # currently displayed extension in the data view 39 | current_extension = '' 40 | 41 | refresh_symbol = '\U0001f504' # 🔄 42 | 43 | class ToolButton(gr.Button, gr.components.FormComponent): 44 | """Small button with single emoji as text, fits inside gradio forms""" 45 | 46 | def __init__(self, **kwargs): 47 | super().__init__(variant="tool", **kwargs) 48 | 49 | def get_block_name(self): 50 | return "button" 51 | 52 | 53 | 54 | def clean_path(base_path: str, path: str): 55 | """Strips unusual symbols and forcibly builds a path as relative to the intended directory.""" 56 | # TODO: Probably could do with a security audit to guarantee there's no ways this can be bypassed to target an unwanted path. 57 | # Or swap it to a strict whitelist of [a-zA-Z_0-9] 58 | path = path.replace('\\', '/').replace('..', '_') 59 | if base_path is None: 60 | return path 61 | 62 | return f'{Path(base_path).absolute()}/{path}' 63 | 64 | def process_extens(): 65 | global loaded_extens 66 | 67 | loaded_extens.clear() 68 | 69 | loaded_extens.append("[sys.modules]") 70 | result = '' 71 | for i, name in enumerate(shared.args.extensions): 72 | if name in extensions_module.available_extensions: 73 | 74 | #ext = f"extensions.{name}.script" 75 | loaded_extens.append(name) 76 | 77 | if name != 'api': 78 | print(f'Extension "{name}"...') 79 | result+=name+', ' 80 | 81 | 82 | return result 83 | 84 | def reload(full_name): 85 | if full_name in sys.modules: 86 | print(f"Reloading module: \033[1;31;1m{full_name}\033[0;37;0m") 87 | importlib.reload(sys.modules[full_name]) 88 | 89 | 90 | def reload_extens(): 91 | 92 | result ='Reloaded :' 93 | 94 | for i, name in enumerate(shared.args.extensions): 95 | if name in extensions_module.available_extensions: 96 | if name != 'api': 97 | extension = f"extensions.{name}.script" 98 | 99 | if extension != "extensions.FPreloader.script": 100 | reload(extension) 101 | result+='['+name+'] ' 102 | 103 | additional = params['additional'] 104 | if additional: 105 | additional_array = additional.split(",") 106 | additional_array = [item.strip() for item in additional_array] 107 | for item in additional_array: 108 | if item in sys.modules: 109 | reload(item) 110 | result+='['+item+'] ' 111 | 112 | 113 | return result 114 | 115 | def process_allmodules(): 116 | 117 | names = [] 118 | for i, name in enumerate(shared.args.extensions): 119 | if name in extensions_module.available_extensions: 120 | if name != 'api': 121 | names.append(name+'.') 122 | 123 | result='' 124 | for mod in sys.modules: 125 | if any(name in mod for name in names): 126 | result+=mod+'\n' 127 | 128 | return result 129 | 130 | def reload_extensAll(): 131 | 132 | names = [] 133 | for i, name in enumerate(shared.args.extensions): 134 | if name in extensions_module.available_extensions: 135 | if name != 'api': 136 | names.append(name+'.') 137 | 138 | result='' 139 | extensions = [] 140 | for mod in sys.modules: 141 | if any(name in mod for name in names): 142 | if mod != "extensions.FPreloader.script": 143 | extensions.append(mod) 144 | 145 | 146 | for extension in extensions: 147 | reload(extension) 148 | result+='['+name+'] ' 149 | 150 | return result 151 | 152 | 153 | 154 | def wait_recomp(): 155 | time.sleep(params['timeout']) 156 | shared.need_restart = True 157 | 158 | def gradio_restart(): 159 | shared.need_restart = True 160 | 161 | def display_module(ext_module,module_name): 162 | 163 | global attribute_watch 164 | lines = '' 165 | if len(attribute_watch) > 0: 166 | lines = f"# Attributes in {module_name}\n" 167 | ext_module_items = dir(ext_module) 168 | for item in attribute_watch: 169 | itemstr = f"{item}" 170 | key_str = '' 171 | # Find the index positions of '[' and ']' 172 | start_index = itemstr.find('[') 173 | end_index = itemstr.find(']') 174 | 175 | if start_index != -1 and end_index != -1: 176 | # Square brackets found 177 | param = itemstr[:start_index] 178 | key_str = itemstr[start_index + 1 : end_index] 179 | key_str = key_str.strip() 180 | key_str = key_str.replace('\"','') 181 | key_str = key_str.replace('\'','') 182 | 183 | itemstr = param 184 | else: 185 | # Square brackets not found 186 | key_str = "" 187 | 188 | line = f"{itemstr}:\n" 189 | if itemstr in ext_module_items: 190 | if not callable(getattr(ext_module, itemstr)): 191 | value = getattr(ext_module, itemstr) 192 | # value is dictionaries 193 | if isinstance(value, dict): 194 | line =line+ "{\n" 195 | for key,val in value.items(): 196 | valstr = f'{val}' 197 | if isinstance(val, str): 198 | valstr = valstr.replace('\n','\\n') 199 | valstr = valstr.replace("'","\\'") 200 | valstr = "'"+valstr+"'" 201 | 202 | if key_str: 203 | #display only the desired key 204 | keynew = f"{key}" 205 | if key_str==keynew: 206 | line =line+ f"..., \'{key}\': {valstr}\n" 207 | else: 208 | line =line+ f" \'{key}\': {valstr},\n" 209 | line =line+ "}\n" 210 | else: 211 | valstr = f'{value}' 212 | if isinstance(value, str): 213 | valstr = valstr.replace('\n','\\n') 214 | valstr = valstr.replace("'","\\'") 215 | valstr = "'"+valstr+"'" 216 | 217 | line = f"{itemstr}: {valstr}\n\n" 218 | lines = lines+line 219 | else: 220 | line = f"{itemstr}: --none--\n\n" 221 | lines = lines+line 222 | 223 | return lines 224 | 225 | lines = lines +'#----Attributes:----\n' 226 | ext_module_items = dir(ext_module) 227 | for item in ext_module_items: 228 | if not callable(getattr(ext_module, item)) and not item.startswith('__') and not item=='gradio': 229 | value = getattr(ext_module, item) 230 | type_str = f"{type(value).__name__}" 231 | value_str = f"{value}" 232 | if type_str=='str': 233 | value_str = "'"+value_str+"'" 234 | if type_str: 235 | type_str = "("+type_str+") " 236 | 237 | line = f"{item}: {type_str}{value_str}\n" 238 | #line = f"{item}: {value}\n" 239 | lines = lines+line 240 | 241 | lines = lines+ '#----Functions:----\n' 242 | 243 | for item in ext_module_items: 244 | obj = getattr(ext_module, item) 245 | try: 246 | if inspect.isfunction(obj): 247 | signature = inspect.signature(obj) 248 | #parameters = list(signature.parameters.keys()) 249 | #parameters_str = ', '.join(parameters) 250 | line = f"{item}{signature}\n" 251 | lines = lines+line 252 | except Exception as e: 253 | print(f"Error occurred while inspecting {item}: {str(e)}") 254 | 255 | 256 | lines = lines+ '#----Classes:----\n' 257 | for item in ext_module_items: 258 | obj = getattr(ext_module, item) 259 | if inspect.isclass(obj): 260 | line = f"{item}\n" 261 | lines = lines+line 262 | 263 | return lines 264 | 265 | 266 | 267 | def radio_change(selected_extension): 268 | global current_extension 269 | 270 | if selected_extension=="[sys.modules]": 271 | current_extension = '[sys.modules]' 272 | return modulenames() 273 | 274 | extension = f"extensions.{selected_extension}.script" 275 | 276 | textout = '' 277 | current_extension = '' 278 | if extension in sys.modules: 279 | current_extension = extension 280 | ext_module = sys.modules[extension] 281 | 282 | textout = display_module(ext_module,extension) 283 | 284 | 285 | #textout = f"{ext_module}" 286 | 287 | return textout 288 | 289 | def custom_module(module,selected_extension): 290 | global current_extension 291 | textout = '' 292 | if module=='': 293 | textout = radio_change(selected_extension) 294 | return textout 295 | 296 | current_extension = '' 297 | if module in sys.modules: 298 | ext_module = sys.modules[module] 299 | current_extension = module 300 | textout = display_module(ext_module,module) 301 | else: 302 | textout = f"Module {module} does not exist." 303 | 304 | return textout 305 | 306 | def modulenames(): 307 | global current_extension 308 | module_names = list(sys.modules.keys()) 309 | lines = '' 310 | current_extension = '[sys.modules]' 311 | lines = f"# All imported modules\n" 312 | 313 | if len(attribute_watch) > 0: 314 | for module_name in module_names: 315 | for item in attribute_watch: 316 | itemstr = f"{item}" 317 | if module_name.startswith(itemstr): 318 | lines += f"{module_name}\n" 319 | 320 | return lines 321 | 322 | grouped_modules = {} 323 | 324 | grouped_modules["0stock_import"] = [] 325 | grouped_modules["_0stock_import"] = [] 326 | for module_name in module_names: 327 | parts = module_name.split('.') 328 | prefix = parts[0] # Use the first part as the prefix 329 | if len(parts)==1: 330 | if prefix.startswith('_'): 331 | grouped_modules["_0stock_import"].append(module_name) 332 | else: 333 | grouped_modules["0stock_import"].append(module_name) 334 | else: 335 | if prefix in grouped_modules: 336 | grouped_modules[prefix].append(module_name) 337 | else: 338 | grouped_modules[prefix] = [module_name] 339 | 340 | grouped_modules["0stock_import"] = sorted(grouped_modules["0stock_import"]) 341 | grouped_modules["_0stock_import"] = sorted(grouped_modules["_0stock_import"]) 342 | 343 | sorted_keys = sorted(grouped_modules.items()) 344 | # Print the grouped modules 345 | lines = "# Grouped imported modules\n" 346 | 347 | for prefix, modules in sorted_keys: 348 | line = f"{', '.join(modules)}\n" 349 | lines += line 350 | 351 | return lines 352 | 353 | def attributewatch(attribs): 354 | global attribute_watch 355 | if attribs: 356 | attribute_watch = [item.strip() for item in attribs.split(',')] 357 | else: 358 | attribute_watch = [] 359 | 360 | extension = current_extension 361 | if extension=='[sys.modules]': 362 | return modulenames() 363 | 364 | if extension in sys.modules: 365 | ext_module = sys.modules[extension] 366 | ext_module = sys.modules[extension] 367 | textout = display_module(ext_module,extension) 368 | else: 369 | textout = f"Module {extension} does not exist." 370 | 371 | return textout 372 | 373 | def get_available_lorasProper(): 374 | return sorted([item.name for item in list(Path(shared.args.lora_dir).glob('*')) if not item.name.endswith(('.txt', '-np', '.pt', '.json'))], key=utils.natural_keys) 375 | 376 | def list_subfolders2(directory, subdir): 377 | subfolders = [] 378 | for entry in os.scandir(directory): 379 | if entry.is_dir() and entry.name != 'runs': 380 | newdir = f"{subdir}/{entry.name}" 381 | subfolders.append(newdir) 382 | 383 | return sorted(subfolders, key=utils.natural_keys) 384 | 385 | 386 | def list_subfoldersROOT(directory): 387 | subfolders = [] 388 | for entry in os.scandir(directory): 389 | if entry.is_dir(): 390 | newdir = f"{directory}/{entry.name}" 391 | subfolders.append(entry.name) 392 | subfolders = subfolders+list_subfolders2(newdir,entry.name) 393 | 394 | 395 | return sorted(subfolders, key=utils.natural_keys) 396 | 397 | 398 | def sorted_ls(path): 399 | mtime = lambda f: os.stat(os.path.join(path, f)).st_mtime 400 | return list(sorted(os.listdir(path), key=mtime)) 401 | 402 | def list_subfoldersByTime(directory,isSubfolders): 403 | 404 | if not directory.endswith('/'): 405 | directory += '/' 406 | subfolders = [] 407 | path = directory 408 | name_list = os.listdir(path) 409 | full_list = [os.path.join(path,i) for i in name_list] 410 | time_sorted_list = sorted(full_list, key=os.path.getmtime,reverse=True) 411 | 412 | for entry in time_sorted_list: 413 | if os.path.isdir(entry): 414 | entry_str = f"{entry}" # Convert entry to a string 415 | full_path = entry_str 416 | entry_str = entry_str.replace('\\','/') 417 | entry_str = entry_str.replace(f"{directory}", "") # Remove directory part 418 | subfolders.append(entry_str) 419 | if isSubfolders: 420 | subfolders = subfolders+ list_subfolders2(full_path,entry_str) 421 | 422 | return subfolders 423 | 424 | 425 | def get_available_loras_monkey(): 426 | print("[FP] LORA Detour Activated") 427 | model_dir = shared.args.lora_dir # Update with the appropriate directory path 428 | subfolders = [] 429 | if Lora_sortedByTime: 430 | subfolders = list_subfoldersByTime(model_dir,Lora_witSubs) 431 | else: 432 | subfolders = list_subfoldersROOT(model_dir) 433 | 434 | 435 | return subfolders 436 | 437 | def get_available_models_monkey(): 438 | print("[FP] MODELS Detour Activated") 439 | model_dir = shared.args.model_dir # Update with the appropriate directory path 440 | subfolders = [] 441 | subfolders = list_subfoldersByTime(model_dir, False) 442 | 443 | return subfolders 444 | 445 | def save_PRAMS(): 446 | return 447 | # try: 448 | # global params 449 | # with open(file_nameJSON, 'w') as json_file: 450 | # json.dump(params, json_file,indent=2) 451 | # #print(f"Saved: {file_nameJSON}") 452 | # except IOError as e: 453 | # print(f"An error occurred while saving the file: {e}") 454 | 455 | def update_monkey_detour_internal(bEnableSubs,bEnableTimeSort): 456 | global Lora_sortedByTime 457 | global Lora_witSubs 458 | Lora_witSubs = bEnableSubs 459 | Lora_sortedByTime = bEnableTimeSort 460 | 461 | if bEnableSubs or bEnableTimeSort: 462 | utils.get_available_loras = get_available_loras_monkey 463 | print(f"[FP] LoRA Subfolders: {Lora_witSubs}, Sorted by Time: {Lora_sortedByTime}") 464 | else: 465 | utils.get_available_loras = original_get_available_loras 466 | print("[FP] LoRA Detour Deactivated") 467 | 468 | 469 | def update_monkey_detour(bEnableSubs,bEnableTimeSort): 470 | update_monkey_detour_internal(bEnableSubs,bEnableTimeSort) 471 | params.update({"LORAsubs": bEnableSubs}) 472 | params.update({"LORATime": bEnableTimeSort}) 473 | save_PRAMS() 474 | 475 | def update_monkey_detour_models_internal(bEnableMonkey): 476 | if bEnableMonkey: 477 | utils.get_available_models = get_available_models_monkey 478 | print(f"[FP] Models Sorted by Time") 479 | else: 480 | utils.get_available_models = original_get_available_models 481 | print("[FP] Models Detour Deactivated") 482 | 483 | 484 | def update_monkey_detour_models(bEnableMonkey): 485 | update_monkey_detour_models_internal(bEnableMonkey) 486 | params.update({"MODELTime": bEnableMonkey}) 487 | save_PRAMS() 488 | 489 | def colored(r, g, b, text): 490 | return f"\033[38;2;{r};{g};{b}m{text}\033[0m" 491 | #print(colored(255, 0, 0, 'Hello, World!')) 492 | #coloured = lambda r, g, b, text: f"\033[38;2;{r};{g};{b}m{text}\033[38;2;255;255;255m" 493 | train_choices = ["All Modules","Attention Layers","Only Q and V layers"] 494 | 495 | def ui(): 496 | 497 | from peft.utils.other import \ 498 | TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING as \ 499 | model_to_lora_modules 500 | 501 | global train_choices 502 | # try: 503 | # with open(file_nameJSON, 'r') as json_file: 504 | # new_params = json.load(json_file) 505 | # for item in new_params: 506 | # params[item] = new_params[item] 507 | # except FileNotFoundError: 508 | # params.update({"MODELTime": False}) 509 | # 510 | # if params['MODELTime']: 511 | # update_monkey_detour_models_internal(params['MODELTime']) 512 | # 513 | # if params['LORAsubs'] or params['LORATime']: 514 | # update_monkey_detour_internal(params['LORAsubs'],params['LORATime']) 515 | 516 | all_modules = ["gate_proj","down_proj","up_proj","q_proj","k_proj","v_proj","o_proj"] 517 | all_attention = ["q_proj","k_proj", "v_proj", "o_proj"] 518 | standard_QV = ["q_proj", "v_proj"] 519 | 520 | selected_train = train_choices[2] 521 | 522 | if model_to_lora_modules["llama"]==all_modules: 523 | selected_train = train_choices[0] 524 | if model_to_lora_modules["llama"]==all_attention: 525 | selected_train = train_choices[1] 526 | 527 | 528 | modelview = f"{shared.model}" 529 | obj_class = type(shared.model) 530 | objclass_pr = f"{obj_class}" 531 | 532 | print (f"\033[1;31;1m\nFPreloader ready\033[0;37;0m - Python {version_info[0]}.{version_info[1]}.{version_info[2]}") 533 | 534 | with gr.Accordion("FartyPants Extensions Reloader", open=True): 535 | 536 | with gr.Row(): 537 | extensions_box = gr.Textbox(label='Loaded Extensions',value = process_extens()) 538 | gr_fetch = gr.Button('[Refresh]', elem_classes="small-button") 539 | with gr.Row(): 540 | gr_additional = gr.Textbox(label='Additional modules ( ex: modules.training )',interactive=True, value=params['additional']) 541 | with gr.Row(): 542 | gr_reload = gr.Button(value='Reload All Extensions + Restart Gradio', variant='stop') 543 | with gr.Row(): 544 | gr_reloadonly = gr.Button(value='Reload Extensions') 545 | gr_restart = gr.Button(value='Restart Gradio') 546 | with gr.Accordion("Deep Reload", open=False): 547 | with gr.Row(): 548 | allmodules = gr.Textbox(label='Extensions + Nested Imports',value = 'Press [Refresh] to see the list') 549 | allmodules_fetch = gr.Button('[Refresh]', elem_classes="small-button") 550 | with gr.Row(): 551 | gr_reloadAll = gr.Button(value='Reload All Extensions and Nested Imports + Restart Gradio', variant='stop') 552 | 553 | with gr.Accordion("FartyPants Debugger", open=False): 554 | with gr.Row(): 555 | with gr.Column(scale=1): 556 | gr_refresh = gr.Button(value='Refresh') 557 | class_p = gr.Textbox(label='Class: shared.model', value=objclass_pr) 558 | preview = gr.Code(label='shared.model', lines=10, value=modelview,language="python") 559 | with gr.Column(scale=3): 560 | gr_refresh3 = gr.Button(value='Refresh') 561 | with gr.Row(): 562 | with gr.Column(scale=2): 563 | gr_radio= gr.Radio(choices=loaded_extens, value='None',label='Extensions') 564 | with gr.Row(): 565 | with gr.Column(scale=2): 566 | gr_attrWatch = gr.Textbox(label='Attribute Watch (comma delimited)', lines=1, value='',info="Ex: params, params['display_name'], __builtins__ etc...") 567 | with gr.Column(scale=1 ): 568 | gr_Refresh4 = gr.Button(value="Refresh") 569 | gr_Clear = gr.Button(value="Clear") 570 | with gr.Column(scale=1): 571 | with gr.Row(): 572 | with gr.Column(): 573 | gr_customMod = gr.Textbox(label='Module View',info="Enter name of module, ex: modules.shared", lines=1, value='modules.LoRA') 574 | with gr.Row(): 575 | gr_custApp = gr.Button(value="View Module") 576 | gr_custApp2 = gr.Button(value="Back") 577 | 578 | preview3 = gr.Code(label='module', lines=4, value="# Data View\n",language="python") 579 | with gr.Accordion("FartyPants Monkey Bussines", open=False): 580 | with gr.Row(): 581 | with gr.Column(): 582 | monkey_detour = gr.Checkbox(value = params['LORAsubs'], label='List LoRA + Checkpoints', info='When enabled, the LoRA menu will also shows all nested checkpoints') 583 | monkey_TimeSort = gr.Checkbox(value = params['LORATime'], label='Sort LoRA by recently created', info='When enabled, the LoRA menu will be sorted by time with newest LoRA(s) first') 584 | monkey_TimeSortMod = gr.Checkbox(value = params['MODELTime'], label='Sort MODELS by recently added', info='When enabled, the MODELS menu will be sorted by time with newest models first') 585 | monkey_Training = gr.Radio(value = selected_train, label='Traing Target Modules', info='Change the LLaMA training target modules', choices=train_choices) 586 | with gr.Accordion("Settings", open=True): 587 | with gr.Row(): 588 | with gr.Column(): 589 | timeout = gr.Slider(0.0, 5.0, value=params['timeout'], step=0.5, label='Timeout (seconds)', info='Timeout between Reload and Restart Gradio (Waiting for recompile)') 590 | with gr.Column(): 591 | gr.Markdown('v.07/04/2023') 592 | gr.Markdown('https://github.com/FartyPants/FPreloader') 593 | 594 | 595 | def sliderchange(slider): # SelectData is a subclass of EventData 596 | params['timeout'] = slider 597 | 598 | timeout.change(sliderchange,timeout,None) 599 | 600 | allmodules_fetch.click(process_allmodules, None,allmodules) 601 | gr_reloadAll.click(reload_extensAll,None,allmodules).then( 602 | lambda: None, None, None, _js='() => {document.body.innerHTML=\'