import modules.scripts as scripts import gradio as gr import os import typing import random import json from abc import ABC, abstractmethod from modules import scripts from modules.processing import process_images b_path_base = scripts.basedir() b_file_name_config = "ui-config.json" b_folder_name_scripts = "scripts" b_folder_name_script_config = "b_prompt_builder" b_file_name_layout = "layout.txt" b_file_name_presets = "presets.txt" b_tagged_ignore = False b_validate_skip = False #! unused def printWarning(type: type, name: str, message: str) -> None: print(f"WARNING/{type.__name__}/{name}: {message}") class B_Value(): def __init__(self, value_default): self.value_default = value_default self.value = self.buildDefaultValue() def buildDefaultValue(self): return self.value_default def update(self, value): self.value = value def reset(self): self.value = self.buildDefaultValue() def reinit(self, value_default, keep_current: bool = False): self.value_default = value_default if not keep_current: self.reset() class B_Prompt(ABC): class Meta(): def __init__( self , prompt_enable: bool = False , negative_enable: bool = False , prompt_negative_enable: bool = False , prompt_edit_enable: bool = False ): self.name_visible = True self.prompt_visible = prompt_enable self.prompt_enable = prompt_enable self.prompt_negative_visible = prompt_negative_enable self.prompt_negative_enable = prompt_negative_enable self.negative_visible = negative_enable self.prompt_edit_visible = prompt_edit_enable class Values(): class Defaults(): prompt: str = "" emphasis: float = 1 emphasis_min: float = 0 emphasis_step: float = 0.1 edit: int = 50 edit_min: int = 0 edit_max: int = 100 edit_step: int = 1 negative: bool = False class Keys(): prompt = "pp" prompt_negative = "pn" emphasis = "sp" emphasis_negative = "sn" negative = "n" prompt_a = "a" prompt_b = "b" edit = "r" prefix = "prefix" postfix = "postfix" @staticmethod def _fromArgs(args: dict[str, str]): prompt = args.get(B_Prompt.Values.Keys.prompt) emphasis = args.get(B_Prompt.Values.Keys.emphasis) prompt_negative = args.get(B_Prompt.Values.Keys.prompt_negative) emphasis_negative = args.get(B_Prompt.Values.Keys.emphasis_negative) negative = args.get(B_Prompt.Values.Keys.negative) edit = args.get(B_Prompt.Values.Keys.edit) return B_Prompt.Values( prompt = prompt if prompt is not None else None , emphasis = float(emphasis) if emphasis is not None else None , negative = bool(int(negative)) if negative is not None else None , prompt_negative = prompt_negative if prompt_negative is not None else None , emphasis_negative = float(emphasis_negative) if emphasis_negative is not None else None , edit = int(edit) if edit is not None else None ) @staticmethod def _updateOrResetValue(b_value: B_Value, b_value_new: B_Value, resetIfNone: bool): if b_value_new.value is not None: b_value.update(b_value_new.value) elif resetIfNone: b_value.reset() def __init__( self , prompt: str = Defaults.prompt , emphasis: float = Defaults.emphasis , negative: bool = Defaults.negative , prompt_negative: str = Defaults.prompt , emphasis_negative: float = Defaults.emphasis , prompt_a: str = Defaults.prompt , prompt_b: str = Defaults.prompt , edit: int = Defaults.edit , prefix: str = Defaults.prompt , postfix: str = Defaults.prompt ): self.prompt = B_Value(prompt) self.emphasis = B_Value(emphasis) self.negative = B_Value(negative) self.prompt_negative = B_Value(prompt_negative) self.emphasis_negative = B_Value(emphasis_negative) self.edit = B_Value(edit) self.prompt_a = B_Value(prompt_a) self.prompt_b = B_Value(prompt_b) self.prefix = B_Value(prefix) self.postfix = B_Value(postfix) def updateFromArgs(self, args: dict[str, str], resetIfNone: bool = False): if len(args) == 0: return values_new = self._fromArgs(args) if values_new is None: return self._updateOrResetValue(self.prompt, values_new.prompt, resetIfNone) self._updateOrResetValue(self.emphasis, values_new.emphasis, resetIfNone) self._updateOrResetValue(self.prompt_negative, values_new.prompt_negative, resetIfNone) self._updateOrResetValue(self.emphasis_negative, values_new.emphasis_negative, resetIfNone) self._updateOrResetValue(self.negative, values_new.negative, resetIfNone) self._updateOrResetValue(self.edit, values_new.edit, resetIfNone) class Fn(): @staticmethod def sanitized(prompt: str) -> str: return prompt.strip() if prompt is not None else "" @staticmethod def added(promptExisting: str, promptToAdd: str) -> str: if len(promptToAdd) > 0: if len(promptExisting) > 0: promptExisting += ", " + promptToAdd else: promptExisting = promptToAdd return promptExisting @staticmethod def decorated(prompt: str, prefix: str = "", postfix: str = "") -> str: if len(prompt) > 0: if len(prefix) > 0: prompt = f"{prefix} {prompt}" if len(postfix) > 0: prompt = f"{prompt} {postfix}" return prompt @staticmethod def emphasized(prompt: str, emphasis: float) -> str: if len(prompt) == 0 or emphasis == B_Prompt.Values.Defaults.emphasis_min: return "" if emphasis != 1: prompt = f"({prompt}:{emphasis})" return prompt @staticmethod @abstractmethod def _fromArgs(name: str, args: dict[str, str]): pass def __init__(self, name: str, meta: Meta, values: Values): self.name = name self.meta = meta self.values = values B_Prompt_Map.add(self) def reset(self): if self.meta.prompt_enable: self.values.prompt.reset() self.values.emphasis.reset() self.values.negative.reset() if self.meta.prompt_negative_enable: self.values.prompt_negative.reset() self.values.emphasis_negative.reset() self.values.edit.reset() #! not rendered in UI: # self.values.prompt_a.reset() # self.values.prompt_b.reset() # self.values.prefix.reset() # self.values.postfix.reset() def clear(self): if self.meta.prompt_enable: self.values.prompt.update(B_Prompt.Values.Defaults.prompt) self.values.emphasis.update(B_Prompt.Values.Defaults.emphasis) self.values.negative.update(B_Prompt.Values.Defaults.negative) if self.meta.prompt_negative_enable: self.values.prompt_negative.update(B_Prompt.Values.Defaults.prompt) self.values.emphasis_negative.update(B_Prompt.Values.Defaults.emphasis) self.values.edit.update(B_Prompt.Values.Defaults.edit) #! not rendered in UI: # self.values.prompt_a.update(B_Prompt.Values.Defaults.prompt) # self.values.prompt_b.update(B_Prompt.Values.Defaults.prompt) # self.values.prefix.update(B_Prompt.Values.Defaults.prompt) # self.values.postfix.update(B_Prompt.Values.Defaults.prompt) @abstractmethod def build(self) -> tuple[str, str]: pass class B_Prompt_Single(B_Prompt): @staticmethod def _fromArgs(name: str, args: dict[str, str]): return B_Prompt_Single( name , args.get(B_Prompt.Values.Keys.prompt, B_Prompt.Values.Defaults.prompt) , float(args.get(B_Prompt.Values.Keys.emphasis, B_Prompt.Values.Defaults.emphasis)) , bool(int(args.get(B_Prompt.Values.Keys.negative, int(B_Prompt.Values.Defaults.negative)))) , args.get(B_Prompt.Values.Keys.prefix, B_Prompt.Values.Defaults.prompt) , args.get(B_Prompt.Values.Keys.postfix, B_Prompt.Values.Defaults.prompt) ) def __init__( self , name: str , prompt = B_Prompt.Values.Defaults.prompt , emphasis = B_Prompt.Values.Defaults.emphasis , negative = B_Prompt.Values.Defaults.negative , prefix = B_Prompt.Values.Defaults.prompt , postfix = B_Prompt.Values.Defaults.prompt ): super().__init__( name , B_Prompt.Meta( prompt_enable = True , negative_enable = True ) , B_Prompt.Values( prompt = prompt , emphasis = emphasis , negative = negative , prefix = prefix , postfix = postfix ) ) def build(self) -> tuple[str, str]: prompt = B_Prompt.Fn.emphasized( B_Prompt.Fn.decorated( B_Prompt.Fn.sanitized(self.values.prompt.value) , B_Prompt.Fn.sanitized(self.values.prefix.value) , B_Prompt.Fn.sanitized(self.values.postfix.value) ) , self.values.emphasis.value ) if not self.values.negative.value: return prompt, "" else: return "", prompt class B_Prompt_Dual(B_Prompt): @staticmethod def _fromArgs(name: str, args: dict[str, str]): return B_Prompt_Dual( name , args.get(B_Prompt.Values.Keys.prompt, B_Prompt.Values.Defaults.prompt) , float(args.get(B_Prompt.Values.Keys.emphasis, B_Prompt.Values.Defaults.emphasis)) , args.get(B_Prompt.Values.Keys.prompt_negative, B_Prompt.Values.Defaults.prompt) , float(args.get(B_Prompt.Values.Keys.emphasis_negative, B_Prompt.Values.Defaults.emphasis)) ) def __init__( self , name: str , prompt = B_Prompt.Values.Defaults.prompt , emphasis = B_Prompt.Values.Defaults.emphasis , prompt_negative = B_Prompt.Values.Defaults.prompt , emphasis_negative = B_Prompt.Values.Defaults.emphasis ): super().__init__( name , B_Prompt.Meta( prompt_enable = True , prompt_negative_enable = True ) , B_Prompt.Values( prompt = prompt , emphasis = emphasis , prompt_negative = prompt_negative , emphasis_negative = emphasis_negative ) ) def build(self) -> tuple[str, str]: prompt = B_Prompt.Fn.emphasized( B_Prompt.Fn.decorated( B_Prompt.Fn.sanitized(self.values.prompt.value) , B_Prompt.Fn.sanitized(self.values.prefix.value) , B_Prompt.Fn.sanitized(self.values.postfix.value) ) , self.values.emphasis.value ) prompt_negative = B_Prompt.Fn.emphasized( B_Prompt.Fn.decorated( B_Prompt.Fn.sanitized(self.values.prompt_negative.value) , B_Prompt.Fn.sanitized(self.values.prefix.value) , B_Prompt.Fn.sanitized(self.values.postfix.value) ) , self.values.emphasis_negative.value ) return prompt, prompt_negative class B_Prompt_Edit(B_Prompt): @staticmethod def _fromArgs(name: str, args: dict[str, str]): return B_Prompt_Edit( name , args.get(B_Prompt.Values.Keys.prompt_a, B_Prompt.Values.Defaults.prompt) , args.get(B_Prompt.Values.Keys.prompt_b, B_Prompt.Values.Defaults.prompt) , int(args.get(B_Prompt.Values.Keys.edit, B_Prompt.Values.Defaults.edit)) , bool(int(args.get(B_Prompt.Values.Keys.negative, int(B_Prompt.Values.Defaults.negative)))) ) @staticmethod def _build( prompt_a: str , prompt_b: str , prefix: str , postfix: str , edit: int , negative: bool ) -> tuple[str, str]: prompt_a = B_Prompt.Fn.decorated( B_Prompt.Fn.sanitized(prompt_a) , B_Prompt.Fn.sanitized(prefix) , B_Prompt.Fn.sanitized(postfix) ) prompt_b = B_Prompt.Fn.decorated( B_Prompt.Fn.sanitized(prompt_b) , B_Prompt.Fn.sanitized(prefix) , B_Prompt.Fn.sanitized(postfix) ) value = float(edit) value = value / B_Prompt.Values.Defaults.edit_max value = round(1 - value, 2) prompt: str = None if value == 1: prompt = prompt_a elif value == 0: prompt = prompt_b else: prompt = f"[{prompt_a}:{prompt_b}:{value}]" if not negative: return prompt, "" else: return "", prompt def __init__( self , name: str , prompt_a: str , prompt_b: str , edit = B_Prompt.Values.Defaults.edit , negative = B_Prompt.Values.Defaults.negative ): super().__init__( name , B_Prompt.Meta( negative_enable = True , prompt_edit_enable = True ) , B_Prompt.Values( negative = negative , prompt_a = prompt_a , prompt_b = prompt_b , edit = edit ) ) def build(self) -> tuple[str, str]: return B_Prompt_Edit._build( self.values.prompt_a.value , self.values.prompt_b.value , self.values.prefix.value , self.values.postfix.value , self.values.edit.value , self.values.negative.value ) class B_Prompt_Edit_Link(B_Prompt): @staticmethod def _fromArgs(name: str, args: dict[str, str]): return B_Prompt_Edit_Link( name , args["link"] , args.get(B_Prompt.Values.Keys.prompt_a, B_Prompt.Values.Defaults.prompt) , args.get(B_Prompt.Values.Keys.prompt_b, B_Prompt.Values.Defaults.prompt) , bool(int(args.get(B_Prompt.Values.Keys.negative, int(B_Prompt.Values.Defaults.negative)))) ) def __init__( self , name: str , link_name: str , prompt_a: str , prompt_b: str , negative = B_Prompt.Values.Defaults.negative ): super().__init__( name , B_Prompt.Meta( negative_enable = True ) , B_Prompt.Values( negative = negative , prompt_a = prompt_a , prompt_b = prompt_b ) ) self.link_name = link_name def build(self) -> tuple[str, str]: b_prompt_link = self.getLink() if b_prompt_link is None: printWarning(type(self), f"{self.name} - build()", f"Linked prompt not found -> '{self.link_name}'") return "", "" #! return B_Prompt_Edit._build( self.values.prompt_a.value , self.values.prompt_b.value , self.values.prefix.value , self.values.postfix.value , b_prompt_link.values.edit.value , self.values.negative.value ) def getLink(self) -> B_Prompt_Edit | None: return B_Prompt_Map.get(self.link_name) class B_UI(ABC): @staticmethod @abstractmethod def _fromArgs(args: dict[str, str], name: str = None): #! hide arg pending pass def __init__(self, name: str): self.name = name def init(self) -> None: pass @abstractmethod def build(self) -> None: pass def bind(self, gr_prompt: typing.Any, gr_prompt_negative: typing.Any) -> None: pass def getGrForWebUI(self) -> list[typing.Any]: return [] def reset(self, clear: bool = False) -> None: """Reset UI and values to initial values or default values if clear is True""" pass def update(self, inputValues: tuple) -> int: return 0 def getInput(self) -> list[typing.Any]: return [] def getOutput(self) -> list[typing.Any]: return [] def getOutputUpdate(self) -> list: return [] def updateRandom(self, currentValues: tuple) -> int: offset = self.update(currentValues) self.randomize() return offset def randomize(self) -> None: pass def applyPresetMapping(self, args: dict[str, str], additive: bool): pass class B_UI_Preset(B_UI): @staticmethod def _fromArgs(args: dict[str, str], name: str = None): return B_UI_Preset( name , bool(int(args.get("is_additive", 0))) ) def __init__(self, name: str, additive: bool): super().__init__(name) self.additive = additive self.mappings: dict[str, dict[str, str]] = {} self.gr_button: typing.Any = None def build(self) -> None: self.gr_button = gr.Button(self.name) def bind(self, gr_prompt: typing.Any, gr_prompt_negative: typing.Any) -> None: b_ui_list: list[B_UI] = [] inputs: list[typing.Any] = [] outputs: list[typing.Any] = [] if self.additive: for k in self.mappings: b_ui = B_UI_Map._map[k] b_ui_list.append(b_ui) inputs += b_ui.getInput() outputs += b_ui.getOutput() else: b_ui_list = list(B_UI_Map._map.values()) inputs = B_UI_Map._inputs outputs = B_UI_Map._outputs def _apply(*inputValues): offset: int = 0 for b_ui in b_ui_list: offset += b_ui.update(inputValues[offset:]) self.apply() updates = [] for b_ui in b_ui_list: updates += b_ui.getOutputUpdate() return updates + B_Prompt_Map.buildPromptUpdate() self.gr_button.click( fn = _apply , inputs = inputs , outputs = outputs + [gr_prompt, gr_prompt_negative] ) def getGrForWebUI(self) -> list[typing.Any]: return [self.gr_button] def addMapping(self, name: str, args: dict[str, str]): if name in self.mappings: printWarning(type(self), f"{self.name} - addMapping()", f"Duplicate name -> '{name}'") self.mappings[name] = args def apply(self): if self.additive: for k in self.mappings: B_UI_Map._map[k].applyPresetMapping(self.mappings[k], True) else: for k in B_UI_Map._map: if k in self.mappings: B_UI_Map._map[k].applyPresetMapping(self.mappings[k], False) else: B_UI_Map._map[k].reset() #! class B_UI_Separator(B_UI): #! @staticmethod def _fromArgs(args: dict[str, str], name: str = "Separator"): return B_UI_Separator(name) @staticmethod def _build() -> typing.Any: return gr.Markdown(value = "
") def __init__(self, name: str = "Separator"): super().__init__(name) def build(self) -> None: B_UI_Separator._build() class B_UI_Prompt(B_UI): _prompt_scale: int = 4 _emphasis_scale: int = 1 #! @staticmethod def _fromArgs(args: dict[str, str], name: str = "Prompt"): return B_UI_Prompt(name) def __init__(self, name: str = "Prompt", b_prompt: B_Prompt = None): super().__init__(name) self.b_prompt = b_prompt self.gr_container: typing.Any = None self.gr_markdown: typing.Any = None self.gr_prompt_container: typing.Any = None self.gr_prompt: typing.Any = None self.gr_emphasis: typing.Any = None self.gr_prompt_negative_container: typing.Any = None self.gr_prompt_negative: typing.Any = None self.gr_emphasis_negative: typing.Any = None #! buttons? self.gr_slider: typing.Any = None self.gr_negative: typing.Any = None self.gr_button_apply: typing.Any = None self.gr_button_remove: typing.Any = None self.outputs_override: list[typing.Any] = [] self.fn_updates_override: typing.Callable[[], list] = None def init(self) -> None: #! activate on prompt map if self.b_prompt is not None: B_Prompt_Map.update(self.b_prompt) def build(self) -> None: meta, values, name, visible, enabled_button_remove = self.getUpdateValues() self.gr_container = gr.Column(variant = "panel", visible = visible) with self.gr_container: self.gr_markdown = gr.Markdown(value = name, visible = meta.name_visible) self.gr_prompt_container = gr.Row(visible = meta.prompt_visible) with self.gr_prompt_container: self.gr_prompt = gr.Textbox( label = "Prompt" , value = values.prompt.value , scale = B_UI_Prompt._prompt_scale , interactive = meta.prompt_enable ) self.gr_emphasis = gr.Number( label = "Emphasis" , value = values.emphasis.value , minimum = B_Prompt.Values.Defaults.emphasis_min , step = B_Prompt.Values.Defaults.emphasis_step , scale = B_UI_Prompt._emphasis_scale ) self.gr_prompt_negative_container = gr.Row(visible = meta.prompt_negative_visible) with self.gr_prompt_negative_container: self.gr_prompt_negative = gr.Textbox( label = "Prompt (N)" , value = values.prompt_negative.value , scale = B_UI_Prompt._prompt_scale , interactive = meta.prompt_negative_enable ) self.gr_emphasis_negative = gr.Number( label = "Emphasis (N)" , value = values.emphasis_negative.value , minimum = B_Prompt.Values.Defaults.emphasis_min , step = B_Prompt.Values.Defaults.emphasis_step , scale = B_UI_Prompt._emphasis_scale ) self.gr_slider = gr.Slider( label = "Edit" , value = values.edit.value , minimum = B_Prompt.Values.Defaults.edit_min , maximum = B_Prompt.Values.Defaults.edit_max , step = B_Prompt.Values.Defaults.edit_step , visible = meta.prompt_edit_visible ) self.gr_negative = gr.Checkbox( label = "Negative?" , value = values.negative.value , visible = meta.negative_visible ) B_UI_Separator._build() with gr.Row(): self.gr_button_apply = gr.Button( value = "Apply" ) self.gr_button_remove = gr.Button( value = "Remove" , interactive = enabled_button_remove ) #! register on ui map if self.b_prompt is not None: B_UI_Map.add(self) def bind(self, gr_prompt: typing.Any, gr_prompt_negative: typing.Any) -> None: def _fnBuildUpdates(remove: bool = False): B_Prompt_Map.update(self.b_prompt, remove) return ( [gr.Button(interactive = not remove)] if self.fn_updates_override is None else self.fn_updates_override() ) + B_Prompt_Map.buildPromptUpdate() outputs = ( [self.gr_button_remove] if len(self.outputs_override) == 0 else self.outputs_override ) + [gr_prompt, gr_prompt_negative] def _fnApply(*inputValues): self.update(inputValues) return _fnBuildUpdates() applyArgs = { "fn": _fnApply , "inputs": self.getInput() , "outputs": outputs } self.gr_prompt.submit(**applyArgs) self.gr_emphasis.submit(**applyArgs) self.gr_prompt_negative.submit(**applyArgs) self.gr_emphasis_negative.submit(**applyArgs) self.gr_button_apply.click(**applyArgs) # Remove def _fnRemove(): return _fnBuildUpdates(True) self.gr_button_remove.click( fn = _fnRemove , outputs = outputs ) def getGrForWebUI(self) -> list[typing.Any]: return [ self.gr_prompt , self.gr_emphasis , self.gr_negative , self.gr_prompt_negative , self.gr_emphasis_negative , self.gr_slider , self.gr_button_apply , self.gr_button_remove ] def reset(self, clear: bool = False) -> None: if not clear: self.b_prompt.reset() B_Prompt_Map.update(self.b_prompt) else: self.b_prompt.clear() B_Prompt_Map.update(self.b_prompt, True) def update(self, inputValues: tuple) -> int: offset: int = 0 prompt = str(inputValues[offset]) offset += 1 emphasis = float(inputValues[offset]) offset += 1 prompt_negative = str(inputValues[offset]) offset += 1 emphasis_negative = float(inputValues[offset]) offset += 1 edit = int(inputValues[offset]) offset += 1 negative = bool(inputValues[offset]) offset += 1 if self.b_prompt is not None: self.b_prompt.values.prompt.update(prompt) self.b_prompt.values.emphasis.update(emphasis) self.b_prompt.values.negative.update(negative) self.b_prompt.values.prompt_negative.update(prompt_negative) self.b_prompt.values.emphasis_negative.update(emphasis_negative) self.b_prompt.values.edit.update(edit) return offset def getInput(self) -> list[typing.Any]: return [ self.gr_prompt , self.gr_emphasis , self.gr_prompt_negative , self.gr_emphasis_negative , self.gr_slider , self.gr_negative ] def getOutput(self) -> list[typing.Any]: return [ self.gr_container , self.gr_markdown , self.gr_prompt_container , self.gr_prompt , self.gr_emphasis , self.gr_prompt_negative_container , self.gr_prompt_negative , self.gr_emphasis_negative , self.gr_slider , self.gr_negative , self.gr_button_remove ] def getOutputUpdate(self) -> list: meta, values, name, visible, enabled_button_remove = self.getUpdateValues() return [ gr.Column(visible = visible) , gr.Markdown(value = name, visible = meta.name_visible) , gr.Row(visible = meta.prompt_visible) , gr.Textbox(value = values.prompt.value, interactive = meta.prompt_enable) , values.emphasis.value , gr.Row(visible = meta.prompt_negative_visible) , gr.Textbox(value = values.prompt_negative.value, interactive = meta.prompt_negative_enable) , values.emphasis_negative.value , gr.Slider(visible = meta.prompt_edit_visible, value = values.edit.value, step = B_Prompt.Values.Defaults.edit_step) , gr.Checkbox(visible = meta.negative_visible, value = values.negative.value) , gr.Button(interactive = enabled_button_remove) ] def getUpdateValues(self) -> tuple[B_Prompt.Meta, B_Prompt.Values, str, bool, bool]: if self.b_prompt is not None: return ( self.b_prompt.meta , self.b_prompt.values , f"**{self.b_prompt.name}**" , True , B_Prompt_Map.isSelected(self.b_prompt) ) else: return ( B_Prompt.Meta() , B_Prompt.Values() , f"**{self.name}**" , False , False ) def applyPresetMapping(self, args: dict[str, str], additive: bool): if self.b_prompt is None: return self.b_prompt.values.updateFromArgs(args, not additive) class B_UI_Dropdown(B_UI): #_choice_empty: str = "-" _choice_random_count_max: int = 5 @staticmethod def _fromArgs(args: dict[str, str], name: str = "Dropdown"): return B_UI_Dropdown( name , bool(int(args.get("sort", 1))) , B_UI_Dropdown._fromArgsValue(args) , args.get(B_Prompt.Values.Keys.prefix, B_Prompt.Values.Defaults.prompt) , args.get(B_Prompt.Values.Keys.postfix, B_Prompt.Values.Defaults.prompt) , int(args.get("scale", 0)) ) @staticmethod def _fromArgsValue(args: dict[str, str]): valueMap: dict[str, dict[str, str]] = {} if len(args) > 0: choicesWithPromptArgs = list(filter(lambda v: len(v) > 0, map(lambda v: v.strip(), args.get("v", "").split(",")))) for x in choicesWithPromptArgs: promptArgsMap: dict[str, str] = {} x_split = x.split("::") choice = x_split[0] if len(x_split) > 1: promptArgs = x_split[1].split("|") for promptArg in promptArgs: arg_name = promptArg[:promptArg.index(" ")] arg_value = promptArg[len(arg_name) + 1:].strip() promptArgsMap[arg_name] = arg_value valueMap[choice] = promptArgsMap return valueMap @staticmethod def _buildColorChoicesList(postfix: str = "") -> list[B_Prompt_Single]: return list(map( lambda text: B_Prompt_Single( f"{text} {postfix}" , text.lower() , postfix = postfix ) , [ "Dark" , "Light" , "Black" , "Grey" , "White" , "Brown" , "Blue" , "Green" , "Red" , "Blonde" , "Rainbow" , "Pink" , "Purple" , "Orange" , "Yellow" , "Multicolored" , "Pale" , "Silver" , "Gold" , "Tan" , "Two tone" ] )) def __init__( self , name: str = "Dropdown" , sort_choices: bool = True , b_prompts_applied_default: dict[str, dict[str, str]] = None , prefix = B_Prompt.Values.Defaults.prompt , postfix = B_Prompt.Values.Defaults.prompt , scale: int = 1 , b_prompts: list[B_Prompt] = None ): super().__init__(name) self.b_prompts_applied_default = b_prompts_applied_default if b_prompts_applied_default is not None else {} self.sort_choices = sort_choices self.prefix = prefix self.postfix = postfix self.scale = scale if scale > 0 else 1 self.choice_list: list[B_Prompt] = [] if b_prompts is not None: for b_prompt in b_prompts: self.addChoice(b_prompt) self.choice_map: dict[str, B_Prompt] = {} self.choice_preset_map: dict[str, B_UI_Preset] = {} self.gr_dropdown: typing.Any = None self.gr_remove: typing.Any = None self.gr_buttons_container: typing.Any = None self.gr_buttons: list[typing.Any] = [] self.b_prompt_ui = B_UI_Prompt(f"{self.name} (Prompt)") def init(self) -> None: # Self # - sort choices if self.sort_choices: self.choice_list = sorted(self.choice_list, key = lambda b_prompt: b_prompt.name) # - build map for b_prompt in self.choice_list: self.choice_map[b_prompt.name] = b_prompt # - apply default choices #! for k in self.b_prompts_applied_default: b_prompt = self.choice_map[k] b_prompt.values.updateFromArgs(self.b_prompts_applied_default[k]) B_Prompt_Map.update(b_prompt) # Prompt UI self.b_prompt_ui.init() def build(self) -> None: with gr.Column(scale = self.scale): # Self self.gr_dropdown = gr.Dropdown( label = self.name , choices = list(map(lambda b_prompt: b_prompt.name, self.choice_list)) , multiselect = True , value = list(self.b_prompts_applied_default.keys()) , allow_custom_value = False ) self.gr_buttons_container = gr.Row(variant = "panel", visible = self.initButtonContainerVisible()) with self.gr_buttons_container: for b_prompt in self.choice_list: self.gr_buttons.append( gr.Button( value = b_prompt.name , variant = "primary" , size = "sm" , visible = B_Prompt_Map.isSelected(b_prompt) ) ) # Prompt UI self.b_prompt_ui.build() #! register on map B_UI_Map.add(self) def bind(self, gr_prompt: typing.Any, gr_prompt_negative: typing.Any) -> None: # Self b_ui_names_presets: set[str] = set() for b_ui_preset in self.choice_preset_map.values(): for k in b_ui_preset.mappings: b_ui_names_presets.add(k) inputs_presets: list[typing.Any] = [] outputs_presets: list = [] for k in b_ui_names_presets: inputs_presets += B_UI_Map._map[k].getInput() outputs_presets += B_UI_Map._map[k].getOutput() def _updateSelections(choices: str | list[str], *input_values_presets): selected_choices: list[str] = choices if issubclass(type(choices), list) else [choices] for b_prompt in self.choice_list: B_Prompt_Map.update(b_prompt, b_prompt.name not in selected_choices) offset_presets: int = 0 for k in b_ui_names_presets: offset_presets += B_UI_Map._map[k].update(input_values_presets[offset_presets:]) for k in selected_choices: preset = self.choice_preset_map.get(k) if preset is not None: preset.apply() if not B_Prompt_Map.isSelected(self.b_prompt_ui.b_prompt): self.b_prompt_ui.b_prompt = None updates: list = [self.getPromptButtonContainerUpdate()] + self.getPromptButtonUpdates() + self.b_prompt_ui.getOutputUpdate() for k in b_ui_names_presets: updates += B_UI_Map._map[k].getOutputUpdate() updates += B_Prompt_Map.buildPromptUpdate() return updates self.gr_dropdown.input( fn = _updateSelections , inputs = [self.gr_dropdown] + inputs_presets , outputs = [self.gr_buttons_container] + self.gr_buttons + self.b_prompt_ui.getOutput() + outputs_presets + [gr_prompt, gr_prompt_negative] ) # - Show/hide prompt ui if len(self.gr_buttons) > 0: outputs_prompt_ui = self.b_prompt_ui.getOutput() def _fnSelect(k: str): if self.b_prompt_ui.b_prompt is not None and self.b_prompt_ui.b_prompt.name == k: self.b_prompt_ui.b_prompt = None else: self.b_prompt_ui.b_prompt = self.choice_map.get(k) return self.b_prompt_ui.getOutputUpdate() for gr_button in self.gr_buttons: gr_button.click( fn = _fnSelect , inputs = gr_button , outputs = outputs_prompt_ui ) # Prompt UI #!!! self.b_prompt_ui.outputs_override = [self.gr_dropdown, self.gr_buttons_container] + self.gr_buttons + self.b_prompt_ui.getOutput() def _fnUpdatesOverride(): self.b_prompt_ui.b_prompt = None #! return [self.initChoicesSelected(), self.getPromptButtonContainerUpdate()] + self.getPromptButtonUpdates() + self.b_prompt_ui.getOutputUpdate() self.b_prompt_ui.fn_updates_override = _fnUpdatesOverride self.b_prompt_ui.bind(gr_prompt, gr_prompt_negative) def getGrForWebUI(self) -> list[typing.Any]: return [self.gr_dropdown] + self.gr_buttons + self.b_prompt_ui.getGrForWebUI() def reset(self, clear: bool = False) -> None: for b_prompt in self.choice_list: if not clear: b_prompt.reset() b_prompt_args = self.b_prompts_applied_default.get(b_prompt.name) if b_prompt_args is not None: b_prompt.values.updateFromArgs(b_prompt_args, True) B_Prompt_Map.update(b_prompt, b_prompt_args is None) else: b_prompt.clear() B_Prompt_Map.update(b_prompt, True) self.b_prompt_ui.b_prompt = None def update(self, inputValues: tuple) -> int: return self.b_prompt_ui.update(inputValues) def randomize(self) -> None: if len(self.choice_list) > 0: choices: list[str] = list(self.choice_map.keys()) choices_selected: list[str] = [] c_max = len(choices) if self._choice_random_count_max > 0 and self._choice_random_count_max < c_max: c_max = self._choice_random_count_max r = random.randint(0, c_max) if r > 0: for c in range(r): i = random.randint(0, len(choices) - 1) choices_selected.append(choices.pop(i)) for b_prompt in self.choice_map.values(): B_Prompt_Map.update(b_prompt, b_prompt.name not in choices_selected) def getInput(self) -> list[typing.Any]: return self.b_prompt_ui.getInput() def getOutput(self) -> list[typing.Any]: return [self.gr_dropdown, self.gr_buttons_container] + self.gr_buttons + self.b_prompt_ui.getOutput() def getOutputUpdate(self) -> list: return [self.initChoicesSelected(), self.getPromptButtonContainerUpdate()] + self.getPromptButtonUpdates() + self.b_prompt_ui.getOutputUpdate() def getPromptButtonContainerUpdate(self): return gr.Row(visible = self.initButtonContainerVisible()) def getPromptButtonUpdates(self) -> list: updates = [] i: int = 0 for b_prompt in self.choice_list: updates.append(gr.Button(visible = B_Prompt_Map.isSelected(b_prompt))) i += 1 return updates def initChoicesSelected(self): choices_selected: list[str] = [] for b_prompt in self.choice_list: if B_Prompt_Map.isSelected(b_prompt): choices_selected.append(b_prompt.name) return choices_selected def initButtonContainerVisible(self): return any(map(lambda b_prompt: B_Prompt_Map.isSelected(b_prompt), self.choice_list)) #! optimize? def addChoice(self, item: B_Prompt): if len(item.values.prefix.value) == 0: item.values.prefix.reinit(self.prefix) if len(item.values.postfix.value) == 0: item.values.postfix.reinit(self.postfix) item.meta.name_visible = False item.meta.prompt_enable = True item.meta.prompt_negative_enable = True self.choice_list.append(item) def addChoicePresetMapping(self, target_name: str, target_args: dict[str, str]): b_prompt = self.choice_list[-1] preset = self.choice_preset_map.get(b_prompt.name, None) if preset is None: preset = B_UI_Preset(f"{self.name}_{b_prompt.name} (PRESET)", True) self.choice_preset_map[b_prompt.name] = preset preset.addMapping(target_name, target_args) def addChoices(self, args: dict[str, str]): b_prompt_list: list[B_Prompt] = None special_type = args.get("type", "") match special_type: case "COLOR": postfix = args.get(B_Prompt.Values.Keys.postfix, "") b_prompt_list = self._buildColorChoicesList(postfix) case "": printWarning(type(self), self.name, f"No CHOICES type specified") case _: printWarning(type(self), self.name, f"Invalid CHOICES type -> '{special_type}'") if b_prompt_list is not None: for b_prompt in b_prompt_list: self.addChoice(b_prompt) #!!! def applyPresetMapping(self, args: dict[str, str], additive: bool): valueMap = B_UI_Dropdown._fromArgsValue(args) for b_prompt in self.choice_list: b_prompt_value_args = valueMap.get(b_prompt.name) if b_prompt_value_args is not None: b_prompt.values.updateFromArgs(b_prompt_value_args, not additive) B_Prompt_Map.update(b_prompt, b_prompt_value_args is None) class B_UI_Container(B_UI, ABC): def __init__(self, name: str, build_button_reset: bool = False, build_button_random: bool = False, children: list[B_UI] = None): super().__init__(name) self.build_button_reset = build_button_reset self.build_button_random = build_button_random self.children = children if children is not None else [] self.gr_container: typing.Any = None self.gr_reset: typing.Any = None self.gr_random: typing.Any = None def init(self) -> None: for b_ui in self.children: b_ui.init() def build(self) -> None: self.gr_container = self.buildContainer() with self.gr_container: for b_ui in self.children: b_ui.build() if self.build_button_reset or self.build_button_random: def _buildReset(): self.gr_reset = gr.Button(f"Reset {self.name}") def _buildRandomize(): self.gr_random = gr.Button(f"Randomize {self.name}") B_UI_Separator._build() if self.build_button_reset and self.build_button_random: with gr.Row(): _buildRandomize() _buildReset() elif self.build_button_random: _buildRandomize() else: _buildReset() def bind(self, gr_prompt: typing.Any, gr_prompt_negative: typing.Any) -> None: # Children for b_ui in self.children: b_ui.bind(gr_prompt, gr_prompt_negative) # Self # - Reset if self.build_button_reset: def _reset(): for b_ui in self.children: b_ui.reset() return B_Prompt_Map.buildPromptUpdate() + self.getOutputUpdate() self.gr_reset.click( fn = _reset , outputs = [gr_prompt, gr_prompt_negative] + self.getOutput() ) # - Randomize if self.build_button_random: def _randomize(*currentValues): self.updateRandom(currentValues) return B_Prompt_Map.buildPromptUpdate() + self.getOutputUpdate() self.gr_random.click( fn = _randomize , inputs = self.getInput() , outputs = [gr_prompt, gr_prompt_negative] + self.getOutput() ) def getGrForWebUI(self) -> list[typing.Any]: gr_list: list[typing.Any] = [] if self.build_button_random: gr_list.append(self.gr_random) if self.build_button_reset: gr_list.append(self.gr_reset) for b_ui in self.children: gr_list += b_ui.getGrForWebUI() return gr_list def reset(self, clear: bool = False) -> None: for b_ui in self.children: b_ui.reset(clear) def update(self, inputValues: tuple) -> int: offset: int = 0 for b_ui in self.children: offset += b_ui.update(inputValues[offset:]) return offset def randomize(self) -> None: for b_ui in self.children: b_ui.randomize() def getInput(self) -> list[typing.Any]: gr_inputs: list[typing.Any] = [] for b_ui in self.children: gr_inputs += b_ui.getInput() return gr_inputs def getOutput(self) -> list[typing.Any]: gr_outputs: list[typing.Any] = [] for b_ui in self.children: gr_outputs += b_ui.getOutput() return gr_outputs def getOutputUpdate(self) -> list: gr_updates = [] for b_ui in self.children: gr_updates += b_ui.getOutputUpdate() return gr_updates def addChild(self, item: B_UI): self.children.append(item) @abstractmethod def buildContainer(self) -> typing.Any: pass class B_UI_Container_Tab(B_UI_Container): @staticmethod def _fromArgs(args: dict[str, str], name: str = "Tab"): return B_UI_Container_Tab( name , bool(int(args.get("build_button_reset", 1))) , bool(int(args.get("build_button_random", 1))) ) def __init__(self, name: str = "Tab", build_button_reset: bool = True, build_button_random: bool = True, children: list[B_UI] = None): super().__init__(name, build_button_reset, build_button_random, children) def buildContainer(self) -> typing.Any: return gr.Tab(self.name) class B_UI_Container_Row(B_UI_Container): @staticmethod def _fromArgs(args: dict[str, str], name: str = "Row"): return B_UI_Container_Row( name , bool(int(args.get("build_button_reset", 0))) , bool(int(args.get("build_button_random", 0))) ) def __init__(self, name: str = "Row", build_button_reset: bool = False, build_button_random: bool = False, children: list[B_UI] = None): super().__init__(name, build_button_reset, build_button_random, children) def buildContainer(self) -> typing.Any: return gr.Row() class B_UI_Container_Column(B_UI_Container): @staticmethod def _fromArgs(args: dict[str, str], name: str = "Column"): return B_UI_Container_Column( name , int(args.get("scale", 1)) , bool(int(args.get("build_button_reset", 0))) , bool(int(args.get("build_button_random", 0))) ) def __init__(self, name: str = "Column", scale: int = 1, build_button_reset: bool = False, build_button_random: bool = False, children: list[B_UI] = None): super().__init__(name, build_button_reset, build_button_random, children) self.scale = scale def buildContainer(self) -> typing.Any: return gr.Column(scale = self.scale) class B_UI_Container_Accordion(B_UI_Container): @staticmethod def _fromArgs(args: dict[str, str], name: str = "Accordion"): return B_UI_Container_Accordion( name , bool(int(args.get("open", 0))) , bool(int(args.get("build_button_reset", 0))) , bool(int(args.get("build_button_random", 0))) ) def __init__(self, name: str = "Accordion", init_open: bool = False, build_button_reset: bool = False, build_button_random: bool = False, children: list[B_UI] = None): super().__init__(name, build_button_reset, build_button_random, children) self.init_open = init_open def buildContainer(self) -> typing.Any: return gr.Accordion(label = self.name, open = self.init_open) class B_UI_Container_Group(B_UI_Container): @staticmethod def _fromArgs(args: dict[str, str], name: str = "Group"): return B_UI_Container_Group( name , bool(int(args.get("build_button_reset", 0))) , bool(int(args.get("build_button_random", 0))) ) def __init__(self, name: str = "Group", build_button_reset: bool = False, build_button_random: bool = False, children: list[B_UI] = None): super().__init__(name, build_button_reset, build_button_random, children) def buildContainer(self) -> typing.Any: return gr.Group() class B_Prompt_Map(): _map: dict[str, tuple[B_Prompt, bool]] = {} @staticmethod def add(b_prompt: B_Prompt): if b_prompt.name in B_Prompt_Map._map: printWarning(B_Prompt_Map, "add()", f"Duplicate name -> '{b_prompt.name}'") B_Prompt_Map._map[b_prompt.name] = b_prompt, False @staticmethod def update(b_prompt: B_Prompt | None, remove: bool = False): if b_prompt is None: return B_Prompt_Map._map[b_prompt.name] = b_prompt, not remove @staticmethod def get(b_prompt_name: str) -> B_Prompt | None: mapping = B_Prompt_Map._map.get(b_prompt_name) return mapping[0] if mapping is not None else None @staticmethod def isSelected(b_prompt: B_Prompt | None): return b_prompt is not None and B_Prompt_Map._map[b_prompt.name][1] @staticmethod def buildPromptUpdate() -> list[str]: prompt: str = "" prompt_negative: str = "" for b_prompt, selected in B_Prompt_Map._map.values(): if not selected: continue b_prompt_positive, b_prompt_negative = b_prompt.build() prompt = B_Prompt.Fn.added(prompt, b_prompt_positive) prompt_negative = B_Prompt.Fn.added(prompt_negative, b_prompt_negative) return [prompt, prompt_negative] class B_UI_Map(): _map: dict[str, B_UI] = {} _inputs: list[typing.Any] = [] _outputs: list[typing.Any] = [] @staticmethod def add(b_ui: B_UI): if b_ui.name in B_UI_Map._map: printWarning(B_UI_Map, "add()", f"Duplicate name -> '{b_ui.name}'") B_UI_Map._map[b_ui.name] = b_ui B_UI_Map._inputs += b_ui.getInput() B_UI_Map._outputs += b_ui.getOutput() @staticmethod def getInput(): inputs = [] for b_ui in B_UI_Map._map.values(): inputs += b_ui.getInput() return inputs @staticmethod def getOutput(): outputs = [] for b_ui in B_UI_Map._map.values(): outputs += b_ui.getOutput() return outputs @staticmethod def consumeInputValues(inputValues: tuple): offset = 0 for b_ui in B_UI_Map._map.values(): offset += b_ui.update(inputValues[offset:]) @staticmethod def getOutputUpdates(): updates = [] for b_ui in B_UI_Map._map.values(): updates += b_ui.getOutputUpdate() return updates class B_UI_Master(): @staticmethod def readLine(l: str) -> tuple[str, str, dict[str, str]]: #! TODO: Fix empty str l_name l = l.strip() l_type: str = l l_name: str = None l_args: dict[str, str] = {} if len(l) > 0: index = l.find(" ") if index != -1: l_type = l[:index] l = l[len(l_type) + 1:] l_arg_index = l.find("--") if l_arg_index == -1: l_name = l elif l_arg_index > 0: l_name = l[:l_arg_index - 1 if l_arg_index > -1 else len(l)] l = l[len(l_name) + 1:] l_args = {} for l_arg in l.split("--")[1:]: l_arg_name = l_arg[:l_arg.index(" ")] l_arg_value = l_arg[len(l_arg_name) + 1:].strip() l_args[l_arg_name] = l_arg_value return l_type, l_name, l_args def __init__(self, layout: list[B_UI] = None): self.path_script_config = os.path.join(b_path_base, b_folder_name_scripts, b_folder_name_script_config) self.path_layout = os.path.join(self.path_script_config, b_file_name_layout) self.path_presets = os.path.join(self.path_script_config, b_file_name_presets) self.layout = (layout if layout is not None else []) + self.parseLayout() self.presets = self.parsePresets() for b_ui in self.layout + self.presets: b_ui.init() #! validate self.gr_prompt: typing.Any = None self.gr_prompt_negative: typing.Any = None self.gr_apply: typing.Any = None self.gr_remove: typing.Any = None self.gr_clear: typing.Any = None self.gr_reset: typing.Any = None self.gr_clear_config: typing.Any def parseLayout(self) -> list[B_UI]: layout: list[B_UI] = [] stack_containers: list[B_UI_Container] = [] stack_dropdowns: list[B_UI_Dropdown] = [] dropdown_choice_has_preset: bool = False skip = 0 def _buildPrompt(item: B_Prompt): if len(stack_dropdowns) > 0: stack_dropdowns[-1].addChoice(item) return _build(B_UI_Prompt(item.name, item)) def _build(item: B_UI): if len(stack_containers) > 0: stack_containers[-1].addChild(item) return layout.append(item) with open(self.path_layout) as file_layout: line_number: int = 0 for l in file_layout: line_number += 1 if l.lstrip().startswith("#"): print(f"# LAYOUT - commented out line @{line_number}") continue l_type, l_name, l_args = self.readLine(l) if len(l_type) == 0: continue if l_type == ".": break if l_type == "END": if skip == 0: if dropdown_choice_has_preset: dropdown_choice_has_preset = False continue if len(stack_dropdowns) > 0: item_dropdown = stack_dropdowns.pop() _build(item_dropdown) continue if len(stack_containers) > 0: item_container = stack_containers.pop() _build(item_container) continue continue skip -= 1 continue ignore: bool = skip > 0 if l_args.get("x", "") == "1": if not ignore: ignore = b_tagged_ignore match l_type: case "SINGLE": if ignore: continue _buildPrompt(B_Prompt_Single._fromArgs(l_name, l_args)) case "DUAL": if ignore: continue _buildPrompt(B_Prompt_Dual._fromArgs(l_name, l_args)) case "EDIT": if ignore: continue _buildPrompt(B_Prompt_Edit._fromArgs(l_name, l_args)) case "EDIT_LINK": if ignore: continue _buildPrompt(B_Prompt_Edit_Link._fromArgs(l_name, l_args)) case "SELECT": if ignore: skip += 1 continue stack_dropdowns.append(B_UI_Dropdown._fromArgs(l_args, l_name)) case "CHOICES": if ignore: continue stack_dropdowns[-1].addChoices(l_args) #! case "SET": dropdown_choice_has_preset = True stack_dropdowns[-1].addChoicePresetMapping(l_name, l_args) case "GROUP": if ignore: skip += 1 continue stack_containers.append(B_UI_Container_Group._fromArgs(l_args, l_name)) case "TAB": if ignore: skip += 1 continue stack_containers.append(B_UI_Container_Tab._fromArgs(l_args, l_name)) case "ROW": if ignore: skip += 1 continue stack_containers.append(B_UI_Container_Row._fromArgs(l_args, l_name)) case "COLUMN": if ignore: skip += 1 continue stack_containers.append(B_UI_Container_Column._fromArgs(l_args, l_name)) case "ACCORDION": if ignore: skip += 1 continue stack_containers.append(B_UI_Container_Accordion._fromArgs(l_args, l_name)) case "SEPARATOR": if ignore: continue _build(B_UI_Separator._fromArgs(l_args)) case _: printWarning(type(self), "parseLayout()", f"Invalid layout type -> '{l_type}'") return layout def parsePresets(self) -> list[B_UI_Preset]: presets: list[B_UI_Preset] = [] preset_current: B_UI_Preset = None with open(self.path_presets) as file_presets: line_number: int = 0 for l in file_presets: line_number += 1 if l.lstrip().startswith("#"): print(f"# PRESETS - commented out line @{line_number}") continue l_type, l_name, l_args = self.readLine(l) if len(l_type) == 0: continue if l_type == ".": break if l_type == "END": presets.append(preset_current) preset_current = None continue match l_type: case "PRESET": preset_current = B_UI_Preset._fromArgs(l_args, l_name) case "SET": preset_current.addMapping(l_name, l_args) case _: printWarning(type(self), "parsePresets()", f"Invalid preset type -> '{l_type}'") return presets def build(self): # PRESETS B_UI_Separator._build() with gr.Accordion("Presets", open = False): i = 0 for preset in self.presets: preset.build() i += 1 if i < len(self.presets): B_UI_Separator._build() # LAYOUT B_UI_Separator._build() for b_ui in self.layout: b_ui.build() # MAIN B_UI_Separator._build() prompt = B_Prompt_Map.buildPromptUpdate() self.gr_prompt = gr.Textbox(label = "Final Prompt", value = prompt[0]) self.gr_prompt_negative = gr.Textbox(label = "Final Negative Prompt", value = prompt[1]) B_UI_Separator._build() with gr.Row(): self.gr_apply = gr.Button("Apply All") self.gr_remove = gr.Button("Remove All") B_UI_Separator._build() with gr.Row(): self.gr_clear = gr.Button("Clear All") self.gr_reset = gr.Button("Reset All") # EXTRAS B_UI_Separator._build() with gr.Accordion("Settings", open = False): self.gr_clear_config = gr.Button("Clear config") def bind(self) -> None: # - Presets for preset in self.presets: preset.bind(self.gr_prompt, self.gr_prompt_negative) # - Layout for b_ui in self.layout: b_ui.bind(self.gr_prompt, self.gr_prompt_negative) # - Self inputs = B_UI_Map._inputs outputs = B_UI_Map._outputs + [self.gr_prompt, self.gr_prompt_negative] #! def _fnApply(*inputValues): B_UI_Map.consumeInputValues(inputValues) for b_ui in B_UI_Map._map.values(): if type(b_ui) is B_UI_Prompt and b_ui.b_prompt is not None: B_Prompt_Map.update(b_ui.b_prompt) return B_UI_Map.getOutputUpdates() + B_Prompt_Map.buildPromptUpdate() self.gr_apply.click( fn = _fnApply , inputs = inputs , outputs = outputs ) #! def _fnRemove(*inputValues): B_UI_Map.consumeInputValues(inputValues) for b_prompt, selected in B_Prompt_Map._map.values(): B_Prompt_Map.update(b_prompt, True) return B_UI_Map.getOutputUpdates() + B_Prompt_Map.buildPromptUpdate() self.gr_remove.click( fn = _fnRemove , inputs = inputs , outputs = outputs ) #! def _fnReset(): for b_ui in B_UI_Map._map.values(): b_ui.reset() return B_UI_Map.getOutputUpdates() + B_Prompt_Map.buildPromptUpdate() self.gr_reset.click( fn = _fnReset , outputs = outputs ) #! def _fnClear(): for b_ui in B_UI_Map._map.values(): b_ui.reset(True) return B_UI_Map.getOutputUpdates() + B_Prompt_Map.buildPromptUpdate() self.gr_clear.click( fn = _fnClear , outputs = outputs ) #! Would be better if the original config file dump function is used somehow: def _fnClearConfigFile(): path = os.path.join(b_path_base, b_file_name_config) with open(path, "r+", encoding = "utf-8") as file_config: config: dict[str, typing.Any] = json.load(file_config) config_keys = filter(lambda k: k.find(b_folder_name_script_config) == -1, config.keys()) config_new: dict[str, typing.Any] = {} for k in config_keys: config_new[k] = config[k] file_config.seek(0) json.dump(config_new, file_config, indent = 4) file_config.truncate() return gr.Button(interactive = False) self.gr_clear_config.click( fn = _fnClearConfigFile , outputs = self.gr_clear_config ) def ui(self) -> list[typing.Any]: self.build() self.bind() gr_list: list[typing.Any] = [ self.gr_prompt , self.gr_prompt_negative , self.gr_apply , self.gr_remove , self.gr_clear , self.gr_reset , self.gr_clear_config ] for b_ui in self.layout + self.presets: gr_list += b_ui.getGrForWebUI() return gr_list #: Webui script class Script(scripts.Script): b_ui_master = B_UI_Master() def title(self): return "B Prompt Builder" def show(self, is_img2img): return not is_img2img #! def ui(self, is_img2img): return self.b_ui_master.ui() def run( self , p , prompt: str , prompt_negative: str , *outputValues ): p.prompt = B_Prompt.Fn.added(p.prompt, prompt) p.negative_prompt = B_Prompt.Fn.added(p.negative_prompt, prompt_negative) proc = process_images(p) return proc