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