ui_common.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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):
  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(variant='panel', elem_id=f"{tabname}_results"):
  105. with gr.Group(elem_id=f"{tabname}_gallery_container"):
  106. result_gallery = gr.Gallery(label='Output', show_label=False, elem_id=f"{tabname}_gallery", columns=4)
  107. generation_info = None
  108. with gr.Column():
  109. with gr.Row(elem_id=f"image_buttons_{tabname}", elem_classes="image-buttons"):
  110. open_folder_button = gr.Button(folder_symbol, visible=not shared.cmd_opts.hide_ui_dir_config)
  111. if tabname != "extras":
  112. save = gr.Button('Save', elem_id=f'save_{tabname}')
  113. save_zip = gr.Button('Zip', elem_id=f'save_zip_{tabname}')
  114. buttons = parameters_copypaste.create_buttons(["img2img", "inpaint", "extras"])
  115. open_folder_button.click(
  116. fn=lambda: open_folder(shared.opts.outdir_samples or outdir),
  117. inputs=[],
  118. outputs=[],
  119. )
  120. if tabname != "extras":
  121. download_files = gr.File(None, file_count="multiple", interactive=False, show_label=False, visible=False, elem_id=f'download_files_{tabname}')
  122. with gr.Group():
  123. html_info = gr.HTML(elem_id=f'html_info_{tabname}', elem_classes="infotext")
  124. html_log = gr.HTML(elem_id=f'html_log_{tabname}', elem_classes="html-log")
  125. generation_info = gr.Textbox(visible=False, elem_id=f'generation_info_{tabname}')
  126. if tabname == 'txt2img' or tabname == 'img2img':
  127. generation_info_button = gr.Button(visible=False, elem_id=f"{tabname}_generation_info_button")
  128. generation_info_button.click(
  129. fn=update_generation_info,
  130. _js="function(x, y, z){ return [x, y, selected_gallery_index()] }",
  131. inputs=[generation_info, html_info, html_info],
  132. outputs=[html_info, html_info],
  133. show_progress=False,
  134. )
  135. save.click(
  136. fn=call_queue.wrap_gradio_call(save_files),
  137. _js="(x, y, z, w) => [x, y, false, selected_gallery_index()]",
  138. inputs=[
  139. generation_info,
  140. result_gallery,
  141. html_info,
  142. html_info,
  143. ],
  144. outputs=[
  145. download_files,
  146. html_log,
  147. ],
  148. show_progress=False,
  149. )
  150. save_zip.click(
  151. fn=call_queue.wrap_gradio_call(save_files),
  152. _js="(x, y, z, w) => [x, y, true, selected_gallery_index()]",
  153. inputs=[
  154. generation_info,
  155. result_gallery,
  156. html_info,
  157. html_info,
  158. ],
  159. outputs=[
  160. download_files,
  161. html_log,
  162. ]
  163. )
  164. else:
  165. html_info_x = gr.HTML(elem_id=f'html_info_x_{tabname}')
  166. html_info = gr.HTML(elem_id=f'html_info_{tabname}', elem_classes="infotext")
  167. html_log = gr.HTML(elem_id=f'html_log_{tabname}')
  168. paste_field_names = []
  169. if tabname == "txt2img":
  170. paste_field_names = modules.scripts.scripts_txt2img.paste_field_names
  171. elif tabname == "img2img":
  172. paste_field_names = modules.scripts.scripts_img2img.paste_field_names
  173. for paste_tabname, paste_button in buttons.items():
  174. parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(
  175. paste_button=paste_button, tabname=paste_tabname, source_tabname="txt2img" if tabname == "txt2img" else None, source_image_component=result_gallery,
  176. paste_field_names=paste_field_names
  177. ))
  178. return result_gallery, generation_info if tabname != "extras" else html_info_x, html_info, html_log
  179. def create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_id):
  180. refresh_components = refresh_component if isinstance(refresh_component, list) else [refresh_component]
  181. label = None
  182. for comp in refresh_components:
  183. label = getattr(comp, 'label', None)
  184. if label is not None:
  185. break
  186. def refresh():
  187. refresh_method()
  188. args = refreshed_args() if callable(refreshed_args) else refreshed_args
  189. for k, v in args.items():
  190. for comp in refresh_components:
  191. setattr(comp, k, v)
  192. return [gr.update(**(args or {})) for _ in refresh_components] if len(refresh_components) > 1 else gr.update(**(args or {}))
  193. refresh_button = ToolButton(value=refresh_symbol, elem_id=elem_id, tooltip=f"{label}: refresh" if label else "Refresh")
  194. refresh_button.click(
  195. fn=refresh,
  196. inputs=[],
  197. outputs=refresh_components
  198. )
  199. return refresh_button
  200. def setup_dialog(button_show, dialog, *, button_close=None):
  201. """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."""
  202. dialog.visible = False
  203. button_show.click(
  204. fn=lambda: gr.update(visible=True),
  205. inputs=[],
  206. outputs=[dialog],
  207. ).then(fn=None, _js="function(){ popup(gradioApp().getElementById('" + dialog.elem_id + "')); }")
  208. if button_close:
  209. button_close.click(fn=None, _js="closePopup")