ui_extensions.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. import json
  2. import os.path
  3. import shutil
  4. import sys
  5. import time
  6. import traceback
  7. import git
  8. import gradio as gr
  9. import html
  10. from modules import extensions, shared, paths
  11. available_extensions = {"extensions": []}
  12. def check_access():
  13. assert not shared.cmd_opts.disable_extension_access, "extension access disabled because of command line flags"
  14. def apply_and_restart(disable_list, update_list):
  15. check_access()
  16. disabled = json.loads(disable_list)
  17. assert type(disabled) == list, f"wrong disable_list data for apply_and_restart: {disable_list}"
  18. update = json.loads(update_list)
  19. assert type(update) == list, f"wrong update_list data for apply_and_restart: {update_list}"
  20. update = set(update)
  21. for ext in extensions.extensions:
  22. if ext.name not in update:
  23. continue
  24. try:
  25. ext.fetch_and_reset_hard()
  26. except Exception:
  27. print(f"Error getting updates for {ext.name}:", file=sys.stderr)
  28. print(traceback.format_exc(), file=sys.stderr)
  29. shared.opts.disabled_extensions = disabled
  30. shared.opts.save(shared.config_filename)
  31. shared.state.interrupt()
  32. shared.state.need_restart = True
  33. def check_updates():
  34. check_access()
  35. for ext in extensions.extensions:
  36. if ext.remote is None:
  37. continue
  38. try:
  39. ext.check_updates()
  40. except Exception:
  41. print(f"Error checking updates for {ext.name}:", file=sys.stderr)
  42. print(traceback.format_exc(), file=sys.stderr)
  43. return extension_table()
  44. def extension_table():
  45. code = f"""<!-- {time.time()} -->
  46. <table id="extensions">
  47. <thead>
  48. <tr>
  49. <th><abbr title="Use checkbox to enable the extension; it will be enabled or disabled when you click apply button">Extension</abbr></th>
  50. <th>URL</th>
  51. <th><abbr title="Use checkbox to mark the extension for update; it will be updated when you click apply button">Update</abbr></th>
  52. </tr>
  53. </thead>
  54. <tbody>
  55. """
  56. for ext in extensions.extensions:
  57. remote = ""
  58. if ext.is_builtin:
  59. remote = "built-in"
  60. elif ext.remote:
  61. remote = f"""<a href="{html.escape(ext.remote or '')}" target="_blank">{html.escape("built-in" if ext.is_builtin else ext.remote or '')}</a>"""
  62. if ext.can_update:
  63. ext_status = f"""<label><input class="gr-check-radio gr-checkbox" name="update_{html.escape(ext.name)}" checked="checked" type="checkbox">{html.escape(ext.status)}</label>"""
  64. else:
  65. ext_status = ext.status
  66. code += f"""
  67. <tr>
  68. <td><label><input class="gr-check-radio gr-checkbox" name="enable_{html.escape(ext.name)}" type="checkbox" {'checked="checked"' if ext.enabled else ''}>{html.escape(ext.name)}</label></td>
  69. <td>{remote}</td>
  70. <td{' class="extension_status"' if ext.remote is not None else ''}>{ext_status}</td>
  71. </tr>
  72. """
  73. code += """
  74. </tbody>
  75. </table>
  76. """
  77. return code
  78. def normalize_git_url(url):
  79. if url is None:
  80. return ""
  81. url = url.replace(".git", "")
  82. return url
  83. def install_extension_from_url(dirname, url):
  84. check_access()
  85. assert url, 'No URL specified'
  86. if dirname is None or dirname == "":
  87. *parts, last_part = url.split('/')
  88. last_part = normalize_git_url(last_part)
  89. dirname = last_part
  90. target_dir = os.path.join(extensions.extensions_dir, dirname)
  91. assert not os.path.exists(target_dir), f'Extension directory already exists: {target_dir}'
  92. normalized_url = normalize_git_url(url)
  93. assert len([x for x in extensions.extensions if normalize_git_url(x.remote) == normalized_url]) == 0, 'Extension with this URL is already installed'
  94. tmpdir = os.path.join(paths.script_path, "tmp", dirname)
  95. try:
  96. shutil.rmtree(tmpdir, True)
  97. repo = git.Repo.clone_from(url, tmpdir)
  98. repo.remote().fetch()
  99. os.rename(tmpdir, target_dir)
  100. import launch
  101. launch.run_extension_installer(target_dir)
  102. extensions.list_extensions()
  103. return [extension_table(), html.escape(f"Installed into {target_dir}. Use Installed tab to restart.")]
  104. finally:
  105. shutil.rmtree(tmpdir, True)
  106. def install_extension_from_index(url, hide_tags):
  107. ext_table, message = install_extension_from_url(None, url)
  108. code, _ = refresh_available_extensions_from_data(hide_tags)
  109. return code, ext_table, message
  110. def refresh_available_extensions(url, hide_tags):
  111. global available_extensions
  112. import urllib.request
  113. with urllib.request.urlopen(url) as response:
  114. text = response.read()
  115. available_extensions = json.loads(text)
  116. code, tags = refresh_available_extensions_from_data(hide_tags)
  117. return url, code, gr.CheckboxGroup.update(choices=tags), ''
  118. def refresh_available_extensions_for_tags(hide_tags):
  119. code, _ = refresh_available_extensions_from_data(hide_tags)
  120. return code, ''
  121. def refresh_available_extensions_from_data(hide_tags):
  122. extlist = available_extensions["extensions"]
  123. installed_extension_urls = {normalize_git_url(extension.remote): extension.name for extension in extensions.extensions}
  124. tags = available_extensions.get("tags", {})
  125. tags_to_hide = set(hide_tags)
  126. hidden = 0
  127. code = f"""<!-- {time.time()} -->
  128. <table id="available_extensions">
  129. <thead>
  130. <tr>
  131. <th>Extension</th>
  132. <th>Description</th>
  133. <th>Action</th>
  134. </tr>
  135. </thead>
  136. <tbody>
  137. """
  138. for ext in extlist:
  139. name = ext.get("name", "noname")
  140. url = ext.get("url", None)
  141. description = ext.get("description", "")
  142. extension_tags = ext.get("tags", [])
  143. if url is None:
  144. continue
  145. if len([x for x in extension_tags if x in tags_to_hide]) > 0:
  146. hidden += 1
  147. continue
  148. existing = installed_extension_urls.get(normalize_git_url(url), None)
  149. install_code = f"""<input onclick="install_extension_from_index(this, '{html.escape(url)}')" type="button" value="{"Install" if not existing else "Installed"}" {"disabled=disabled" if existing else ""} class="gr-button gr-button-lg gr-button-secondary">"""
  150. tags_text = ", ".join([f"<span class='extension-tag' title='{tags.get(x, '')}'>{x}</span>" for x in extension_tags])
  151. code += f"""
  152. <tr>
  153. <td><a href="{html.escape(url)}" target="_blank">{html.escape(name)}</a><br />{tags_text}</td>
  154. <td>{html.escape(description)}</td>
  155. <td>{install_code}</td>
  156. </tr>
  157. """
  158. code += """
  159. </tbody>
  160. </table>
  161. """
  162. if hidden > 0:
  163. code += f"<p>Extension hidden: {hidden}</p>"
  164. return code, list(tags)
  165. def create_ui():
  166. import modules.ui
  167. with gr.Blocks(analytics_enabled=False) as ui:
  168. with gr.Tabs(elem_id="tabs_extensions") as tabs:
  169. with gr.TabItem("Installed"):
  170. with gr.Row():
  171. apply = gr.Button(value="Apply and restart UI", variant="primary")
  172. check = gr.Button(value="Check for updates")
  173. extensions_disabled_list = gr.Text(elem_id="extensions_disabled_list", visible=False).style(container=False)
  174. extensions_update_list = gr.Text(elem_id="extensions_update_list", visible=False).style(container=False)
  175. extensions_table = gr.HTML(lambda: extension_table())
  176. apply.click(
  177. fn=apply_and_restart,
  178. _js="extensions_apply",
  179. inputs=[extensions_disabled_list, extensions_update_list],
  180. outputs=[],
  181. )
  182. check.click(
  183. fn=check_updates,
  184. _js="extensions_check",
  185. inputs=[],
  186. outputs=[extensions_table],
  187. )
  188. with gr.TabItem("Available"):
  189. with gr.Row():
  190. refresh_available_extensions_button = gr.Button(value="Load from:", variant="primary")
  191. available_extensions_index = gr.Text(value="https://raw.githubusercontent.com/wiki/AUTOMATIC1111/stable-diffusion-webui/Extensions-index.md", label="Extension index URL").style(container=False)
  192. extension_to_install = gr.Text(elem_id="extension_to_install", visible=False)
  193. install_extension_button = gr.Button(elem_id="install_extension_button", visible=False)
  194. with gr.Row():
  195. hide_tags = gr.CheckboxGroup(value=["ads", "localization"], label="Hide extensions with tags", choices=["script", "ads", "localization"])
  196. install_result = gr.HTML()
  197. available_extensions_table = gr.HTML()
  198. refresh_available_extensions_button.click(
  199. fn=modules.ui.wrap_gradio_call(refresh_available_extensions, extra_outputs=[gr.update(), gr.update(), gr.update()]),
  200. inputs=[available_extensions_index, hide_tags],
  201. outputs=[available_extensions_index, available_extensions_table, hide_tags, install_result],
  202. )
  203. install_extension_button.click(
  204. fn=modules.ui.wrap_gradio_call(install_extension_from_index, extra_outputs=[gr.update(), gr.update()]),
  205. inputs=[extension_to_install, hide_tags],
  206. outputs=[available_extensions_table, extensions_table, install_result],
  207. )
  208. hide_tags.change(
  209. fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]),
  210. inputs=[hide_tags],
  211. outputs=[available_extensions_table, install_result]
  212. )
  213. with gr.TabItem("Install from URL"):
  214. install_url = gr.Text(label="URL for extension's git repository")
  215. install_dirname = gr.Text(label="Local directory name", placeholder="Leave empty for auto")
  216. install_button = gr.Button(value="Install", variant="primary")
  217. install_result = gr.HTML(elem_id="extension_install_result")
  218. install_button.click(
  219. fn=modules.ui.wrap_gradio_call(install_extension_from_url, extra_outputs=[gr.update()]),
  220. inputs=[install_dirname, install_url],
  221. outputs=[extensions_table, install_result],
  222. )
  223. return ui