stable-diffusion-webui

Форк
0
/
ui_extra_networks.py 
754 строки · 28.8 Кб
1
import functools
2
import os.path
3
import urllib.parse
4
from pathlib import Path
5
from typing import Optional, Union
6
from dataclasses import dataclass
7

8
from modules import shared, ui_extra_networks_user_metadata, errors, extra_networks, util
9
from modules.images import read_info_from_image, save_image_with_geninfo
10
import gradio as gr
11
import json
12
import html
13
from fastapi.exceptions import HTTPException
14

15
from modules.infotext_utils import image_from_url_text
16

17
extra_pages = []
18
allowed_dirs = set()
19
default_allowed_preview_extensions = ["png", "jpg", "jpeg", "webp", "gif"]
20

21
@functools.cache
22
def allowed_preview_extensions_with_extra(extra_extensions=None):
23
    return set(default_allowed_preview_extensions) | set(extra_extensions or [])
24

25

26
def allowed_preview_extensions():
27
    return allowed_preview_extensions_with_extra((shared.opts.samples_format, ))
28

29

30
@dataclass
31
class ExtraNetworksItem:
32
    """Wrapper for dictionaries representing ExtraNetworks items."""
33
    item: dict
34

35

36
def get_tree(paths: Union[str, list[str]], items: dict[str, ExtraNetworksItem]) -> dict:
37
    """Recursively builds a directory tree.
38

39
    Args:
40
        paths: Path or list of paths to directories. These paths are treated as roots from which
41
            the tree will be built.
42
        items: A dictionary associating filepaths to an ExtraNetworksItem instance.
43

44
    Returns:
45
        The result directory tree.
46
    """
47
    if isinstance(paths, (str,)):
48
        paths = [paths]
49

50
    def _get_tree(_paths: list[str], _root: str):
51
        _res = {}
52
        for path in _paths:
53
            relpath = os.path.relpath(path, _root)
54
            if os.path.isdir(path):
55
                dir_items = os.listdir(path)
56
                # Ignore empty directories.
57
                if not dir_items:
58
                    continue
59
                dir_tree = _get_tree([os.path.join(path, x) for x in dir_items], _root)
60
                # We only want to store non-empty folders in the tree.
61
                if dir_tree:
62
                    _res[relpath] = dir_tree
63
            else:
64
                if path not in items:
65
                    continue
66
                # Add the ExtraNetworksItem to the result.
67
                _res[relpath] = items[path]
68
        return _res
69

70
    res = {}
71
    # Handle each root directory separately.
72
    # Each root WILL have a key/value at the root of the result dict though
73
    # the value can be an empty dict if the directory is empty. We want these
74
    # placeholders for empty dirs so we can inform the user later.
75
    for path in paths:
76
        root = os.path.dirname(path)
77
        relpath = os.path.relpath(path, root)
78
        # Wrap the path in a list since that is what the `_get_tree` expects.
79
        res[relpath] = _get_tree([path], root)
80
        if res[relpath]:
81
            # We need to pull the inner path out one for these root dirs.
82
            res[relpath] = res[relpath][relpath]
83

84
    return res
85

86
def register_page(page):
87
    """registers extra networks page for the UI; recommend doing it in on_before_ui() callback for extensions"""
88

89
    extra_pages.append(page)
90
    allowed_dirs.clear()
91
    allowed_dirs.update(set(sum([x.allowed_directories_for_previews() for x in extra_pages], [])))
92

93

94
def fetch_file(filename: str = ""):
95
    from starlette.responses import FileResponse
96

97
    if not os.path.isfile(filename):
98
        raise HTTPException(status_code=404, detail="File not found")
99

100
    if not any(Path(x).absolute() in Path(filename).absolute().parents for x in allowed_dirs):
101
        raise ValueError(f"File cannot be fetched: {filename}. Must be in one of directories registered by extra pages.")
102

103
    ext = os.path.splitext(filename)[1].lower()[1:]
104
    if ext not in allowed_preview_extensions():
105
        raise ValueError(f"File cannot be fetched: {filename}. Extensions allowed: {allowed_preview_extensions()}.")
106

107
    # would profit from returning 304
108
    return FileResponse(filename, headers={"Accept-Ranges": "bytes"})
109

110

111
def get_metadata(page: str = "", item: str = ""):
112
    from starlette.responses import JSONResponse
113

114
    page = next(iter([x for x in extra_pages if x.name == page]), None)
115
    if page is None:
116
        return JSONResponse({})
117

118
    metadata = page.metadata.get(item)
119
    if metadata is None:
120
        return JSONResponse({})
121

122
    return JSONResponse({"metadata": json.dumps(metadata, indent=4, ensure_ascii=False)})
123

124

125
def get_single_card(page: str = "", tabname: str = "", name: str = ""):
126
    from starlette.responses import JSONResponse
127

128
    page = next(iter([x for x in extra_pages if x.name == page]), None)
129

130
    try:
131
        item = page.create_item(name, enable_filter=False)
132
        page.items[name] = item
133
    except Exception as e:
134
        errors.display(e, "creating item for extra network")
135
        item = page.items.get(name)
136

137
    page.read_user_metadata(item, use_cache=False)
138
    item_html = page.create_item_html(tabname, item, shared.html("extra-networks-card.html"))
139

140
    return JSONResponse({"html": item_html})
141

142

143
def add_pages_to_demo(app):
144
    app.add_api_route("/sd_extra_networks/thumb", fetch_file, methods=["GET"])
145
    app.add_api_route("/sd_extra_networks/metadata", get_metadata, methods=["GET"])
146
    app.add_api_route("/sd_extra_networks/get-single-card", get_single_card, methods=["GET"])
147

148

149
def quote_js(s):
150
    s = s.replace('\\', '\\\\')
151
    s = s.replace('"', '\\"')
152
    return f'"{s}"'
153

154
class ExtraNetworksPage:
155
    def __init__(self, title):
156
        self.title = title
157
        self.name = title.lower()
158
        # This is the actual name of the extra networks tab (not txt2img/img2img).
159
        self.extra_networks_tabname = self.name.replace(" ", "_")
160
        self.allow_prompt = True
161
        self.allow_negative_prompt = False
162
        self.metadata = {}
163
        self.items = {}
164
        self.lister = util.MassFileLister()
165
        # HTML Templates
166
        self.pane_tpl = shared.html("extra-networks-pane.html")
167
        self.card_tpl = shared.html("extra-networks-card.html")
168
        self.btn_tree_tpl = shared.html("extra-networks-tree-button.html")
169
        self.btn_copy_path_tpl = shared.html("extra-networks-copy-path-button.html")
170
        self.btn_metadata_tpl = shared.html("extra-networks-metadata-button.html")
171
        self.btn_edit_item_tpl = shared.html("extra-networks-edit-item-button.html")
172

173
    def refresh(self):
174
        pass
175

176
    def read_user_metadata(self, item, use_cache=True):
177
        filename = item.get("filename", None)
178
        metadata = extra_networks.get_user_metadata(filename, lister=self.lister if use_cache else None)
179

180
        desc = metadata.get("description", None)
181
        if desc is not None:
182
            item["description"] = desc
183

184
        item["user_metadata"] = metadata
185

186
    def link_preview(self, filename):
187
        quoted_filename = urllib.parse.quote(filename.replace('\\', '/'))
188
        mtime, _ = self.lister.mctime(filename)
189
        return f"./sd_extra_networks/thumb?filename={quoted_filename}&mtime={mtime}"
190

191
    def search_terms_from_path(self, filename, possible_directories=None):
192
        abspath = os.path.abspath(filename)
193
        for parentdir in (possible_directories if possible_directories is not None else self.allowed_directories_for_previews()):
194
            parentdir = os.path.dirname(os.path.abspath(parentdir))
195
            if abspath.startswith(parentdir):
196
                return os.path.relpath(abspath, parentdir)
197

198
        return ""
199

200
    def create_item_html(
201
        self,
202
        tabname: str,
203
        item: dict,
204
        template: Optional[str] = None,
205
    ) -> Union[str, dict]:
206
        """Generates HTML for a single ExtraNetworks Item.
207

208
        Args:
209
            tabname: The name of the active tab.
210
            item: Dictionary containing item information.
211
            template: Optional template string to use.
212

213
        Returns:
214
            If a template is passed: HTML string generated for this item.
215
                Can be empty if the item is not meant to be shown.
216
            If no template is passed: A dictionary containing the generated item's attributes.
217
        """
218
        preview = item.get("preview", None)
219
        style_height = f"height: {shared.opts.extra_networks_card_height}px;" if shared.opts.extra_networks_card_height else ''
220
        style_width = f"width: {shared.opts.extra_networks_card_width}px;" if shared.opts.extra_networks_card_width else ''
221
        style_font_size = f"font-size: {shared.opts.extra_networks_card_text_scale*100}%;"
222
        card_style = style_height + style_width + style_font_size
223
        background_image = f'<img src="{html.escape(preview)}" class="preview" loading="lazy">' if preview else ''
224

225
        onclick = item.get("onclick", None)
226
        if onclick is None:
227
            # Don't quote prompt/neg_prompt since they are stored as js strings already.
228
            onclick_js_tpl = "cardClicked('{tabname}', {prompt}, {neg_prompt}, {allow_neg});"
229
            onclick = onclick_js_tpl.format(
230
                **{
231
                    "tabname": tabname,
232
                    "prompt": item["prompt"],
233
                    "neg_prompt": item.get("negative_prompt", "''"),
234
                    "allow_neg": str(self.allow_negative_prompt).lower(),
235
                }
236
            )
237
            onclick = html.escape(onclick)
238

239
        btn_copy_path = self.btn_copy_path_tpl.format(**{"filename": item["filename"]})
240
        btn_metadata = ""
241
        metadata = item.get("metadata")
242
        if metadata:
243
            btn_metadata = self.btn_metadata_tpl.format(
244
                **{
245
                    "extra_networks_tabname": self.extra_networks_tabname,
246
                    "name": html.escape(item["name"]),
247
                }
248
            )
249
        btn_edit_item = self.btn_edit_item_tpl.format(
250
            **{
251
                "tabname": tabname,
252
                "extra_networks_tabname": self.extra_networks_tabname,
253
                "name": html.escape(item["name"]),
254
            }
255
        )
256

257
        local_path = ""
258
        filename = item.get("filename", "")
259
        for reldir in self.allowed_directories_for_previews():
260
            absdir = os.path.abspath(reldir)
261

262
            if filename.startswith(absdir):
263
                local_path = filename[len(absdir):]
264

265
        # if this is true, the item must not be shown in the default view, and must instead only be
266
        # shown when searching for it
267
        if shared.opts.extra_networks_hidden_models == "Always":
268
            search_only = False
269
        else:
270
            search_only = "/." in local_path or "\\." in local_path
271

272
        if search_only and shared.opts.extra_networks_hidden_models == "Never":
273
            return ""
274

275
        sort_keys = " ".join(
276
            [
277
                f'data-sort-{k}="{html.escape(str(v))}"'
278
                for k, v in item.get("sort_keys", {}).items()
279
            ]
280
        ).strip()
281

282
        search_terms_html = ""
283
        search_term_template = "<span class='hidden {class}'>{search_term}</span>"
284
        for search_term in item.get("search_terms", []):
285
            search_terms_html += search_term_template.format(
286
                **{
287
                    "class": f"search_terms{' search_only' if search_only else ''}",
288
                    "search_term": search_term,
289
                }
290
            )
291

292
        description = (item.get("description", "") or "" if shared.opts.extra_networks_card_show_desc else "")
293
        if not shared.opts.extra_networks_card_description_is_html:
294
            description = html.escape(description)
295

296
        # Some items here might not be used depending on HTML template used.
297
        args = {
298
            "background_image": background_image,
299
            "card_clicked": onclick,
300
            "copy_path_button": btn_copy_path,
301
            "description": description,
302
            "edit_button": btn_edit_item,
303
            "local_preview": quote_js(item["local_preview"]),
304
            "metadata_button": btn_metadata,
305
            "name": html.escape(item["name"]),
306
            "prompt": item.get("prompt", None),
307
            "save_card_preview": html.escape(f"return saveCardPreview(event, '{tabname}', '{item['local_preview']}');"),
308
            "search_only": " search_only" if search_only else "",
309
            "search_terms": search_terms_html,
310
            "sort_keys": sort_keys,
311
            "style": card_style,
312
            "tabname": tabname,
313
            "extra_networks_tabname": self.extra_networks_tabname,
314
        }
315

316
        if template:
317
            return template.format(**args)
318
        else:
319
            return args
320

321
    def create_tree_dir_item_html(
322
        self,
323
        tabname: str,
324
        dir_path: str,
325
        content: Optional[str] = None,
326
    ) -> Optional[str]:
327
        """Generates HTML for a directory item in the tree.
328

329
        The generated HTML is of the format:
330
        ```html
331
        <li class="tree-list-item tree-list-item--has-subitem">
332
            <div class="tree-list-content tree-list-content-dir"></div>
333
            <ul class="tree-list tree-list--subgroup">
334
                {content}
335
            </ul>
336
        </li>
337
        ```
338

339
        Args:
340
            tabname: The name of the active tab.
341
            dir_path: Path to the directory for this item.
342
            content: Optional HTML string that will be wrapped by this <ul>.
343

344
        Returns:
345
            HTML formatted string.
346
        """
347
        if not content:
348
            return None
349

350
        btn = self.btn_tree_tpl.format(
351
            **{
352
                "search_terms": "",
353
                "subclass": "tree-list-content-dir",
354
                "tabname": tabname,
355
                "extra_networks_tabname": self.extra_networks_tabname,
356
                "onclick_extra": "",
357
                "data_path": dir_path,
358
                "data_hash": "",
359
                "action_list_item_action_leading": "<i class='tree-list-item-action-chevron'></i>",
360
                "action_list_item_visual_leading": "🗀",
361
                "action_list_item_label": os.path.basename(dir_path),
362
                "action_list_item_visual_trailing": "",
363
                "action_list_item_action_trailing": "",
364
            }
365
        )
366
        ul = f"<ul class='tree-list tree-list--subgroup' hidden>{content}</ul>"
367
        return (
368
            "<li class='tree-list-item tree-list-item--has-subitem' data-tree-entry-type='dir'>"
369
            f"{btn}{ul}"
370
            "</li>"
371
        )
372

373
    def create_tree_file_item_html(self, tabname: str, file_path: str, item: dict) -> str:
374
        """Generates HTML for a file item in the tree.
375

376
        The generated HTML is of the format:
377
        ```html
378
        <li class="tree-list-item tree-list-item--subitem">
379
            <span data-filterable-item-text hidden></span>
380
            <div class="tree-list-content tree-list-content-file"></div>
381
        </li>
382
        ```
383

384
        Args:
385
            tabname: The name of the active tab.
386
            file_path: The path to the file for this item.
387
            item: Dictionary containing the item information.
388

389
        Returns:
390
            HTML formatted string.
391
        """
392
        item_html_args = self.create_item_html(tabname, item)
393
        action_buttons = "".join(
394
            [
395
                item_html_args["copy_path_button"],
396
                item_html_args["metadata_button"],
397
                item_html_args["edit_button"],
398
            ]
399
        )
400
        action_buttons = f"<div class=\"button-row\">{action_buttons}</div>"
401
        btn = self.btn_tree_tpl.format(
402
            **{
403
                "search_terms": "",
404
                "subclass": "tree-list-content-file",
405
                "tabname": tabname,
406
                "extra_networks_tabname": self.extra_networks_tabname,
407
                "onclick_extra": item_html_args["card_clicked"],
408
                "data_path": file_path,
409
                "data_hash": item["shorthash"],
410
                "action_list_item_action_leading": "<i class='tree-list-item-action-chevron'></i>",
411
                "action_list_item_visual_leading": "🗎",
412
                "action_list_item_label": item["name"],
413
                "action_list_item_visual_trailing": "",
414
                "action_list_item_action_trailing": action_buttons,
415
            }
416
        )
417
        return (
418
            "<li class='tree-list-item tree-list-item--subitem' data-tree-entry-type='file'>"
419
            f"{btn}"
420
            "</li>"
421
        )
422

423
    def create_tree_view_html(self, tabname: str) -> str:
424
        """Generates HTML for displaying folders in a tree view.
425

426
        Args:
427
            tabname: The name of the active tab.
428

429
        Returns:
430
            HTML string generated for this tree view.
431
        """
432
        res = ""
433

434
        # Setup the tree dictionary.
435
        roots = self.allowed_directories_for_previews()
436
        tree_items = {v["filename"]: ExtraNetworksItem(v) for v in self.items.values()}
437
        tree = get_tree([os.path.abspath(x) for x in roots], items=tree_items)
438

439
        if not tree:
440
            return res
441

442
        def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> Optional[str]:
443
            """Recursively builds HTML for a tree.
444

445
            Args:
446
                data: Dictionary representing a directory tree. Can be NoneType.
447
                    Data keys should be absolute paths from the root and values
448
                    should be subdirectory trees or an ExtraNetworksItem.
449

450
            Returns:
451
                If data is not None: HTML string
452
                Else: None
453
            """
454
            if not data:
455
                return None
456

457
            # Lists for storing <li> items html for directories and files separately.
458
            _dir_li = []
459
            _file_li = []
460

461
            for k, v in sorted(data.items(), key=lambda x: shared.natural_sort_key(x[0])):
462
                if isinstance(v, (ExtraNetworksItem,)):
463
                    _file_li.append(self.create_tree_file_item_html(tabname, k, v.item))
464
                else:
465
                    _dir_li.append(self.create_tree_dir_item_html(tabname, k, _build_tree(v)))
466

467
            # Directories should always be displayed before files so we order them here.
468
            return "".join(_dir_li) + "".join(_file_li)
469

470
        # Add each root directory to the tree.
471
        for k, v in sorted(tree.items(), key=lambda x: shared.natural_sort_key(x[0])):
472
            item_html = self.create_tree_dir_item_html(tabname, k, _build_tree(v))
473
            # Only add non-empty entries to the tree.
474
            if item_html is not None:
475
                res += item_html
476

477
        return f"<ul class='tree-list tree-list--tree'>{res}</ul>"
478

479
    def create_card_view_html(self, tabname: str, *, none_message) -> str:
480
        """Generates HTML for the network Card View section for a tab.
481

482
        This HTML goes into the `extra-networks-pane.html` <div> with
483
        `id='{tabname}_{extra_networks_tabname}_cards`.
484

485
        Args:
486
            tabname: The name of the active tab.
487
            none_message: HTML text to show when there are no cards.
488

489
        Returns:
490
            HTML formatted string.
491
        """
492
        res = ""
493
        for item in self.items.values():
494
            res += self.create_item_html(tabname, item, self.card_tpl)
495

496
        if res == "":
497
            dirs = "".join([f"<li>{x}</li>" for x in self.allowed_directories_for_previews()])
498
            res = none_message or shared.html("extra-networks-no-cards.html").format(dirs=dirs)
499

500
        return res
501

502
    def create_html(self, tabname, *, empty=False):
503
        """Generates an HTML string for the current pane.
504

505
        The generated HTML uses `extra-networks-pane.html` as a template.
506

507
        Args:
508
            tabname: The name of the active tab.
509
            empty: create an empty HTML page with no items
510

511
        Returns:
512
            HTML formatted string.
513
        """
514
        self.lister.reset()
515
        self.metadata = {}
516

517
        items_list = [] if empty else self.list_items()
518
        self.items = {x["name"]: x for x in items_list}
519

520
        # Populate the instance metadata for each item.
521
        for item in self.items.values():
522
            metadata = item.get("metadata")
523
            if metadata:
524
                self.metadata[item["name"]] = metadata
525

526
            if "user_metadata" not in item:
527
                self.read_user_metadata(item)
528

529
        data_sortdir = shared.opts.extra_networks_card_order
530
        data_sortmode = shared.opts.extra_networks_card_order_field.lower().replace("sort", "").replace(" ", "_").rstrip("_").strip()
531
        data_sortkey = f"{data_sortmode}-{data_sortdir}-{len(self.items)}"
532
        tree_view_btn_extra_class = ""
533
        tree_view_div_extra_class = "hidden"
534
        if shared.opts.extra_networks_tree_view_default_enabled:
535
            tree_view_btn_extra_class = "extra-network-control--enabled"
536
            tree_view_div_extra_class = ""
537

538
        return self.pane_tpl.format(
539
            **{
540
                "tabname": tabname,
541
                "extra_networks_tabname": self.extra_networks_tabname,
542
                "data_sortmode": data_sortmode,
543
                "data_sortkey": data_sortkey,
544
                "data_sortdir": data_sortdir,
545
                "tree_view_btn_extra_class": tree_view_btn_extra_class,
546
                "tree_view_div_extra_class": tree_view_div_extra_class,
547
                "tree_html": self.create_tree_view_html(tabname),
548
                "items_html": self.create_card_view_html(tabname, none_message="Loading..." if empty else None),
549
            }
550
        )
551

552
    def create_item(self, name, index=None):
553
        raise NotImplementedError()
554

555
    def list_items(self):
556
        raise NotImplementedError()
557

558
    def allowed_directories_for_previews(self):
559
        return []
560

561
    def get_sort_keys(self, path):
562
        """
563
        List of default keys used for sorting in the UI.
564
        """
565
        pth = Path(path)
566
        mtime, ctime = self.lister.mctime(path)
567
        return {
568
            "date_created": int(mtime),
569
            "date_modified": int(ctime),
570
            "name": pth.name.lower(),
571
            "path": str(pth).lower(),
572
        }
573

574
    def find_preview(self, path):
575
        """
576
        Find a preview PNG for a given path (without extension) and call link_preview on it.
577
        """
578

579
        potential_files = sum([[f"{path}.{ext}", f"{path}.preview.{ext}"] for ext in allowed_preview_extensions()], [])
580

581
        for file in potential_files:
582
            if self.lister.exists(file):
583
                return self.link_preview(file)
584

585
        return None
586

587
    def find_description(self, path):
588
        """
589
        Find and read a description file for a given path (without extension).
590
        """
591
        for file in [f"{path}.txt", f"{path}.description.txt"]:
592
            if not self.lister.exists(file):
593
                continue
594

595
            try:
596
                with open(file, "r", encoding="utf-8", errors="replace") as f:
597
                    return f.read()
598
            except OSError:
599
                pass
600
        return None
601

602
    def create_user_metadata_editor(self, ui, tabname):
603
        return ui_extra_networks_user_metadata.UserMetadataEditor(ui, tabname, self)
604

605

606
def initialize():
607
    extra_pages.clear()
608

609

610
def register_default_pages():
611
    from modules.ui_extra_networks_textual_inversion import ExtraNetworksPageTextualInversion
612
    from modules.ui_extra_networks_hypernets import ExtraNetworksPageHypernetworks
613
    from modules.ui_extra_networks_checkpoints import ExtraNetworksPageCheckpoints
614
    register_page(ExtraNetworksPageTextualInversion())
615
    register_page(ExtraNetworksPageHypernetworks())
616
    register_page(ExtraNetworksPageCheckpoints())
617

618

619
class ExtraNetworksUi:
620
    def __init__(self):
621
        self.pages = None
622
        """gradio HTML components related to extra networks' pages"""
623

624
        self.page_contents = None
625
        """HTML content of the above; empty initially, filled when extra pages have to be shown"""
626

627
        self.stored_extra_pages = None
628

629
        self.button_save_preview = None
630
        self.preview_target_filename = None
631

632
        self.tabname = None
633

634

635
def pages_in_preferred_order(pages):
636
    tab_order = [x.lower().strip() for x in shared.opts.ui_extra_networks_tab_reorder.split(",")]
637

638
    def tab_name_score(name):
639
        name = name.lower()
640
        for i, possible_match in enumerate(tab_order):
641
            if possible_match in name:
642
                return i
643

644
        return len(pages)
645

646
    tab_scores = {page.name: (tab_name_score(page.name), original_index) for original_index, page in enumerate(pages)}
647

648
    return sorted(pages, key=lambda x: tab_scores[x.name])
649

650

651
def create_ui(interface: gr.Blocks, unrelated_tabs, tabname):
652
    ui = ExtraNetworksUi()
653
    ui.pages = []
654
    ui.pages_contents = []
655
    ui.user_metadata_editors = []
656
    ui.stored_extra_pages = pages_in_preferred_order(extra_pages.copy())
657
    ui.tabname = tabname
658

659
    related_tabs = []
660

661
    for page in ui.stored_extra_pages:
662
        with gr.Tab(page.title, elem_id=f"{tabname}_{page.extra_networks_tabname}", elem_classes=["extra-page"]) as tab:
663
            with gr.Column(elem_id=f"{tabname}_{page.extra_networks_tabname}_prompts", elem_classes=["extra-page-prompts"]):
664
                pass
665

666
            elem_id = f"{tabname}_{page.extra_networks_tabname}_cards_html"
667
            page_elem = gr.HTML(page.create_html(tabname, empty=True), elem_id=elem_id)
668
            ui.pages.append(page_elem)
669
            editor = page.create_user_metadata_editor(ui, tabname)
670
            editor.create_ui()
671
            ui.user_metadata_editors.append(editor)
672
            related_tabs.append(tab)
673

674
    ui.button_save_preview = gr.Button('Save preview', elem_id=f"{tabname}_save_preview", visible=False)
675
    ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=f"{tabname}_preview_filename", visible=False)
676

677
    for tab in unrelated_tabs:
678
        tab.select(fn=None, _js=f"function(){{extraNetworksUnrelatedTabSelected('{tabname}');}}", inputs=[], outputs=[], show_progress=False)
679

680
    for page, tab in zip(ui.stored_extra_pages, related_tabs):
681
        jscode = (
682
            "function(){{"
683
            f"extraNetworksTabSelected('{tabname}', '{tabname}_{page.extra_networks_tabname}_prompts', {str(page.allow_prompt).lower()}, {str(page.allow_negative_prompt).lower()}, '{tabname}_{page.extra_networks_tabname}');"
684
            f"applyExtraNetworkFilter('{tabname}_{page.extra_networks_tabname}');"
685
            "}}"
686
        )
687
        tab.select(fn=None, _js=jscode, inputs=[], outputs=[], show_progress=False)
688

689
        def refresh():
690
            for pg in ui.stored_extra_pages:
691
                pg.refresh()
692
            create_html()
693
            return ui.pages_contents
694

695
        button_refresh = gr.Button("Refresh", elem_id=f"{tabname}_{page.extra_networks_tabname}_extra_refresh_internal", visible=False)
696
        button_refresh.click(fn=refresh, inputs=[], outputs=ui.pages).then(fn=lambda: None, _js="function(){ " + f"applyExtraNetworkFilter('{tabname}_{page.extra_networks_tabname}');" + " }")
697

698
    def create_html():
699
        ui.pages_contents = [pg.create_html(ui.tabname) for pg in ui.stored_extra_pages]
700

701
    def pages_html():
702
        if not ui.pages_contents:
703
            create_html()
704
        return ui.pages_contents
705

706
    interface.load(fn=pages_html, inputs=[], outputs=ui.pages)
707

708
    return ui
709

710

711
def path_is_parent(parent_path, child_path):
712
    parent_path = os.path.abspath(parent_path)
713
    child_path = os.path.abspath(child_path)
714

715
    return child_path.startswith(parent_path)
716

717

718
def setup_ui(ui, gallery):
719
    def save_preview(index, images, filename):
720
        # this function is here for backwards compatibility and likely will be removed soon
721

722
        if len(images) == 0:
723
            print("There is no image in gallery to save as a preview.")
724
            return [page.create_html(ui.tabname) for page in ui.stored_extra_pages]
725

726
        index = int(index)
727
        index = 0 if index < 0 else index
728
        index = len(images) - 1 if index >= len(images) else index
729

730
        img_info = images[index if index >= 0 else 0]
731
        image = image_from_url_text(img_info)
732
        geninfo, items = read_info_from_image(image)
733

734
        is_allowed = False
735
        for extra_page in ui.stored_extra_pages:
736
            if any(path_is_parent(x, filename) for x in extra_page.allowed_directories_for_previews()):
737
                is_allowed = True
738
                break
739

740
        assert is_allowed, f'writing to {filename} is not allowed'
741

742
        save_image_with_geninfo(image, geninfo, filename)
743

744
        return [page.create_html(ui.tabname) for page in ui.stored_extra_pages]
745

746
    ui.button_save_preview.click(
747
        fn=save_preview,
748
        _js="function(x, y, z){return [selected_gallery_index(), y, z]}",
749
        inputs=[ui.preview_target_filename, gallery, ui.preview_target_filename],
750
        outputs=[*ui.pages]
751
    )
752

753
    for editor in ui.user_metadata_editors:
754
        editor.setup_ui(gallery)
755

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.