Merge pull request #4752 from vladmandic/feat/gallery-ux

Feat/gallery ux
This commit is contained in:
Vladimir Mandic
2026-04-13 09:27:27 +02:00
committed by GitHub
4 changed files with 85 additions and 25 deletions
+1 -1
View File
@@ -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. <br><br> 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"},
+18 -4
View File
@@ -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,17 @@ 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.
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();
document.getElementById('control_nav')?.click();
});
appendContextMenuOption(id, 'Transfer all parameters to Images tab', () => {
document.querySelector(`#image_buttons_${tab} #control_tab_params`)?.click();
document.getElementById('control_nav')?.click();
});
}
addContextMenuEventListener();
}
+33 -20
View File
@@ -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,
+33
View File
@@ -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