diff --git a/html/locale_en.json b/html/locale_en.json index 88a3d6203..c785cc937 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -24,7 +24,7 @@ {"id":"xyz_grid_x_list","label":"⊜","localized":"","hint":"Fill","ui":"script_xyz_grid_script"}, {"id":"txt2img_caption_output","label":"","localized":"","hint":"Caption image","ui":"txt2img"}, {"id":"txt2img_image_fit","label":"⁜","localized":"","hint":"Cycle image fit method","ui":"txt2img"}, - {"id":"","label":"➠ Control","localized":"","hint":"Transfer image to control interface","ui":"txt2img"}, + {"id":"","label":"➠ Control","localized":"","hint":"Transfer image to control interface.

Right-click this button to transfer only the prompt or all generation parameters to the Images tab without sending the image itself.","ui":"txt2img"}, {"id":"","label":"➠ Text","localized":"","hint":"Transfer image to text interface","ui":"txt2img"}, {"id":"","label":"➠ Image","localized":"","hint":"Transfer image to image interface","ui":"txt2img"}, {"id":"","label":"➠ Process","localized":"","hint":"Transfer image to process interface","ui":"txt2img"}, diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index ec92275ec..245a63d3f 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -67,8 +67,9 @@ const contextMenuInit = () => { if (oldMenu) oldMenu.remove(); menuSpecs.forEach((v, k) => { const items = v.filter((item) => item.primary); - if (items.length > 0 && e.composedPath()[0].matches(k)) { - showContextMenu(e, e.composedPath()[0], items); + const matched = e.target.closest(k); + if (items.length > 0 && matched) { + showContextMenu(e, matched, items); e.preventDefault(); } }); @@ -78,8 +79,9 @@ const contextMenuInit = () => { if (oldMenu) oldMenu.remove(); menuSpecs.forEach((v, k) => { const items = v.filter((item) => !item.primary); - if (items.length > 0 && e.composedPath()[0].matches(k)) { - showContextMenu(e, e.composedPath()[0], items); + const matched = e.target.closest(k); + if (items.length > 0 && matched) { + showContextMenu(e, matched, items); e.preventDefault(); } }); @@ -161,5 +163,13 @@ async function initContextMenu() { appendContextMenuOption(id, 'Refine & HiRes pass', () => reprocessClick(`${tab}`, 'reprocess_refine'), true); appendContextMenuOption(id, 'Detailer pass', () => reprocessClick(`${tab}`, 'reprocess_detail'), true); } + // Right-click send-to-control button for prompt/params-only transfer. + // Scoped per tab because #control_tab and its paired #control_tab_prompt / + // #control_tab_params elements are duplicated across output panels. + for (const tab of ['gallery', 'txt2img', 'img2img', 'extras']) { + id = `#${tab}_tabitem #control_tab`; + appendContextMenuOption(id, 'Transfer only prompt to Images tab', () => document.querySelector(`#image_buttons_${tab} #control_tab_prompt`)?.click()); + appendContextMenuOption(id, 'Transfer all parameters to Images tab', () => document.querySelector(`#image_buttons_${tab} #control_tab_params`)?.click()); + } addContextMenuEventListener(); } diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 171af66fa..9d55f13b9 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -24,7 +24,7 @@ param_aliases: dict[str, str] = {} class ParamBinding: - def __init__(self, paste_button, tabname: str, source_text_component=None, source_image_component=None, source_tabname=None, override_settings_component=None, paste_field_names=None): + def __init__(self, paste_button, tabname: str, source_text_component=None, source_image_component=None, source_tabname=None, override_settings_component=None, paste_field_names=None, skip_image: bool = False, only_prompt: bool = False): self.paste_button = paste_button self.tabname = tabname self.source_text_component = source_text_component @@ -32,6 +32,8 @@ class ParamBinding: self.source_tabname = source_tabname self.override_settings_component = override_settings_component self.paste_field_names = paste_field_names or [] + self.skip_image = skip_image + self.only_prompt = only_prompt # debug(f'ParamBinding: {vars(self)}') @@ -142,23 +144,26 @@ def get_all_fields(): return all_fields -def create_buttons(tabs_list: list[str]) -> dict[str, gr.Button]: +def create_buttons(tabs_list: list[str], label_prefix: str = "➠", label_override: str | None = None, id_suffix: str = "") -> dict[str, gr.Button]: buttons = {} for tab in tabs_list: - name = tab - if name == 'txt2img': - name = 'Text' - elif name == 'img2img': - name = 'Image' - elif name == 'inpaint': - name = 'Inpaint' - elif name == 'extras': - name = 'Process' - elif name == 'control': - name = 'Control' - elif name == 'caption': - name = 'Caption' - buttons[tab] = gr.Button(f"➠ {name}", elem_id=f"{tab}_tab") + if label_override is not None: + display = label_override + else: + display = tab + if display == 'txt2img': + display = 'Text' + elif display == 'img2img': + display = 'Image' + elif display == 'inpaint': + display = 'Inpaint' + elif display == 'extras': + display = 'Process' + elif display == 'control': + display = 'Control' + elif display == 'caption': + display = 'Caption' + buttons[tab] = gr.Button(f"{label_prefix} {display}", elem_id=f"{tab}_tab{id_suffix}") return buttons @@ -198,7 +203,7 @@ def connect_paste_params_buttons(): fields: list[tuple[gr.components.Component, str]] = paste_fields[binding.tabname]["fields"] destination_image_component = paste_fields[binding.tabname]["init_img"] - if binding.source_image_component: + if binding.source_image_component and not binding.skip_image: if isinstance(destination_image_component, gr.Image): binding.paste_button.click( _js="extract_image_from_gallery" if isinstance(binding.source_image_component, gr.Gallery) else None, @@ -216,10 +221,18 @@ def connect_paste_params_buttons(): show_progress='hidden', ) override_settings_component = binding.override_settings_component or paste_fields[binding.tabname]["override_settings_component"] - if binding.source_text_component is not None and fields is not None: - connect_paste(binding.paste_button, fields, binding.source_text_component, override_settings_component, binding.tabname) + if binding.only_prompt: + override_settings_component = None + text_fields = fields + if binding.only_prompt and fields is not None: + text_fields = [(component, key) for component, key in fields if key in ('Prompt', 'Negative prompt')] + if binding.source_text_component is not None and text_fields is not None: + connect_paste(binding.paste_button, text_fields, binding.source_text_component, override_settings_component, binding.tabname) if binding.source_tabname is not None and fields is not None and binding.source_tabname in paste_fields: - paste_field_names = ['Prompt', 'Negative prompt', 'Steps'] + (["Seed"] if shared.opts.send_seed else []) + binding.paste_field_names + if binding.only_prompt: + paste_field_names = ['Prompt', 'Negative prompt'] + else: + paste_field_names = ['Prompt', 'Negative prompt', 'Steps'] + (["Seed"] if shared.opts.send_seed else []) + binding.paste_field_names if "fields" in paste_fields[binding.source_tabname] and paste_fields[binding.source_tabname]["fields"] is not None: binding.paste_button.click( fn=lambda *x: x, diff --git a/modules/ui_common.py b/modules/ui_common.py index e4957e1df..4f758909c 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -299,8 +299,16 @@ def create_output_panel(tabname, preview=True, prompt=None, height=None, transfe delete = gr.Button('Delete', elem_id=f'delete_{tabname}') if transfer: buttons = generation_parameters_copypaste.create_buttons(["control", "txt2img", "img2img", "extras", "caption"]) + if tabname in ("gallery", "txt2img", "img2img", "extras"): + prompt_buttons = generation_parameters_copypaste.create_buttons(["control"], label_prefix="✎", label_override="prompt", id_suffix="_prompt") + params_buttons = generation_parameters_copypaste.create_buttons(["control"], label_prefix="⚙", label_override="params", id_suffix="_params") + else: + prompt_buttons = None + params_buttons = None else: buttons = None + prompt_buttons = None + params_buttons = None download_files = gr.File(None, file_count="multiple", interactive=False, show_label=False, visible=False, elem_id=f'download_files_{tabname}') with gr.Group(): @@ -349,6 +357,31 @@ def create_output_panel(tabname, preview=True, prompt=None, height=None, transfe source_text_component=prompt or generation_info ) generation_parameters_copypaste.register_paste_params_button(bindings) + if prompt_buttons is not None: + for paste_tabname, paste_button in prompt_buttons.items(): + debug(f'Create output panel prompt-only: source={tabname} target={paste_tabname} button={paste_button}') + generation_parameters_copypaste.register_paste_params_button(generation_parameters_copypaste.ParamBinding( + paste_button=paste_button, + tabname=paste_tabname, + source_tabname=tabname, + source_image_component=result_gallery, + paste_field_names=paste_field_names, + source_text_component=prompt or generation_info, + skip_image=True, + only_prompt=True, + )) + if params_buttons is not None: + for paste_tabname, paste_button in params_buttons.items(): + debug(f'Create output panel params-only: source={tabname} target={paste_tabname} button={paste_button}') + generation_parameters_copypaste.register_paste_params_button(generation_parameters_copypaste.ParamBinding( + paste_button=paste_button, + tabname=paste_tabname, + source_tabname=tabname, + source_image_component=result_gallery, + paste_field_names=paste_field_names, + source_text_component=prompt or generation_info, + skip_image=True, + )) return result_gallery, generation_info, html_info, html_info_formatted, html_log