ui_common.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import json
  2. import html
  3. import os
  4. import platform
  5. import sys
  6. import gradio as gr
  7. import subprocess as sp
  8. from modules import call_queue, shared
  9. from modules.generation_parameters_copypaste import image_from_url_text
  10. import modules.images
  11. from modules.ui_components import ToolButton
  12. import modules.generation_parameters_copypaste as parameters_copypaste
  13. folder_symbol = '\U0001f4c2' # 📂
  14. refresh_symbol = '\U0001f504' # 🔄
  15. def update_generation_info(generation_info, html_info, img_index):
  16. try:
  17. generation_info = json.loads(generation_info)
  18. if img_index < 0 or img_index >= len(generation_info["infotexts"]):
  19. return html_info, gr.update()
  20. return plaintext_to_html(generation_info["infotexts"][img_index]), gr.update()
  21. except Exception:
  22. pass
  23. # if the json parse or anything else fails, just return the old html_info
  24. return html_info, gr.update()
  25. def plaintext_to_html(text, classname=None):
  26. content = "<br>\n".join(html.escape(x) for x in text.split('\n'))
  27. return f"<p class='{classname}'>{content}</p>" if classname else f"<p>{content}</p>"
  28. def save_files(js_data, images, do_make_zip, index):
  29. import csv
  30. filenames = []
  31. fullfns = []
  32. #quick dictionary to class object conversion. Its necessary due apply_filename_pattern requiring it
  33. class MyObject:
  34. def __init__(self, d=None):
  35. if d is not None:
  36. for key, value in d.items():
  37. setattr(self, key, value)
  38. data = json.loads(js_data)
  39. p = MyObject(data)
  40. path = shared.opts.outdir_save
  41. save_to_dirs = shared.opts.use_save_to_dirs_for_ui
  42. extension: str = shared.opts.samples_format
  43. start_index = 0
  44. only_one = False
  45. if index > -1 and shared.opts.save_selected_only and (index >= data["index_of_first_image"]): # ensures we are looking at a specific non-grid picture, and we have save_selected_only
  46. only_one = True
  47. images = [images[index]]
  48. start_index = index
  49. os.makedirs(shared.opts.outdir_save, exist_ok=True)
  50. with open(os.path.join(shared.opts.outdir_save, "log.csv"), "a", encoding="utf8", newline='') as file:
  51. at_start = file.tell() == 0
  52. writer = csv.writer(file)
  53. if at_start:
  54. writer.writerow(["prompt", "seed", "width", "height", "sampler", "cfgs", "steps", "filename", "negative_prompt"])
  55. for image_index, filedata in enumerate(images, start_index):
  56. image = image_from_url_text(filedata)
  57. is_grid = image_index < p.index_of_first_image
  58. i = 0 if is_grid else (image_index - p.index_of_first_image)
  59. p.batch_index = image_index-1
  60. fullfn, txt_fullfn = modules.images.save_image(image, path, "", seed=p.all_seeds[i], prompt=p.all_prompts[i], extension=extension, info=p.infotexts[image_index], grid=is_grid, p=p, save_to_dirs=save_to_dirs)
  61. filename = os.path.relpath(fullfn, path)
  62. filenames.append(filename)
  63. fullfns.append(fullfn)
  64. if txt_fullfn:
  65. filenames.append(os.path.basename(txt_fullfn))
  66. fullfns.append(txt_fullfn)
  67. writer.writerow([data["prompt"], data["seed"], data["width"], data["height"], data["sampler_name"], data["cfg_scale"], data["steps"], filenames[0], data["negative_prompt"]])
  68. # Make Zip
  69. if do_make_zip:
  70. zip_fileseed = p.all_seeds[index-1] if only_one else p.all_seeds[0]
  71. namegen = modules.images.FilenameGenerator(p, zip_fileseed, p.all_prompts[0], image, True)
  72. zip_filename = namegen.apply(shared.opts.grid_zip_filename_pattern or "[datetime]_[[model_name]]_[seed]-[seed_last]")
  73. zip_filepath = os.path.join(path, f"{zip_filename}.zip")
  74. from zipfile import ZipFile
  75. with ZipFile(zip_filepath, "w") as zip_file:
  76. for i in range(len(fullfns)):
  77. with open(fullfns[i], mode="rb") as f:
  78. zip_file.writestr(filenames[i], f.read())
  79. fullfns.insert(0, zip_filepath)
  80. return gr.File.update(value=fullfns, visible=True), plaintext_to_html(f"Saved: {filenames[0]}")
  81. def create_output_panel(tabname, outdir, toprow=None):
  82. def open_folder(f):
  83. if not os.path.exists(f):
  84. print(f'Folder "{f}" does not exist. After you create an image, the folder will be created.')
  85. return
  86. elif not os.path.isdir(f):
  87. print(f"""
  88. WARNING
  89. An open_folder request was made with an argument that is not a folder.
  90. This could be an error or a malicious attempt to run code on your computer.
  91. Requested path was: {f}
  92. """, file=sys.stderr)
  93. return
  94. if not shared.cmd_opts.hide_ui_dir_config:
  95. path = os.path.normpath(f)
  96. if platform.system() == "Windows":
  97. os.startfile(path)
  98. elif platform.system() == "Darwin":
  99. sp.Popen(["open", path])
  100. elif "microsoft-standard-WSL2" in platform.uname().release:
  101. sp.Popen(["wsl-open", path])
  102. else:
  103. sp.Popen(["xdg-open", path])
  104. with gr.Column(elem_id=f"{tabname}_results"):
  105. if toprow:
  106. toprow.create_inline_toprow_image()
  107. with gr.Column(variant='panel', elem_id=f"{tabname}_results_panel"):
  108. with gr.Group(elem_id=f"{tabname}_gallery_container"):
  109. result_gallery = gr.Gallery(label='Output', show_label=False, elem_id=f"{tabname}_gallery", columns=4, preview=True, height=shared.opts.gallery_height or None)
  110. generation_info = None
  111. with gr.Row(elem_id=f"image_buttons_{tabname}", elem_classes="image-buttons"):
  112. open_folder_button = ToolButton(folder_symbol, elem_id=f'{tabname}_open_folder', visible=not shared.cmd_opts.hide_ui_dir_config, tooltip="Open images output directory.")
  113. if tabname != "extras":
  114. save = ToolButton('💾', elem_id=f'save_{tabname}', tooltip=f"Save the image to a dedicated directory ({shared.opts.outdir_save}).")
  115. save_zip = ToolButton('🗃️', elem_id=f'save_zip_{tabname}', tooltip=f"Save zip archive with images to a dedicated directory ({shared.opts.outdir_save})")
  116. buttons = {
  117. 'img2img': ToolButton('🖼️', elem_id=f'{tabname}_send_to_img2img', tooltip="Send image and generation parameters to img2img tab."),
  118. 'inpaint': ToolButton('🎨️', elem_id=f'{tabname}_send_to_inpaint', tooltip="Send image and generation parameters to img2img inpaint tab."),
  119. 'extras': ToolButton('📐', elem_id=f'{tabname}_send_to_extras', tooltip="Send image and generation parameters to extras tab.")
  120. }
  121. open_folder_button.click(
  122. fn=lambda: open_folder(shared.opts.outdir_samples or outdir),
  123. inputs=[],
  124. outputs=[],
  125. )
  126. if tabname != "extras":
  127. download_files = gr.File(None, file_count="multiple", interactive=False, show_label=False, visible=False, elem_id=f'download_files_{tabname}')
  128. with gr.Group():
  129. html_info = gr.HTML(elem_id=f'html_info_{tabname}', elem_classes="infotext")
  130. html_log = gr.HTML(elem_id=f'html_log_{tabname}', elem_classes="html-log")
  131. generation_info = gr.Textbox(visible=False, elem_id=f'generation_info_{tabname}')
  132. if tabname == 'txt2img' or tabname == 'img2img':
  133. generation_info_button = gr.Button(visible=False, elem_id=f"{tabname}_generation_info_button")
  134. generation_info_button.click(
  135. fn=update_generation_info,
  136. _js="function(x, y, z){ return [x, y, selected_gallery_index()] }",
  137. inputs=[generation_info, html_info, html_info],
  138. outputs=[html_info, html_info],
  139. show_progress=False,
  140. )
  141. save.click(
  142. fn=call_queue.wrap_gradio_call(save_files),
  143. _js="(x, y, z, w) => [x, y, false, selected_gallery_index()]",
  144. inputs=[
  145. generation_info,
  146. result_gallery,
  147. html_info,
  148. html_info,
  149. ],
  150. outputs=[
  151. download_files,
  152. html_log,
  153. ],
  154. show_progress=False,
  155. )
  156. save_zip.click(
  157. fn=call_queue.wrap_gradio_call(save_files),
  158. _js="(x, y, z, w) => [x, y, true, selected_gallery_index()]",
  159. inputs=[
  160. generation_info,
  161. result_gallery,
  162. html_info,
  163. html_info,
  164. ],
  165. outputs=[
  166. download_files,
  167. html_log,
  168. ]
  169. )
  170. else:
  171. html_info_x = gr.HTML(elem_id=f'html_info_x_{tabname}')
  172. html_info = gr.HTML(elem_id=f'html_info_{tabname}', elem_classes="infotext")
  173. html_log = gr.HTML(elem_id=f'html_log_{tabname}')
  174. paste_field_names = []
  175. if tabname == "txt2img":
  176. paste_field_names = modules.scripts.scripts_txt2img.paste_field_names
  177. elif tabname == "img2img":
  178. paste_field_names = modules.scripts.scripts_img2img.paste_field_names
  179. for paste_tabname, paste_button in buttons.items():
  180. parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(
  181. paste_button=paste_button, tabname=paste_tabname, source_tabname="txt2img" if tabname == "txt2img" else None, source_image_component=result_gallery,
  182. paste_field_names=paste_field_names
  183. ))
  184. return result_gallery, generation_info if tabname != "extras" else html_info_x, html_info, html_log
  185. def create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_id):
  186. refresh_components = refresh_component if isinstance(refresh_component, list) else [refresh_component]
  187. label = None
  188. for comp in refresh_components:
  189. label = getattr(comp, 'label', None)
  190. if label is not None:
  191. break
  192. def refresh():
  193. refresh_method()
  194. args = refreshed_args() if callable(refreshed_args) else refreshed_args
  195. for k, v in args.items():
  196. for comp in refresh_components:
  197. setattr(comp, k, v)
  198. return [gr.update(**(args or {})) for _ in refresh_components] if len(refresh_components) > 1 else gr.update(**(args or {}))
  199. refresh_button = ToolButton(value=refresh_symbol, elem_id=elem_id, tooltip=f"{label}: refresh" if label else "Refresh")
  200. refresh_button.click(
  201. fn=refresh,
  202. inputs=[],
  203. outputs=refresh_components
  204. )
  205. return refresh_button
  206. def setup_dialog(button_show, dialog, *, button_close=None):
  207. """Sets up the UI so that the dialog (gr.Box) is invisible, and is only shown when buttons_show is clicked, in a fullscreen modal window."""
  208. dialog.visible = False
  209. button_show.click(
  210. fn=lambda: gr.update(visible=True),
  211. inputs=[],
  212. outputs=[dialog],
  213. ).then(fn=None, _js="function(){ popupId('" + dialog.elem_id + "'); }")
  214. if button_close:
  215. button_close.click(fn=None, _js="closePopup")