2022-10-31 17:36:45 +03:00
import json
import os . path
import sys
import time
import traceback
import git
import gradio as gr
import html
2022-11-13 21:39:41 +03:00
import shutil
import errno
2022-10-31 17:36:45 +03:00
from modules import extensions , shared , paths
2023-01-28 15:57:56 +03:00
from modules . call_queue import wrap_gradio_gpu_call
2022-10-31 17:36:45 +03:00
2022-11-01 09:59:00 +03:00
available_extensions = { " extensions " : [ ] }
2022-10-31 18:33:44 +03:00
def check_access ( ) :
2022-12-02 13:53:26 +03:00
assert not shared . cmd_opts . disable_extension_access , " extension access disabled because of command line flags "
2022-10-31 18:33:44 +03:00
2023-03-27 19:44:49 +03:00
def apply_and_restart ( disable_list , update_list , disable_all ) :
2022-10-31 18:33:44 +03:00
check_access ( )
2022-10-31 17:36:45 +03:00
disabled = json . loads ( disable_list )
assert type ( disabled ) == list , f " wrong disable_list data for apply_and_restart: { disable_list } "
update = json . loads ( update_list )
assert type ( update ) == list , f " wrong update_list data for apply_and_restart: { update_list } "
update = set ( update )
for ext in extensions . extensions :
if ext . name not in update :
continue
try :
2022-11-12 21:44:42 +03:00
ext . fetch_and_reset_hard ( )
2022-10-31 17:36:45 +03:00
except Exception :
2022-11-12 21:44:42 +03:00
print ( f " Error getting updates for { ext . name } : " , file = sys . stderr )
2022-10-31 17:36:45 +03:00
print ( traceback . format_exc ( ) , file = sys . stderr )
shared . opts . disabled_extensions = disabled
2023-03-27 19:44:49 +03:00
shared . opts . disable_all_extensions = disable_all
2022-10-31 17:36:45 +03:00
shared . opts . save ( shared . config_filename )
shared . state . interrupt ( )
shared . state . need_restart = True
2023-01-28 15:57:56 +03:00
def check_updates ( id_task , disable_list ) :
2022-10-31 18:33:44 +03:00
check_access ( )
2023-01-28 15:57:56 +03:00
disabled = json . loads ( disable_list )
assert type ( disabled ) == list , f " wrong disable_list data for apply_and_restart: { disable_list } "
exts = [ ext for ext in extensions . extensions if ext . remote is not None and ext . name not in disabled ]
shared . state . job_count = len ( exts )
for ext in exts :
shared . state . textinfo = ext . name
2022-10-31 17:36:45 +03:00
try :
ext . check_updates ( )
2023-03-27 10:02:30 +03:00
except FileNotFoundError as e :
if ' FETCH_HEAD ' not in str ( e ) :
raise
2022-10-31 17:36:45 +03:00
except Exception :
print ( f " Error checking updates for { ext . name } : " , file = sys . stderr )
print ( traceback . format_exc ( ) , file = sys . stderr )
2023-01-28 15:57:56 +03:00
shared . state . nextjob ( )
return extension_table ( ) , " "
2022-10-31 17:36:45 +03:00
def extension_table ( ) :
code = f """ <!-- { time . time ( ) } -->
< table id = " extensions " >
< thead >
< tr >
< th > < abbr title = " Use checkbox to enable the extension; it will be enabled or disabled when you click apply button " > Extension < / abbr > < / th >
< th > URL < / th >
2023-02-13 19:04:34 +03:00
< th > < abbr title = " Extension version " > Version < / abbr > < / th >
2022-10-31 17:36:45 +03:00
< th > < abbr title = " Use checkbox to mark the extension for update; it will be updated when you click apply button " > Update < / abbr > < / th >
< / tr >
< / thead >
< tbody >
"""
for ext in extensions . extensions :
2023-03-27 10:02:30 +03:00
ext . read_info_from_repo ( )
2023-02-13 19:04:34 +03:00
remote = f """ <a href= " { html . escape ( ext . remote or ' ' ) } " target= " _blank " > { html . escape ( " built-in " if ext . is_builtin else ext . remote or ' ' ) } </a> """
2022-12-03 18:06:33 +03:00
2022-10-31 17:36:45 +03:00
if ext . can_update :
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> """
else :
ext_status = ext . status
2023-03-27 19:44:49 +03:00
style = " "
if shared . opts . disable_all_extensions == " extra " and not ext . is_builtin or shared . opts . disable_all_extensions == " all " :
style = ' style= " color: var(--primary-400) " '
2022-10-31 17:36:45 +03:00
code + = f """
< tr >
2023-03-27 19:44:49 +03:00
< td > < label { style } > < 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 >
2022-12-03 18:06:33 +03:00
< td > { remote } < / td >
2023-02-13 19:04:34 +03:00
< td > { ext . version } < / td >
2022-10-31 17:36:45 +03:00
< td { ' class= " extension_status " ' if ext . remote is not None else ' ' } > { ext_status } < / td >
< / tr >
"""
code + = """
< / tbody >
< / table >
"""
return code
2022-11-01 09:59:00 +03:00
def normalize_git_url ( url ) :
if url is None :
return " "
url = url . replace ( " .git " , " " )
return url
2022-10-31 17:36:45 +03:00
def install_extension_from_url ( dirname , url ) :
2022-10-31 18:33:44 +03:00
check_access ( )
2022-10-31 17:36:45 +03:00
assert url , ' No URL specified '
if dirname is None or dirname == " " :
* parts , last_part = url . split ( ' / ' )
2022-11-01 09:59:00 +03:00
last_part = normalize_git_url ( last_part )
2022-10-31 17:36:45 +03:00
dirname = last_part
target_dir = os . path . join ( extensions . extensions_dir , dirname )
assert not os . path . exists ( target_dir ) , f ' Extension directory already exists: { target_dir } '
2022-11-01 09:59:00 +03:00
normalized_url = normalize_git_url ( url )
assert len ( [ x for x in extensions . extensions if normalize_git_url ( x . remote ) == normalized_url ] ) == 0 , ' Extension with this URL is already installed '
2022-10-31 17:36:45 +03:00
2023-01-25 19:15:42 +03:00
tmpdir = os . path . join ( paths . data_path , " tmp " , dirname )
2022-10-31 17:36:45 +03:00
try :
shutil . rmtree ( tmpdir , True )
2023-03-15 14:29:50 +03:00
with git . Repo . clone_from ( url , tmpdir ) as repo :
repo . remote ( ) . fetch ( )
for submodule in repo . submodules :
submodule . update ( )
2022-11-13 21:39:41 +03:00
try :
os . rename ( tmpdir , target_dir )
except OSError as err :
if err . errno == errno . EXDEV :
# Cross device link, typical in docker or when tmp/ and extensions/ are on different file systems
# Since we can't use a rename, do the slower but more versitile shutil.move()
shutil . move ( tmpdir , target_dir )
else :
# Something else, not enough free space, permissions, etc. rethrow it so that it gets handled.
2023-03-16 06:35:48 +03:00
raise err
2022-10-31 17:36:45 +03:00
2022-11-12 11:11:47 +03:00
import launch
launch . run_extension_installer ( target_dir )
2022-10-31 17:36:45 +03:00
extensions . list_extensions ( )
return [ extension_table ( ) , html . escape ( f " Installed into { target_dir } . Use Installed tab to restart. " ) ]
finally :
shutil . rmtree ( tmpdir , True )
2023-03-23 18:43:00 +03:00
def install_extension_from_index ( url , hide_tags , sort_column , filter_text ) :
2022-11-01 09:59:00 +03:00
ext_table , message = install_extension_from_url ( None , url )
2023-03-23 18:43:00 +03:00
code , _ = refresh_available_extensions_from_data ( hide_tags , sort_column , filter_text )
2022-11-01 09:59:00 +03:00
2023-03-23 18:43:00 +03:00
return code , ext_table , message , ' '
2022-11-01 09:59:00 +03:00
2022-11-06 10:12:53 +03:00
2023-01-06 12:32:44 +03:00
def refresh_available_extensions ( url , hide_tags , sort_column ) :
2022-11-01 09:59:00 +03:00
global available_extensions
import urllib . request
with urllib . request . urlopen ( url ) as response :
text = response . read ( )
available_extensions = json . loads ( text )
2023-01-06 12:32:44 +03:00
code , tags = refresh_available_extensions_from_data ( hide_tags , sort_column )
2022-11-06 10:12:53 +03:00
2023-03-23 18:43:00 +03:00
return url , code , gr . CheckboxGroup . update ( choices = tags ) , ' ' , ' '
2022-11-06 10:12:53 +03:00
2023-03-23 18:43:00 +03:00
def refresh_available_extensions_for_tags ( hide_tags , sort_column , filter_text ) :
code , _ = refresh_available_extensions_from_data ( hide_tags , sort_column , filter_text )
return code , ' '
def search_extensions ( filter_text , hide_tags , sort_column ) :
code , _ = refresh_available_extensions_from_data ( hide_tags , sort_column , filter_text )
2022-11-01 09:59:00 +03:00
2022-11-06 10:12:53 +03:00
return code , ' '
2022-11-01 09:59:00 +03:00
2022-11-06 10:12:53 +03:00
2023-01-06 12:32:44 +03:00
sort_ordering = [
# (reverse, order_by_function)
( True , lambda x : x . get ( ' added ' , ' z ' ) ) ,
( False , lambda x : x . get ( ' added ' , ' z ' ) ) ,
( False , lambda x : x . get ( ' name ' , ' z ' ) ) ,
( True , lambda x : x . get ( ' name ' , ' z ' ) ) ,
( False , lambda x : ' z ' ) ,
]
2023-03-23 18:43:00 +03:00
def refresh_available_extensions_from_data ( hide_tags , sort_column , filter_text = " " ) :
2022-11-01 09:59:00 +03:00
extlist = available_extensions [ " extensions " ]
installed_extension_urls = { normalize_git_url ( extension . remote ) : extension . name for extension in extensions . extensions }
2022-11-06 10:12:53 +03:00
tags = available_extensions . get ( " tags " , { } )
tags_to_hide = set ( hide_tags )
hidden = 0
2022-11-01 09:59:00 +03:00
code = f """ <!-- { time . time ( ) } -->
< table id = " available_extensions " >
< thead >
< tr >
< th > Extension < / th >
< th > Description < / th >
< th > Action < / th >
< / tr >
< / thead >
< tbody >
"""
2023-01-06 12:32:44 +03:00
sort_reverse , sort_function = sort_ordering [ sort_column if 0 < = sort_column < len ( sort_ordering ) else 0 ]
for ext in sorted ( extlist , key = sort_function , reverse = sort_reverse ) :
2022-11-01 09:59:00 +03:00
name = ext . get ( " name " , " noname " )
2023-01-06 12:32:44 +03:00
added = ext . get ( ' added ' , ' unknown ' )
2022-11-01 09:59:00 +03:00
url = ext . get ( " url " , None )
description = ext . get ( " description " , " " )
2022-11-06 10:12:53 +03:00
extension_tags = ext . get ( " tags " , [ ] )
2022-11-01 09:59:00 +03:00
if url is None :
continue
2022-12-10 15:05:22 +03:00
existing = installed_extension_urls . get ( normalize_git_url ( url ) , None )
extension_tags = extension_tags + [ " installed " ] if existing else extension_tags
2022-11-06 10:12:53 +03:00
if len ( [ x for x in extension_tags if x in tags_to_hide ] ) > 0 :
hidden + = 1
continue
2023-03-23 18:43:00 +03:00
if filter_text and filter_text . strip ( ) :
if filter_text . lower ( ) not in html . escape ( name ) . lower ( ) and filter_text . lower ( ) not in html . escape ( description ) . lower ( ) :
hidden + = 1
continue
2023-03-21 08:49:08 +03:00
install_code = f """ <button onclick= " install_extension_from_index(this, ' { html . escape ( url ) } ' ) " { " disabled=disabled " if existing else " " } class= " lg secondary gradio-button custom-button " > { " Install " if not existing else " Installed " } </button> """
2022-11-01 09:59:00 +03:00
2022-11-06 10:12:53 +03:00
tags_text = " , " . join ( [ f " <span class= ' extension-tag ' title= ' { tags . get ( x , ' ' ) } ' > { x } </span> " for x in extension_tags ] )
2022-11-01 09:59:00 +03:00
code + = f """
< tr >
2022-11-06 10:12:53 +03:00
< td > < a href = " { html.escape(url)} " target = " _blank " > { html . escape ( name ) } < / a > < br / > { tags_text } < / td >
2023-01-06 12:32:44 +03:00
< td > { html . escape ( description ) } < p class = " info " > < span class = " date_added " > Added : { html . escape ( added ) } < / span > < / p > < / td >
2022-11-01 09:59:00 +03:00
< td > { install_code } < / td >
< / tr >
2022-12-10 15:05:22 +03:00
"""
for tag in [ x for x in extension_tags if x not in tags ] :
tags [ tag ] = tag
2022-11-01 09:59:00 +03:00
code + = """
< / tbody >
< / table >
"""
2022-11-06 10:12:53 +03:00
if hidden > 0 :
code + = f " <p>Extension hidden: { hidden } </p> "
return code , list ( tags )
2022-11-01 09:59:00 +03:00
2022-10-31 17:36:45 +03:00
def create_ui ( ) :
import modules . ui
with gr . Blocks ( analytics_enabled = False ) as ui :
with gr . Tabs ( elem_id = " tabs_extensions " ) as tabs :
with gr . TabItem ( " Installed " ) :
2023-01-28 15:57:56 +03:00
with gr . Row ( elem_id = " extensions_installed_top " ) :
2022-10-31 17:36:45 +03:00
apply = gr . Button ( value = " Apply and restart UI " , variant = " primary " )
check = gr . Button ( value = " Check for updates " )
2023-03-27 19:44:49 +03:00
extensions_disable_all = gr . Radio ( label = " Disable all extensions " , choices = [ " none " , " extra " , " all " ] , value = shared . opts . disable_all_extensions , elem_id = " extensions_disable_all " )
2022-11-01 09:59:00 +03:00
extensions_disabled_list = gr . Text ( elem_id = " extensions_disabled_list " , visible = False ) . style ( container = False )
extensions_update_list = gr . Text ( elem_id = " extensions_update_list " , visible = False ) . style ( container = False )
2022-10-31 17:36:45 +03:00
2023-03-27 19:44:49 +03:00
html = " "
if shared . opts . disable_all_extensions != " none " :
html = """
< span style = " color: var(--primary-400); " >
" Disable all extensions " was set , change it to " none " to load all extensions again
< / span >
"""
info = gr . HTML ( html )
2022-10-31 17:36:45 +03:00
extensions_table = gr . HTML ( lambda : extension_table ( ) )
apply . click (
fn = apply_and_restart ,
_js = " extensions_apply " ,
2023-03-27 19:44:49 +03:00
inputs = [ extensions_disabled_list , extensions_update_list , extensions_disable_all ] ,
2022-10-31 17:36:45 +03:00
outputs = [ ] ,
)
check . click (
2023-01-28 15:57:56 +03:00
fn = wrap_gradio_gpu_call ( check_updates , extra_outputs = [ gr . update ( ) ] ) ,
2022-10-31 17:36:45 +03:00
_js = " extensions_check " ,
2023-01-28 15:57:56 +03:00
inputs = [ info , extensions_disabled_list ] ,
outputs = [ extensions_table , info ] ,
2022-10-31 17:36:45 +03:00
)
2022-11-01 09:59:00 +03:00
with gr . TabItem ( " Available " ) :
with gr . Row ( ) :
refresh_available_extensions_button = gr . Button ( value = " Load from: " , variant = " primary " )
2023-03-12 17:20:04 +03:00
available_extensions_index = gr . Text ( value = " https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui-extensions/master/index.json " , label = " Extension index URL " ) . style ( container = False )
2022-11-01 09:59:00 +03:00
extension_to_install = gr . Text ( elem_id = " extension_to_install " , visible = False )
install_extension_button = gr . Button ( elem_id = " install_extension_button " , visible = False )
2022-11-06 10:12:53 +03:00
with gr . Row ( ) :
2022-12-10 15:05:22 +03:00
hide_tags = gr . CheckboxGroup ( value = [ " ads " , " localization " , " installed " ] , label = " Hide extensions with tags " , choices = [ " script " , " ads " , " localization " , " installed " ] )
2023-01-06 12:32:44 +03:00
sort_column = gr . Radio ( value = " newest first " , label = " Order " , choices = [ " newest first " , " oldest first " , " a-z " , " z-a " , " internal order " , ] , type = " index " )
2022-11-06 10:12:53 +03:00
2023-03-23 18:43:00 +03:00
with gr . Row ( ) :
search_extensions_text = gr . Text ( label = " Search " ) . style ( container = False )
2022-11-01 09:59:00 +03:00
install_result = gr . HTML ( )
available_extensions_table = gr . HTML ( )
refresh_available_extensions_button . click (
2022-11-06 10:12:53 +03:00
fn = modules . ui . wrap_gradio_call ( refresh_available_extensions , extra_outputs = [ gr . update ( ) , gr . update ( ) , gr . update ( ) ] ) ,
2023-01-06 12:32:44 +03:00
inputs = [ available_extensions_index , hide_tags , sort_column ] ,
2023-03-23 18:43:00 +03:00
outputs = [ available_extensions_index , available_extensions_table , hide_tags , install_result , search_extensions_text ] ,
2022-11-01 09:59:00 +03:00
)
install_extension_button . click (
fn = modules . ui . wrap_gradio_call ( install_extension_from_index , extra_outputs = [ gr . update ( ) , gr . update ( ) ] ) ,
2023-03-23 18:43:00 +03:00
inputs = [ extension_to_install , hide_tags , sort_column , search_extensions_text ] ,
2022-11-01 09:59:00 +03:00
outputs = [ available_extensions_table , extensions_table , install_result ] ,
)
2023-03-23 18:43:00 +03:00
search_extensions_text . change (
fn = modules . ui . wrap_gradio_call ( search_extensions , extra_outputs = [ gr . update ( ) ] ) ,
inputs = [ search_extensions_text , hide_tags , sort_column ] ,
outputs = [ available_extensions_table , install_result ] ,
)
2022-11-06 10:12:53 +03:00
hide_tags . change (
fn = modules . ui . wrap_gradio_call ( refresh_available_extensions_for_tags , extra_outputs = [ gr . update ( ) ] ) ,
2023-03-23 18:43:00 +03:00
inputs = [ hide_tags , sort_column , search_extensions_text ] ,
2023-01-06 12:32:44 +03:00
outputs = [ available_extensions_table , install_result ]
)
sort_column . change (
fn = modules . ui . wrap_gradio_call ( refresh_available_extensions_for_tags , extra_outputs = [ gr . update ( ) ] ) ,
2023-03-23 18:43:00 +03:00
inputs = [ hide_tags , sort_column , search_extensions_text ] ,
2022-11-06 10:12:53 +03:00
outputs = [ available_extensions_table , install_result ]
)
2022-10-31 17:36:45 +03:00
with gr . TabItem ( " Install from URL " ) :
install_url = gr . Text ( label = " URL for extension ' s git repository " )
install_dirname = gr . Text ( label = " Local directory name " , placeholder = " Leave empty for auto " )
2022-11-01 09:59:00 +03:00
install_button = gr . Button ( value = " Install " , variant = " primary " )
install_result = gr . HTML ( elem_id = " extension_install_result " )
2022-10-31 17:36:45 +03:00
2022-11-01 09:59:00 +03:00
install_button . click (
2022-10-31 17:36:45 +03:00
fn = modules . ui . wrap_gradio_call ( install_extension_from_url , extra_outputs = [ gr . update ( ) ] ) ,
inputs = [ install_dirname , install_url ] ,
2022-11-01 09:59:00 +03:00
outputs = [ extensions_table , install_result ] ,
2022-10-31 17:36:45 +03:00
)
return ui