ui_extensions.py 10.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
import json
import os.path
import shutil
import sys
import time
import traceback

import git

import gradio as gr
import html

from modules import extensions, shared, paths


A
AUTOMATIC 已提交
16 17 18
available_extensions = {"extensions": []}


19 20 21 22
def check_access():
    assert not shared.cmd_opts.disable_extension_access, "extension access disabed because of commandline flags"


23
def apply_and_restart(disable_list, update_list):
24 25
    check_access()

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
    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:
            ext.pull()
        except Exception:
            print(f"Error pulling updates for {ext.name}:", file=sys.stderr)
            print(traceback.format_exc(), file=sys.stderr)

    shared.opts.disabled_extensions = disabled
    shared.opts.save(shared.config_filename)

    shared.state.interrupt()
    shared.state.need_restart = True


def check_updates():
52 53
    check_access()

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
    for ext in extensions.extensions:
        if ext.remote is None:
            continue

        try:
            ext.check_updates()
        except Exception:
            print(f"Error checking updates for {ext.name}:", file=sys.stderr)
            print(traceback.format_exc(), file=sys.stderr)

    return extension_table()


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>
                <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:
        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

        code += f"""
            <tr>
                <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>
89
                <td><a href="{html.escape(ext.remote or '')}" target="_blank">{html.escape(ext.remote or '')}</a></td>
90 91 92 93 94 95 96 97 98 99 100 101
                <td{' class="extension_status"' if ext.remote is not None else ''}>{ext_status}</td>
            </tr>
    """

    code += """
        </tbody>
    </table>
    """

    return code


A
AUTOMATIC 已提交
102 103 104 105 106 107 108 109
def normalize_git_url(url):
    if url is None:
        return ""

    url = url.replace(".git", "")
    return url


110
def install_extension_from_url(dirname, url):
111 112
    check_access()

113 114 115 116
    assert url, 'No URL specified'

    if dirname is None or dirname == "":
        *parts, last_part = url.split('/')
A
AUTOMATIC 已提交
117
        last_part = normalize_git_url(last_part)
118 119 120 121 122 123

        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}'

A
AUTOMATIC 已提交
124 125
    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'
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142

    tmpdir = os.path.join(paths.script_path, "tmp", dirname)

    try:
        shutil.rmtree(tmpdir, True)

        repo = git.Repo.clone_from(url, tmpdir)
        repo.remote().fetch()

        os.rename(tmpdir, target_dir)

        extensions.list_extensions()
        return [extension_table(), html.escape(f"Installed into {target_dir}. Use Installed tab to restart.")]
    finally:
        shutil.rmtree(tmpdir, True)


143
def install_extension_from_index(url, hide_tags):
A
AUTOMATIC 已提交
144 145
    ext_table, message = install_extension_from_url(None, url)

146
    code, _ = refresh_available_extensions_from_data(hide_tags)
A
AUTOMATIC 已提交
147

148
    return code, ext_table, message
A
AUTOMATIC 已提交
149

150 151

def refresh_available_extensions(url, hide_tags):
A
AUTOMATIC 已提交
152 153 154 155 156 157 158 159
    global available_extensions

    import urllib.request
    with urllib.request.urlopen(url) as response:
        text = response.read()

    available_extensions = json.loads(text)

160 161 162 163 164 165 166
    code, tags = refresh_available_extensions_from_data(hide_tags)

    return url, code, gr.CheckboxGroup.update(choices=tags), ''


def refresh_available_extensions_for_tags(hide_tags):
    code, _ = refresh_available_extensions_from_data(hide_tags)
A
AUTOMATIC 已提交
167

168
    return code, ''
A
AUTOMATIC 已提交
169

170 171

def refresh_available_extensions_from_data(hide_tags):
A
AUTOMATIC 已提交
172 173 174
    extlist = available_extensions["extensions"]
    installed_extension_urls = {normalize_git_url(extension.remote): extension.name for extension in extensions.extensions}

175 176 177 178
    tags = available_extensions.get("tags", {})
    tags_to_hide = set(hide_tags)
    hidden = 0

A
AUTOMATIC 已提交
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
    code = f"""<!-- {time.time()} -->
    <table id="available_extensions">
        <thead>
            <tr>
                <th>Extension</th>
                <th>Description</th>
                <th>Action</th>
            </tr>
        </thead>
        <tbody>
    """

    for ext in extlist:
        name = ext.get("name", "noname")
        url = ext.get("url", None)
        description = ext.get("description", "")
195
        extension_tags = ext.get("tags", [])
A
AUTOMATIC 已提交
196 197 198 199

        if url is None:
            continue

200 201 202 203
        if len([x for x in extension_tags if x in tags_to_hide]) > 0:
            hidden += 1
            continue

A
AUTOMATIC 已提交
204 205 206 207
        existing = installed_extension_urls.get(normalize_git_url(url), None)

        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">"""

208 209
        tags_text = ", ".join([f"<span class='extension-tag' title='{tags.get(x, '')}'>{x}</span>" for x in extension_tags])

A
AUTOMATIC 已提交
210 211
        code += f"""
            <tr>
212
                <td><a href="{html.escape(url)}" target="_blank">{html.escape(name)}</a><br />{tags_text}</td>
A
AUTOMATIC 已提交
213 214 215 216 217 218 219 220 221 222
                <td>{html.escape(description)}</td>
                <td>{install_code}</td>
            </tr>
    """

    code += """
        </tbody>
    </table>
    """

223 224 225 226
    if hidden > 0:
        code += f"<p>Extension hidden: {hidden}</p>"

    return code, list(tags)
A
AUTOMATIC 已提交
227 228


229 230 231 232 233 234 235 236 237 238
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"):

                with gr.Row():
                    apply = gr.Button(value="Apply and restart UI", variant="primary")
                    check = gr.Button(value="Check for updates")
A
AUTOMATIC 已提交
239 240
                    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)
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257

                extensions_table = gr.HTML(lambda: extension_table())

                apply.click(
                    fn=apply_and_restart,
                    _js="extensions_apply",
                    inputs=[extensions_disabled_list, extensions_update_list],
                    outputs=[],
                )

                check.click(
                    fn=check_updates,
                    _js="extensions_check",
                    inputs=[],
                    outputs=[extensions_table],
                )

A
AUTOMATIC 已提交
258 259 260 261 262 263 264
            with gr.TabItem("Available"):
                with gr.Row():
                    refresh_available_extensions_button = gr.Button(value="Load from:", variant="primary")
                    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)
                    extension_to_install = gr.Text(elem_id="extension_to_install", visible=False)
                    install_extension_button = gr.Button(elem_id="install_extension_button", visible=False)

265 266 267
                with gr.Row():
                    hide_tags = gr.CheckboxGroup(value=["ads", "localization"], label="Hide extensions with tags", choices=["script", "ads", "localization"])

A
AUTOMATIC 已提交
268 269 270 271
                install_result = gr.HTML()
                available_extensions_table = gr.HTML()

                refresh_available_extensions_button.click(
272 273 274
                    fn=modules.ui.wrap_gradio_call(refresh_available_extensions, extra_outputs=[gr.update(), gr.update(), gr.update()]),
                    inputs=[available_extensions_index, hide_tags],
                    outputs=[available_extensions_index, available_extensions_table, hide_tags, install_result],
A
AUTOMATIC 已提交
275 276 277 278
                )

                install_extension_button.click(
                    fn=modules.ui.wrap_gradio_call(install_extension_from_index, extra_outputs=[gr.update(), gr.update()]),
279
                    inputs=[extension_to_install, hide_tags],
A
AUTOMATIC 已提交
280 281 282
                    outputs=[available_extensions_table, extensions_table, install_result],
                )

283 284 285 286 287 288
                hide_tags.change(
                    fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]),
                    inputs=[hide_tags],
                    outputs=[available_extensions_table, install_result]
                )

289 290 291
            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")
A
AUTOMATIC 已提交
292 293
                install_button = gr.Button(value="Install", variant="primary")
                install_result = gr.HTML(elem_id="extension_install_result")
294

A
AUTOMATIC 已提交
295
                install_button.click(
296 297
                    fn=modules.ui.wrap_gradio_call(install_extension_from_url, extra_outputs=[gr.update()]),
                    inputs=[install_dirname, install_url],
A
AUTOMATIC 已提交
298
                    outputs=[extensions_table, install_result],
299 300 301
                )

    return ui